Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/ChannelMediaResource.cpp
Line
Count
Source (jump to first uncovered line)
1
/* vim:set ts=2 sw=2 sts=2 et cindent: */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "ChannelMediaResource.h"
7
8
#include "mozilla/dom/HTMLMediaElement.h"
9
#include "nsIAsyncVerifyRedirectCallback.h"
10
#include "nsICachingChannel.h"
11
#include "nsIClassOfService.h"
12
#include "nsIInputStream.h"
13
#include "nsIThreadRetargetableRequest.h"
14
#include "nsHttp.h"
15
#include "nsNetUtil.h"
16
17
static const uint32_t HTTP_PARTIAL_RESPONSE_CODE = 206;
18
static const uint32_t HTTP_OK_CODE = 200;
19
static const uint32_t HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE = 416;
20
21
mozilla::LazyLogModule gMediaResourceLog("MediaResource");
22
// Debug logging macro with object pointer and class name.
23
#define LOG(msg, ...)                                                          \
24
0
  DDMOZ_LOG(gMediaResourceLog, mozilla::LogLevel::Debug, msg, ##__VA_ARGS__)
25
26
namespace mozilla {
27
28
ChannelMediaResource::ChannelMediaResource(MediaResourceCallback* aCallback,
29
                                           nsIChannel* aChannel,
30
                                           nsIURI* aURI,
31
                                           bool aIsPrivateBrowsing)
32
  : BaseMediaResource(aCallback, aChannel, aURI)
33
  , mCacheStream(this, aIsPrivateBrowsing)
34
  , mSuspendAgent(mCacheStream)
35
0
{
36
0
}
37
38
ChannelMediaResource::~ChannelMediaResource()
39
0
{
40
0
  MOZ_ASSERT(mClosed);
41
0
  MOZ_ASSERT(!mChannel);
42
0
  MOZ_ASSERT(!mListener);
43
0
  if (mSharedInfo) {
44
0
    mSharedInfo->mResources.RemoveElement(this);
45
0
  }
46
0
}
47
48
// ChannelMediaResource::Listener just observes the channel and
49
// forwards notifications to the ChannelMediaResource. We use multiple
50
// listener objects so that when we open a new stream for a seek we can
51
// disconnect the old listener from the ChannelMediaResource and hook up
52
// a new listener, so notifications from the old channel are discarded
53
// and don't confuse us.
54
NS_IMPL_ISUPPORTS(ChannelMediaResource::Listener,
55
                  nsIRequestObserver,
56
                  nsIStreamListener,
57
                  nsIChannelEventSink,
58
                  nsIInterfaceRequestor,
59
                  nsIThreadRetargetableStreamListener)
60
61
nsresult
62
ChannelMediaResource::Listener::OnStartRequest(nsIRequest* aRequest,
63
                                               nsISupports* aContext)
64
0
{
65
0
  MOZ_ASSERT(NS_IsMainThread());
66
0
  if (!mResource)
67
0
    return NS_OK;
68
0
  return mResource->OnStartRequest(aRequest, mOffset);
69
0
}
70
71
nsresult
72
ChannelMediaResource::Listener::OnStopRequest(nsIRequest* aRequest,
73
                                              nsISupports* aContext,
74
                                              nsresult aStatus)
75
0
{
76
0
  MOZ_ASSERT(NS_IsMainThread());
77
0
  if (!mResource)
78
0
    return NS_OK;
79
0
  return mResource->OnStopRequest(aRequest, aStatus);
80
0
}
81
82
nsresult
83
ChannelMediaResource::Listener::OnDataAvailable(nsIRequest* aRequest,
84
                                                nsISupports* aContext,
85
                                                nsIInputStream* aStream,
86
                                                uint64_t aOffset,
87
                                                uint32_t aCount)
88
0
{
89
0
  // This might happen off the main thread.
90
0
  RefPtr<ChannelMediaResource> res;
91
0
  {
92
0
    MutexAutoLock lock(mMutex);
93
0
    res = mResource;
94
0
  }
95
0
  // Note Rekove() might happen at the same time to reset mResource. We check
96
0
  // the load ID to determine if the data is from an old channel.
97
0
  return res ? res->OnDataAvailable(mLoadID, aStream, aCount) : NS_OK;
98
0
}
99
100
nsresult
101
ChannelMediaResource::Listener::AsyncOnChannelRedirect(
102
  nsIChannel* aOld,
103
  nsIChannel* aNew,
104
  uint32_t aFlags,
105
  nsIAsyncVerifyRedirectCallback* cb)
106
0
{
107
0
  MOZ_ASSERT(NS_IsMainThread());
108
0
109
0
  nsresult rv = NS_OK;
110
0
  if (mResource) {
111
0
    rv = mResource->OnChannelRedirect(aOld, aNew, aFlags, mOffset);
112
0
  }
113
0
114
0
  if (NS_FAILED(rv)) {
115
0
    return rv;
116
0
  }
117
0
118
0
  cb->OnRedirectVerifyCallback(NS_OK);
119
0
  return NS_OK;
120
0
}
121
122
nsresult
123
ChannelMediaResource::Listener::CheckListenerChain()
124
0
{
125
0
  return NS_OK;
126
0
}
127
128
nsresult
129
ChannelMediaResource::Listener::GetInterface(const nsIID& aIID, void** aResult)
130
0
{
131
0
  return QueryInterface(aIID, aResult);
132
0
}
133
134
void
135
ChannelMediaResource::Listener::Revoke()
136
0
{
137
0
  MOZ_ASSERT(NS_IsMainThread());
138
0
  MutexAutoLock lock(mMutex);
139
0
  mResource = nullptr;
140
0
}
141
142
static bool
143
IsPayloadCompressed(nsIHttpChannel* aChannel)
144
0
{
145
0
  nsAutoCString encoding;
146
0
  Unused << aChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"), encoding);
147
0
  return encoding.Length() > 0;
148
0
}
149
150
nsresult
151
ChannelMediaResource::OnStartRequest(nsIRequest* aRequest,
152
                                     int64_t aRequestOffset)
153
0
{
154
0
  NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
155
0
  MOZ_DIAGNOSTIC_ASSERT(!mClosed);
156
0
157
0
  MediaDecoderOwner* owner = mCallback->GetMediaOwner();
158
0
  MOZ_DIAGNOSTIC_ASSERT(owner);
159
0
  dom::HTMLMediaElement* element = owner->GetMediaElement();
160
0
  MOZ_DIAGNOSTIC_ASSERT(element);
161
0
162
0
  nsresult status;
163
0
  nsresult rv = aRequest->GetStatus(&status);
164
0
  NS_ENSURE_SUCCESS(rv, rv);
165
0
166
0
  if (status == NS_BINDING_ABORTED) {
167
0
    // Request was aborted before we had a chance to receive any data, or
168
0
    // even an OnStartRequest(). Close the channel. This is important, as
169
0
    // we don't want to mess up our state, as if we're cloned that would
170
0
    // cause the clone to copy incorrect metadata (like whether we're
171
0
    // infinite for example).
172
0
    CloseChannel();
173
0
    return status;
174
0
  }
175
0
176
0
  if (element->ShouldCheckAllowOrigin()) {
177
0
    // If the request was cancelled by nsCORSListenerProxy due to failing
178
0
    // the CORS security check, send an error through to the media element.
179
0
    if (status == NS_ERROR_DOM_BAD_URI) {
180
0
      mCallback->NotifyNetworkError(MediaResult(status, "CORS not allowed"));
181
0
      return NS_ERROR_DOM_BAD_URI;
182
0
    }
183
0
  }
184
0
185
0
  nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
186
0
  bool seekable = false;
187
0
  int64_t length = -1;
188
0
  int64_t startOffset = aRequestOffset;
189
0
190
0
  if (hc) {
191
0
    uint32_t responseStatus = 0;
192
0
    Unused << hc->GetResponseStatus(&responseStatus);
193
0
    bool succeeded = false;
194
0
    Unused << hc->GetRequestSucceeded(&succeeded);
195
0
196
0
    if (!succeeded && NS_SUCCEEDED(status)) {
197
0
      // HTTP-level error (e.g. 4xx); treat this as a fatal network-level error.
198
0
      // We might get this on a seek.
199
0
      // (Note that lower-level errors indicated by NS_FAILED(status) are
200
0
      // handled in OnStopRequest.)
201
0
      // A 416 error should treated as EOF here... it's possible
202
0
      // that we don't get Content-Length, we read N bytes, then we
203
0
      // suspend and resume, the resume reopens the channel and we seek to
204
0
      // offset N, but there are no more bytes, so we get a 416
205
0
      // "Requested Range Not Satisfiable".
206
0
      if (responseStatus == HTTP_REQUESTED_RANGE_NOT_SATISFIABLE_CODE) {
207
0
        // OnStopRequest will not be fired, so we need to do some of its
208
0
        // work here. Note we need to pass the load ID first so the following
209
0
        // NotifyDataEnded() can pass the ID check.
210
0
        mCacheStream.NotifyLoadID(mLoadID);
211
0
        mCacheStream.NotifyDataEnded(mLoadID, status);
212
0
      } else {
213
0
        mCallback->NotifyNetworkError(
214
0
          MediaResult(NS_ERROR_FAILURE, "HTTP error"));
215
0
      }
216
0
217
0
      // This disconnects our listener so we don't get any more data. We
218
0
      // certainly don't want an error page to end up in our cache!
219
0
      CloseChannel();
220
0
      return NS_OK;
221
0
    }
222
0
223
0
    nsAutoCString ranges;
224
0
    Unused << hc->GetResponseHeader(NS_LITERAL_CSTRING("Accept-Ranges"),
225
0
                                    ranges);
226
0
    bool acceptsRanges =
227
0
      net::nsHttp::FindToken(ranges.get(), "bytes", HTTP_HEADER_VALUE_SEPS);
228
0
229
0
    int64_t contentLength = -1;
230
0
    const bool isCompressed = IsPayloadCompressed(hc);
231
0
    if (!isCompressed) {
232
0
      hc->GetContentLength(&contentLength);
233
0
    }
234
0
235
0
    // Check response code for byte-range requests (seeking, chunk requests).
236
0
    // We don't expect to get a 206 response for a compressed stream, but
237
0
    // double check just to be sure.
238
0
    if (!isCompressed && responseStatus == HTTP_PARTIAL_RESPONSE_CODE) {
239
0
      // Parse Content-Range header.
240
0
      int64_t rangeStart = 0;
241
0
      int64_t rangeEnd = 0;
242
0
      int64_t rangeTotal = 0;
243
0
      rv = ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal);
244
0
245
0
      // We received 'Content-Range', so the server accepts range requests.
246
0
      bool gotRangeHeader = NS_SUCCEEDED(rv);
247
0
248
0
      if (gotRangeHeader) {
249
0
        startOffset = rangeStart;
250
0
        // We received 'Content-Range', so the server accepts range requests.
251
0
        // Notify media cache about the length and start offset of data received.
252
0
        // Note: If aRangeTotal == -1, then the total bytes is unknown at this stage.
253
0
        //       For now, tell the decoder that the stream is infinite.
254
0
        if (rangeTotal != -1) {
255
0
          contentLength = std::max(contentLength, rangeTotal);
256
0
        }
257
0
      }
258
0
      acceptsRanges = gotRangeHeader;
259
0
    } else if (responseStatus == HTTP_OK_CODE) {
260
0
      // HTTP_OK_CODE means data will be sent from the start of the stream.
261
0
      startOffset = 0;
262
0
263
0
      if (aRequestOffset > 0) {
264
0
        // If HTTP_OK_CODE is responded for a non-zero range request, we have
265
0
        // to assume seeking doesn't work.
266
0
        acceptsRanges = false;
267
0
      }
268
0
    }
269
0
    if (aRequestOffset == 0 && contentLength >= 0 &&
270
0
        (responseStatus == HTTP_OK_CODE ||
271
0
         responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
272
0
      length = contentLength;
273
0
    }
274
0
    // XXX we probably should examine the Content-Range header in case
275
0
    // the server gave us a range which is not quite what we asked for
276
0
277
0
    // If we get an HTTP_OK_CODE response to our byte range request,
278
0
    // and the server isn't sending Accept-Ranges:bytes then we don't
279
0
    // support seeking. We also can't seek in compressed streams.
280
0
    seekable = !isCompressed && acceptsRanges;
281
0
  } else {
282
0
    // Not an HTTP channel. Assume data will be sent from position zero.
283
0
    startOffset = 0;
284
0
  }
285
0
286
0
  // Update principals before OnDataAvailable() putting the data in the cache.
287
0
  // This is important, we want to make sure all principals are updated before
288
0
  // any consumer can see the new data.
289
0
  UpdatePrincipal();
290
0
  if (owner->HasError()) {
291
0
    // Updating the principal resulted in an error. Abort the load.
292
0
    CloseChannel();
293
0
    return NS_OK;
294
0
  }
295
0
296
0
  mCacheStream.NotifyDataStarted(mLoadID, startOffset, seekable, length);
297
0
  mIsTransportSeekable = seekable;
298
0
  if (mFirstReadLength < 0) {
299
0
    mFirstReadLength = length;
300
0
  }
301
0
302
0
  mSuspendAgent.Delegate(mChannel);
303
0
304
0
  // Fires an initial progress event.
305
0
  owner->DownloadProgressed();
306
0
307
0
  nsCOMPtr<nsIThreadRetargetableRequest> retarget;
308
0
  if (Preferences::GetBool("media.omt_data_delivery.enabled", false) &&
309
0
      (retarget = do_QueryInterface(aRequest))) {
310
0
    // Note this will not always succeed. We need to handle the case where
311
0
    // all resources sharing the same cache might run their data callbacks
312
0
    // on different threads.
313
0
    retarget->RetargetDeliveryTo(mCacheStream.OwnerThread());
314
0
  }
315
0
316
0
  return NS_OK;
317
0
}
318
319
bool
320
ChannelMediaResource::IsTransportSeekable()
321
0
{
322
0
  MOZ_ASSERT(NS_IsMainThread());
323
0
  // We Report the transport as seekable if we know we will never seek into
324
0
  // the underlying transport. As the MediaCache reads content by block of
325
0
  // BLOCK_SIZE bytes, so the content length is less it will always be fully
326
0
  // read from offset = 0 and we can then always successfully seek within this
327
0
  // buffered content.
328
0
  return mIsTransportSeekable ||
329
0
         (mFirstReadLength > 0 &&
330
0
          mFirstReadLength < MediaCacheStream::BLOCK_SIZE);
331
0
}
332
333
nsresult
334
ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
335
                                              int64_t& aRangeStart,
336
                                              int64_t& aRangeEnd,
337
                                              int64_t& aRangeTotal) const
338
0
{
339
0
  NS_ENSURE_ARG(aHttpChan);
340
0
341
0
  nsAutoCString rangeStr;
342
0
  nsresult rv = aHttpChan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Range"),
343
0
                                             rangeStr);
344
0
  NS_ENSURE_SUCCESS(rv, rv);
345
0
  NS_ENSURE_FALSE(rangeStr.IsEmpty(), NS_ERROR_ILLEGAL_VALUE);
346
0
347
0
  // Parse the range header: e.g. Content-Range: bytes 7000-7999/8000.
348
0
  int32_t spacePos = rangeStr.Find(NS_LITERAL_CSTRING(" "));
349
0
  int32_t dashPos = rangeStr.Find(NS_LITERAL_CSTRING("-"), true, spacePos);
350
0
  int32_t slashPos = rangeStr.Find(NS_LITERAL_CSTRING("/"), true, dashPos);
351
0
352
0
  nsAutoCString aRangeStartText;
353
0
  rangeStr.Mid(aRangeStartText, spacePos+1, dashPos-(spacePos+1));
354
0
  aRangeStart = aRangeStartText.ToInteger64(&rv);
355
0
  NS_ENSURE_SUCCESS(rv, rv);
356
0
  NS_ENSURE_TRUE(0 <= aRangeStart, NS_ERROR_ILLEGAL_VALUE);
357
0
358
0
  nsAutoCString aRangeEndText;
359
0
  rangeStr.Mid(aRangeEndText, dashPos+1, slashPos-(dashPos+1));
360
0
  aRangeEnd = aRangeEndText.ToInteger64(&rv);
361
0
  NS_ENSURE_SUCCESS(rv, rv);
362
0
  NS_ENSURE_TRUE(aRangeStart < aRangeEnd, NS_ERROR_ILLEGAL_VALUE);
363
0
364
0
  nsAutoCString aRangeTotalText;
365
0
  rangeStr.Right(aRangeTotalText, rangeStr.Length()-(slashPos+1));
366
0
  if (aRangeTotalText[0] == '*') {
367
0
    aRangeTotal = -1;
368
0
  } else {
369
0
    aRangeTotal = aRangeTotalText.ToInteger64(&rv);
370
0
    NS_ENSURE_TRUE(aRangeEnd < aRangeTotal, NS_ERROR_ILLEGAL_VALUE);
371
0
    NS_ENSURE_SUCCESS(rv, rv);
372
0
  }
373
0
374
0
  LOG("Received bytes [%" PRId64 "] to [%" PRId64 "] of [%" PRId64 "] for decoder[%p]",
375
0
      aRangeStart, aRangeEnd, aRangeTotal, mCallback.get());
376
0
377
0
  return NS_OK;
378
0
}
379
380
nsresult
381
ChannelMediaResource::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
382
0
{
383
0
  NS_ASSERTION(mChannel.get() == aRequest, "Wrong channel!");
384
0
  NS_ASSERTION(!mSuspendAgent.IsSuspended(),
385
0
               "How can OnStopRequest fire while we're suspended?");
386
0
  MOZ_DIAGNOSTIC_ASSERT(!mClosed);
387
0
388
0
  // Move this request back into the foreground.  This is necessary for
389
0
  // requests owned by video documents to ensure the load group fires
390
0
  // OnStopRequest when restoring from session history.
391
0
  nsLoadFlags loadFlags;
392
0
  DebugOnly<nsresult> rv = mChannel->GetLoadFlags(&loadFlags);
393
0
  NS_ASSERTION(NS_SUCCEEDED(rv), "GetLoadFlags() failed!");
394
0
395
0
  if (loadFlags & nsIRequest::LOAD_BACKGROUND) {
396
0
    ModifyLoadFlags(loadFlags & ~nsIRequest::LOAD_BACKGROUND);
397
0
  }
398
0
399
0
  // Note that aStatus might have succeeded --- this might be a normal close
400
0
  // --- even in situations where the server cut us off because we were
401
0
  // suspended. It is also possible that the server sends us fewer bytes than
402
0
  // requested. So we need to "reopen on error" in that case too. The only
403
0
  // cases where we don't need to reopen are when *we* closed the stream.
404
0
  // But don't reopen if we need to seek and we don't think we can... that would
405
0
  // cause us to just re-read the stream, which would be really bad.
406
0
  /*
407
0
   * | length |    offset |   reopen |
408
0
   * +--------+-----------+----------+
409
0
   * |     -1 |         0 |      yes |
410
0
   * +--------+-----------+----------+
411
0
   * |     -1 |       > 0 | seekable |
412
0
   * +--------+-----------+----------+
413
0
   * |      0 |         X |       no |
414
0
   * +--------+-----------+----------+
415
0
   * |    > 0 |         0 |      yes |
416
0
   * +--------+-----------+----------+
417
0
   * |    > 0 | != length | seekable |
418
0
   * +--------+-----------+----------+
419
0
   * |    > 0 | == length |       no |
420
0
   */
421
0
  if (aStatus != NS_ERROR_PARSED_DATA_CACHED && aStatus != NS_BINDING_ABORTED) {
422
0
    auto lengthAndOffset = mCacheStream.GetLengthAndOffset();
423
0
    int64_t length = lengthAndOffset.mLength;
424
0
    int64_t offset = lengthAndOffset.mOffset;
425
0
    if ((offset == 0 || mIsTransportSeekable) && offset != length) {
426
0
      // If the stream did close normally, restart the channel if we're either
427
0
      // at the start of the resource, or if the server is seekable and we're
428
0
      // not at the end of stream. We don't restart the stream if we're at the
429
0
      // end because not all web servers handle this case consistently; see:
430
0
      // https://bugzilla.mozilla.org/show_bug.cgi?id=1373618#c36
431
0
      nsresult rv = Seek(offset, false);
432
0
      if (NS_SUCCEEDED(rv)) {
433
0
        return rv;
434
0
      }
435
0
      // Close the streams that failed due to error. This will cause all
436
0
      // client Read and Seek operations on those streams to fail. Blocked
437
0
      // Reads will also be woken up.
438
0
      Close();
439
0
    }
440
0
  }
441
0
442
0
  mCacheStream.NotifyDataEnded(mLoadID, aStatus);
443
0
  return NS_OK;
444
0
}
445
446
nsresult
447
ChannelMediaResource::OnChannelRedirect(nsIChannel* aOld,
448
                                        nsIChannel* aNew,
449
                                        uint32_t aFlags,
450
                                        int64_t aOffset)
