tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ChannelMediaResource.cpp (38589B)


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