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