451
0
{
452
0
  // OnChannelRedirect() is followed by OnStartRequest() where we will
453
0
  // call mSuspendAgent.Delegate().
454
0
  mChannel = aNew;
455
0
  return SetupChannelHeaders(aOffset);
456
0
}
457
458
nsresult
459
ChannelMediaResource::CopySegmentToCache(nsIInputStream* aInStream,
460
                                         void* aClosure,
461
                                         const char* aFromSegment,
462
                                         uint32_t aToOffset,
463
                                         uint32_t aCount,
464
                                         uint32_t* aWriteCount)
465
0
{
466
0
  *aWriteCount = aCount;
467
0
  Closure* closure = static_cast<Closure*>(aClosure);
468
0
  MediaCacheStream* cacheStream = &closure->mResource->mCacheStream;
469
0
  if (cacheStream->OwnerThread()->IsOnCurrentThread()) {
470
0
    cacheStream->NotifyDataReceived(
471
0
      closure->mLoadID, aCount, reinterpret_cast<const uint8_t*>(aFromSegment));
472
0
    return NS_OK;
473
0
  }
474
0
475
0
  RefPtr<ChannelMediaResource> self = closure->mResource;
476
0
  uint32_t loadID = closure->mLoadID;
477
0
  UniquePtr<uint8_t[]> data = MakeUnique<uint8_t[]>(aCount);
478
0
  memcpy(data.get(), aFromSegment, aCount);
479
0
  cacheStream->OwnerThread()->Dispatch(NS_NewRunnableFunction(
480
0
    "MediaCacheStream::NotifyDataReceived",
481
0
    [ self, loadID, data = std::move(data), aCount ]() {
482
0
      self->mCacheStream.NotifyDataReceived(loadID, aCount, data.get());
483
0
    }));
484
0
485
0
  return NS_OK;
486
0
}
487
488
nsresult
489
ChannelMediaResource::OnDataAvailable(uint32_t aLoadID,
490
                                      nsIInputStream* aStream,
491
                                      uint32_t aCount)
492
0
{
493
0
  // This might happen off the main thread.
494
0
  Closure closure{ aLoadID, this };
495
0
  uint32_t count = aCount;
496
0
  while (count > 0) {
497
0
    uint32_t read;
498
0
    nsresult rv =
499
0
      aStream->ReadSegments(CopySegmentToCache, &closure, count, &read);
500
0
    if (NS_FAILED(rv))
501
0
      return rv;
502
0
    NS_ASSERTION(read > 0, "Read 0 bytes while data was available?");
503
0
    count -= read;
504
0
  }
505
0
506
0
  return NS_OK;
507
0
}
508
509
int64_t
510
ChannelMediaResource::CalculateStreamLength() const
511
0
{
512
0
  if (!mChannel) {
513
0
    return -1;
514
0
  }
515
0
516
0
  nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
517
0
  if (!hc) {
518
0
    return -1;
519
0
  }
520
0
521
0
  bool succeeded = false;
522
0
  Unused << hc->GetRequestSucceeded(&succeeded);
523
0
  if (!succeeded) {
524
0
    return -1;
525
0
  }
526
0
527
0
  // We can't determine the length of uncompressed payload.
528
0
  const bool isCompressed = IsPayloadCompressed(hc);
529
0
  if (isCompressed) {
530
0
    return -1;
531
0
  }
532
0
533
0
  int64_t contentLength = -1;
534
0
  if (NS_FAILED(hc->GetContentLength(&contentLength))) {
535
0
    return -1;
536
0
  }
537
0
538
0
  uint32_t responseStatus = 0;
539
0
  Unused << hc->GetResponseStatus(&responseStatus);
540
0
  if (responseStatus != HTTP_PARTIAL_RESPONSE_CODE) {
541
0
    return contentLength;
542
0
  }
543
0
544
0
  // We have an HTTP Byte Range response. The Content-Length is the length
545
0
  // of the response, not the resource. We need to parse the Content-Range
546
0
  // header and extract the range total in order to get the stream length.
547
0
  int64_t rangeStart = 0;
548
0
  int64_t rangeEnd = 0;
549
0
  int64_t rangeTotal = 0;
550
0
  bool gotRangeHeader = NS_SUCCEEDED(
551
0
    ParseContentRangeHeader(hc, rangeStart, rangeEnd, rangeTotal));
552
0
  if (gotRangeHeader && rangeTotal != -1) {
553
0
    contentLength = std::max(contentLength, rangeTotal);
554
0
  }
555
0
  return contentLength;
556
0
}
557
558
nsresult
559
ChannelMediaResource::Open(nsIStreamListener** aStreamListener)
560
0
{
561
0
  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
562
0
  MOZ_ASSERT(aStreamListener);
563
0
  MOZ_ASSERT(mChannel);
564
0
565
0
  int64_t streamLength = CalculateStreamLength();
566
0
  nsresult rv = mCacheStream.Init(streamLength);
567
0
  if (NS_FAILED(rv)) {
568
0
    return rv;
569
0
  }
570
0
571
0
  mSharedInfo = new SharedInfo;
572
0
  mSharedInfo->mResources.AppendElement(this);
573
0
574
0
  mIsLiveStream = streamLength < 0;
575
0
  mListener = new Listener(this, 0, ++mLoadID);
576
0
  *aStreamListener = mListener;
577
0
  NS_ADDREF(*aStreamListener);
578
0
  return NS_OK;
579
0
}
580
581
nsresult
582
ChannelMediaResource::OpenChannel(int64_t aOffset)
583
0
{
584
0
  MOZ_ASSERT(NS_IsMainThread());
585
0
  MOZ_DIAGNOSTIC_ASSERT(!mClosed);
586
0
  MOZ_ASSERT(mChannel);
587
0
  MOZ_ASSERT(!mListener, "Listener should have been removed by now");
588
0
589
0
  mListener = new Listener(this, aOffset, ++mLoadID);
590
0
  nsresult rv = mChannel->SetNotificationCallbacks(mListener.get());
591
0
  NS_ENSURE_SUCCESS(rv, rv);
592
0
593
0
  rv = SetupChannelHeaders(aOffset);
594
0
  NS_ENSURE_SUCCESS(rv, rv);
595
0
596
0
  rv = mChannel->AsyncOpen2(mListener);
597
0
  NS_ENSURE_SUCCESS(rv, rv);
598
0
599
0
  // Tell the media element that we are fetching data from a channel.
600
0
  MediaDecoderOwner* owner = mCallback->GetMediaOwner();
601
0
  MOZ_DIAGNOSTIC_ASSERT(owner);
602
0
  dom::HTMLMediaElement* element = owner->GetMediaElement();
603
0
  MOZ_DIAGNOSTIC_ASSERT(element);
604
0
  element->DownloadResumed();
605
0
606
0
  return NS_OK;
607
0
}
608
609
nsresult
610
ChannelMediaResource::SetupChannelHeaders(int64_t aOffset)
611
0
{
612
0
  MOZ_ASSERT(NS_IsMainThread());
613
0
  MOZ_DIAGNOSTIC_ASSERT(!mClosed);
614
0
615
0
  // Always use a byte range request even if we're reading from the start
616
0
  // of the resource.
617
0
  // This enables us to detect if the stream supports byte range
618
0
  // requests, and therefore seeking, early.
619
0
  nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
620
0
  if (hc) {
621
0
    // Use |mOffset| if seeking in a complete file download.
622
0
    nsAutoCString rangeString("bytes=");
623
0
    rangeString.AppendInt(aOffset);
624
0
    rangeString.Append('-');
625
0
    nsresult rv = hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false);
626
0
    NS_ENSURE_SUCCESS(rv, rv);
627
0
628
0
    // Send Accept header for video and audio types only (Bug 489071)
629
0
    MediaDecoderOwner* owner = mCallback->GetMediaOwner();
630
0
    MOZ_DIAGNOSTIC_ASSERT(owner);
631
0
    dom::HTMLMediaElement* element = owner->GetMediaElement();
632
0
    MOZ_DIAGNOSTIC_ASSERT(element);
633
0
    element->SetRequestHeaders(hc);
634
0
  } else {
635
0
    NS_ASSERTION(aOffset == 0, "Don't know how to seek on this channel type");
636
0
    return NS_ERROR_FAILURE;
637
0
  }
638
0
  return NS_OK;
639
0
}
640
641
nsresult ChannelMediaResource::Close()
642
0
{
643
0
  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
644
0
645
0
  if (!mClosed) {
646
0
    CloseChannel();
647
0
    mCacheStream.Close();
648
0
    mClosed = true;
649
0
  }
650
0
  return NS_OK;
651
0
}
652
653
already_AddRefed<nsIPrincipal>
654
ChannelMediaResource::GetCurrentPrincipal()
655
0
{
656
0
  MOZ_ASSERT(NS_IsMainThread());
657
0
  return do_AddRef(mSharedInfo->mPrincipal);
658
0
}
659
660
bool ChannelMediaResource::CanClone()
661
0
{
662
0
  return !mClosed && mCacheStream.IsAvailableForSharing();
663
0
}
664
665
already_AddRefed<BaseMediaResource>
666
ChannelMediaResource::CloneData(MediaResourceCallback* aCallback)
667
0
{
668
0
  MOZ_ASSERT(NS_IsMainThread());
669
0
  MOZ_ASSERT(CanClone(), "Stream can't be cloned");
670
0
671
0
  RefPtr<ChannelMediaResource> resource =
672
0
    new ChannelMediaResource(aCallback, nullptr, mURI);
673
0
674
0
  resource->mIsLiveStream = mIsLiveStream;
675
0
  resource->mIsTransportSeekable = mIsTransportSeekable;
676
0
  resource->mSharedInfo = mSharedInfo;
677
0
  mSharedInfo->mResources.AppendElement(resource.get());
678
0
679
0
  // Initially the clone is treated as suspended by the cache, because
680
0
  // we don't have a channel. If the cache needs to read data from the clone
681
0
  // it will call CacheClientResume (or CacheClientSeek with aResume true)
682
0
  // which will recreate the channel. This way, if all of the media data
683
0
  // is already in the cache we don't create an unnecessary HTTP channel
684
0
  // and perform a useless HTTP transaction.
685
0
  resource->mCacheStream.InitAsClone(&mCacheStream);
686
0
  return resource.forget();
687
0
}
688
689
void ChannelMediaResource::CloseChannel()
690
0
{
691
0
  NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
692
0
693
0
  if (mChannel) {
694
0
    mSuspendAgent.Revoke();
695
0
    // The status we use here won't be passed to the decoder, since
696
0
    // we've already revoked the listener. It can however be passed
697
0
    // to nsDocumentViewer::LoadComplete if our channel is the one
698
0
    // that kicked off creation of a video document. We don't want that
699
0
    // document load to think there was an error.
700
0
    // NS_ERROR_PARSED_DATA_CACHED is the best thing we have for that
701
0
    // at the moment.
702
0
    mChannel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
703
0
    mChannel = nullptr;
704
0
  }
705
0
706
0
  if (mListener) {
707
0
    mListener->Revoke();
708
0
    mListener = nullptr;
709
0
  }
710
0
}
711
712
nsresult ChannelMediaResource::ReadFromCache(char* aBuffer,
713
                                             int64_t aOffset,
714
                                             uint32_t aCount)
715
0
{
716
0
  return mCacheStream.ReadFromCache(aBuffer, aOffset, aCount);
717
0
}
718
719
nsresult ChannelMediaResource::ReadAt(int64_t aOffset,
720
                                      char* aBuffer,
721
                                      uint32_t aCount,
722
                                      uint32_t* aBytes)
723
0
{
724
0
  NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
725
0
  return mCacheStream.ReadAt(aOffset, aBuffer, aCount, aBytes);
726
0
}
727
728
void
729
ChannelMediaResource::ThrottleReadahead(bool bThrottle)
730
0
{
731
0
  mCacheStream.ThrottleReadahead(bThrottle);
732
0
}
733
734
nsresult ChannelMediaResource::GetCachedRanges(MediaByteRangeSet& aRanges)
735
0
{
736
0
  return mCacheStream.GetCachedRanges(aRanges);
737
0
}
738
739
void
740
ChannelMediaResource::Suspend(bool aCloseImmediately)
741
0
{
742
0
  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
743
0
744
0
  if (mClosed) {
745
0
    // Nothing to do when we are closed.
746
0
    return;
747
0
  }
748
0
749
0
  MediaDecoderOwner* owner = mCallback->GetMediaOwner();
750
0
  MOZ_DIAGNOSTIC_ASSERT(owner);
751
0
  dom::HTMLMediaElement* element = owner->GetMediaElement();
752
0
  MOZ_DIAGNOSTIC_ASSERT(element);
753
0
754
0
  if (mChannel && aCloseImmediately && mIsTransportSeekable) {
755
0
    CloseChannel();
756
0
  }
757
0
758
0
  if (mSuspendAgent.Suspend()) {
759
0
    element->DownloadSuspended();
760
0
  }
761
0
}
762
763
void
764
ChannelMediaResource::Resume()
765
0
{
766
0
  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
767
0
768
0
  if (mClosed) {
769
0
    // Nothing to do when we are closed.
770
0
    return;
771
0
  }
772
0
773
0
  MediaDecoderOwner* owner = mCallback->GetMediaOwner();
774
0
  MOZ_DIAGNOSTIC_ASSERT(owner);
775
0
  dom::HTMLMediaElement* element = owner->GetMediaElement();
776
0
  MOZ_DIAGNOSTIC_ASSERT(element);
777
0
778
0
  if (mSuspendAgent.Resume()) {
779
0
    if (mChannel) {
780
0
      // Just wake up our existing channel
781
0
      element->DownloadResumed();
782
0
    } else {
783
0
      mCacheStream.NotifyResume();
784
0
    }
785
0
  }
786
0
}
787
788
nsresult
789
ChannelMediaResource::RecreateChannel()
790
0
{
791
0
  MOZ_DIAGNOSTIC_ASSERT(!mClosed);
792
0
793
0
  nsLoadFlags loadFlags =
794
0
    nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
795
0
    nsIChannel::LOAD_CLASSIFY_URI |
796
0
    (mLoadInBackground ? nsIRequest::LOAD_BACKGROUND : 0);
797
0
798
0
  MediaDecoderOwner* owner = mCallback->GetMediaOwner();
799
0
  MOZ_DIAGNOSTIC_ASSERT(owner);
800
0
  dom::HTMLMediaElement* element = owner->GetMediaElement();
801
0
  MOZ_DIAGNOSTIC_ASSERT(element);
802
0
803
0
  nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
804
0
  NS_ENSURE_TRUE(loadGroup, NS_ERROR_NULL_POINTER);
805
0
806
0
  nsSecurityFlags securityFlags = element->ShouldCheckAllowOrigin()
807
0
                                  ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
808
0
                                  : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
809
0
810
0
  MOZ_ASSERT(element->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
811
0
  nsContentPolicyType contentPolicyType = element->IsHTMLElement(nsGkAtoms::audio) ?
812
0
    nsIContentPolicy::TYPE_INTERNAL_AUDIO : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
813
0
814
0
  // If element has 'triggeringprincipal' attribute, we will use the value as
815
0
  // triggeringPrincipal for the channel, otherwise it will default to use
816
0
  // aElement->NodePrincipal().
817
0
  // This function returns true when element has 'triggeringprincipal', so if
818
0
  // setAttrs is true we will override the origin attributes on the channel
819
0
  // later.
820
0
  nsCOMPtr<nsIPrincipal> triggeringPrincipal;
821
0
  bool setAttrs =
822
0
    nsContentUtils::QueryTriggeringPrincipal(element,
823
0
                                             getter_AddRefs(triggeringPrincipal));
824
0
825
0
  nsresult rv = NS_NewChannelWithTriggeringPrincipal(getter_AddRefs(mChannel),
826
0
                                                     mURI,
827
0
                                                     element,
828
0
                                                     triggeringPrincipal,
829
0
                                                     securityFlags,
830
0
                                                     contentPolicyType,
831
0
                                                     nullptr, // aPerformanceStorage
832
0
                                                     loadGroup,
833
0
                                                     nullptr,  // aCallbacks
834
0
                                                     loadFlags);
835
0
  NS_ENSURE_SUCCESS(rv, rv);
836
0
837
0
  if (setAttrs) {
838
0
    nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
839
0
    if (loadInfo) {
840
0
      // The function simply returns NS_OK, so we ignore the return value.
841
0
      Unused << loadInfo->SetOriginAttributes(triggeringPrincipal->OriginAttributesRef());
842
0
   }
843
0
  }
844
0
845
0
  nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel));
846
0
  if (cos) {
847
0
    // Unconditionally disable throttling since we want the media to fluently
848
0
    // play even when we switch the tab to background.
849
0
    cos->AddClassFlags(nsIClassOfService::DontThrottle);
850
0
  }
851
0
852
0
  return rv;
853
0
}
854
855
void
856
ChannelMediaResource::CacheClientNotifyDataReceived()
857
0
{
858
0
  mCallback->AbstractMainThread()->Dispatch(
859
0
    NewRunnableMethod("MediaResourceCallback::NotifyDataArrived",
860
0
                      mCallback.get(),
861
0
                      &MediaResourceCallback::NotifyDataArrived));
862
0
}
863
864
void
865
ChannelMediaResource::CacheClientNotifyDataEnded(nsresult aStatus)
866
0
{
867
0
  mCallback->AbstractMainThread()->Dispatch(NS_NewRunnableFunction(
868
0
    "ChannelMediaResource::CacheClientNotifyDataEnded",
869
0
    [ self = RefPtr<ChannelMediaResource>(this), aStatus ]() {
870
0
      if (NS_SUCCEEDED(aStatus)) {
871
0
        self->mIsLiveStream = false;
872
0
      }
873
0
      self->mCallback->NotifyDataEnded(aStatus);
874
0
    }));
875
0
}
876
877
void
878
ChannelMediaResource::CacheClientNotifyPrincipalChanged()
879
0
{
880
0
  NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
881
0
882
0
  mCallback->NotifyPrincipalChanged();
883
0
}
884
885
void
886
ChannelMediaResource::UpdatePrincipal()
887
0
{
888
0
  MOZ_ASSERT(NS_IsMainThread());
889
0
  MOZ_ASSERT(mChannel);
890
0
  nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
891
0
  if (!secMan) {
892
0
    return;
893
0
  }
894
0
  nsCOMPtr<nsIPrincipal> principal;
895
0
  secMan->GetChannelResultPrincipal(mChannel, getter_AddRefs(principal));
896
0
  if (nsContentUtils::CombineResourcePrincipals(&mSharedInfo->mPrincipal,
897
0
                                                principal)) {
898
0
    for (auto* r : mSharedInfo->mResources) {
899
0
      r->CacheClientNotifyPrincipalChanged();
900
0
    }
901
0
  }
902
0
}
903
904
void
905
ChannelMediaResource::CacheClientNotifySuspendedStatusChanged(bool aSuspended)
906
0
{
907
0
  mCallback->AbstractMainThread()->Dispatch(NewRunnableMethod<bool>(
908
0
    "MediaResourceCallback::NotifySuspendedStatusChanged",
909
0
    mCallback.get(),
910
0
    &MediaResourceCallback::NotifySuspendedStatusChanged,
911
0
    aSuspended));
912
0
}
913
914
nsresult
915
ChannelMediaResource::Seek(int64_t aOffset, bool aResume)
916
0
{
917
0
  MOZ_ASSERT(NS_IsMainThread());
918
0
919
0
  if (mClosed) {
920
0
    // Nothing to do when we are closed.
921
0
    return NS_OK;
922
0
  }
923
0
924
0
  LOG("Seek requested for aOffset [%" PRId64 "]", aOffset);
925
0
926
0
  CloseChannel();
927
0
928
0
  if (aResume) {
929
0
    mSuspendAgent.Resume();
930
0
  }
931
0
932
0
  // Don't create a new channel if we are still suspended. The channel will
933
0
  // be recreated when we are resumed.
934
0
  if (mSuspendAgent.IsSuspended()) {
935
0
    return NS_OK;
936
0
  }
937
0
938
0
  nsresult rv = RecreateChannel();
939
0
  NS_ENSURE_SUCCESS(rv, rv);
940
0
941
0
  return OpenChannel(aOffset);
942
0
}
943
944
void
945
ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
946
0
{
947
0
  RefPtr<ChannelMediaResource> self = this;
948
0
  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
949
0
    "ChannelMediaResource::Seek", [self, aOffset, aResume]() {
950
0
      nsresult rv = self->Seek(aOffset, aResume);
951
0
      if (NS_FAILED(rv)) {
952
0
        // Close the streams that failed due to error. This will cause all
953
0
        // client Read and Seek operations on those streams to fail. Blocked
954
0
        // Reads will also be woken up.
955
0
        self->Close();
956
0
      }
957
0
    });
958
0
  mCallback->AbstractMainThread()->Dispatch(r.forget());
959
0
}
960
961
void
962
ChannelMediaResource::CacheClientSuspend()
963
0
{
964
0
  mCallback->AbstractMainThread()->Dispatch(
965
0
    NewRunnableMethod<bool>("ChannelMediaResource::Suspend",
966
0
                            this,
967
0
                            &ChannelMediaResource::Suspend,
968
0
                            false));
969
0
}
970
971
void
972
ChannelMediaResource::CacheClientResume()
973
0
{
974
0
  mCallback->AbstractMainThread()->Dispatch(NewRunnableMethod(
975
0
    "ChannelMediaResource::Resume", this, &ChannelMediaResource::Resume));
976
0
}
977
978
int64_t
979
ChannelMediaResource::GetNextCachedData(int64_t aOffset)
980
0
{
981
0
  return mCacheStream.GetNextCachedData(aOffset);
982
0
}
983
984
int64_t
985
ChannelMediaResource::GetCachedDataEnd(int64_t aOffset)
986
0
{
987
0
  return mCacheStream.GetCachedDataEnd(aOffset);
988
0
}
989
990
bool
991
ChannelMediaResource::IsDataCachedToEndOfResource(int64_t aOffset)
992
0
{
993
0
  return mCacheStream.IsDataCachedToEndOfStream(aOffset);
994
0
}
995
996
bool
997
ChannelMediaResource::IsSuspended()
998
0
{
999
0
  return mSuspendAgent.IsSuspended();
1000
0
}
1001
1002
void
1003
ChannelMediaResource::SetReadMode(MediaCacheStream::ReadMode aMode)
1004
0
{
1005
0
  mCacheStream.SetReadMode(aMode);
1006
0
}
1007
1008
void
1009
ChannelMediaResource::SetPlaybackRate(uint32_t aBytesPerSecond)
1010
0
{
1011
0
  mCacheStream.SetPlaybackRate(aBytesPerSecond);
1012
0
}
1013
1014
void
1015
ChannelMediaResource::Pin()
1016
0
{
1017
0
  mCacheStream.Pin();
1018
0
}
1019
1020
void
1021
ChannelMediaResource::Unpin()
1022
0
{
1023
0
  mCacheStream.Unpin();
1024
0
}
1025
1026
double
1027
ChannelMediaResource::GetDownloadRate(bool* aIsReliable)
1028
0
{
1029
0
  return mCacheStream.GetDownloadRate(aIsReliable);
1030
0
}
1031
1032
int64_t
1033
ChannelMediaResource::GetLength()
1034
0
{
1035
0
  return mCacheStream.GetLength();
1036
0
}
1037
1038
nsCString
1039
ChannelMediaResource::GetDebugInfo()
1040
0
{
1041
0
  return NS_LITERAL_CSTRING("ChannelMediaResource: ") +
1042
0
         mCacheStream.GetDebugInfo();
1043
0
}
1044
1045
// ChannelSuspendAgent
1046
1047
bool
1048
ChannelSuspendAgent::Suspend()
1049
0
{
1050
0
  MOZ_ASSERT(NS_IsMainThread());
1051
0
  SuspendInternal();
1052
0
  if (++mSuspendCount == 1) {
1053
0
    mCacheStream.NotifyClientSuspended(true);
1054
0
    return true;
1055
0
  }
1056
0
  return false;
1057
0
}
1058
1059
void
1060
ChannelSuspendAgent::SuspendInternal()
1061
0
{
1062
0
  MOZ_ASSERT(NS_IsMainThread());
1063
0
  if (mChannel) {
1064
0
    bool isPending = false;
1065
0
    nsresult rv = mChannel->IsPending(&isPending);
1066
0
    if (NS_SUCCEEDED(rv) && isPending && !mIsChannelSuspended) {
1067
0
      mChannel->Suspend();
1068
0
      mIsChannelSuspended = true;
1069
0
    }
1070
0
  }
1071
0
}
1072
1073
bool
1074
ChannelSuspendAgent::Resume()
1075
0
{
1076
0
  MOZ_ASSERT(NS_IsMainThread());
1077
0
  MOZ_ASSERT(IsSuspended(), "Resume without suspend!");
1078
0
1079
0
  if (--mSuspendCount == 0) {
1080
0
    if (mChannel && mIsChannelSuspended) {
1081
0
      mChannel->Resume();
1082
0
      mIsChannelSuspended = false;
1083
0
    }
1084
0
    mCacheStream.NotifyClientSuspended(false);
1085
0
    return true;
1086
0
  }
1087
0
  return false;
1088
0
}
1089
1090
void
1091
ChannelSuspendAgent::Delegate(nsIChannel* aChannel)
1092
0
{
1093
0
  MOZ_ASSERT(NS_IsMainThread());
1094
0
  MOZ_ASSERT(aChannel);
1095
0
  MOZ_ASSERT(!mChannel, "The previous channel not closed.");
1096
0
  MOZ_ASSERT(!mIsChannelSuspended);
1097
0
1098
0
  mChannel = aChannel;
1099
0
  // Ensure the suspend status of the channel matches our suspend count.
1100
0
  if (IsSuspended()) {
1101
0
    SuspendInternal();
1102
0
  }
1103
0
}
1104
1105
void
1106
ChannelSuspendAgent::Revoke()
1107
0
{
1108
0
  MOZ_ASSERT(NS_IsMainThread());
1109
0
1110
0
  if (!mChannel) {
1111
0
    // Channel already revoked. Nothing to do.
1112
0
    return;
1113
0
  }
1114
0
1115
0
  // Before closing the channel, it needs to be resumed to make sure its internal
1116
0
  // state is correct. Besides, We need to suspend the channel after recreating.
1117
0
  if (mIsChannelSuspended) {
1118
0
    mChannel->Resume();
1119
0
    mIsChannelSuspended = false;
1120
0
  }
1121
0
  mChannel = nullptr;
1122
0
}
1123
1124
bool
1125
ChannelSuspendAgent::IsSuspended()
1126
0
{
1127
0
  MOZ_ASSERT(NS_IsMainThread());
1128
0
  return (mSuspendCount > 0);
1129
0
}
1130
1131
} // mozilla namespace