imgRequest.cpp (39819B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "imgRequest.h" 8 #include "ImageLogging.h" 9 10 #include "imgLoader.h" 11 #include "imgRequestProxy.h" 12 #include "DecodePool.h" 13 #include "ProgressTracker.h" 14 #include "ImageFactory.h" 15 #include "Image.h" 16 #include "MultipartImage.h" 17 #include "RasterImage.h" 18 19 #include "nsIChannel.h" 20 #include "nsICacheInfoChannel.h" 21 #include "nsIClassOfService.h" 22 #include "mozilla/dom/Document.h" 23 #include "nsIThreadRetargetableRequest.h" 24 #include "nsIInputStream.h" 25 #include "nsIMultiPartChannel.h" 26 #include "nsIHttpChannel.h" 27 #include "nsMimeTypes.h" 28 29 #include "nsIInterfaceRequestorUtils.h" 30 #include "nsISupportsPrimitives.h" 31 #include "nsIScriptSecurityManager.h" 32 #include "nsComponentManagerUtils.h" 33 #include "nsContentUtils.h" 34 #include "nsEscape.h" 35 36 #include "prtime.h" // for PR_Now 37 #include "nsNetUtil.h" 38 #include "nsIProtocolHandler.h" 39 #include "imgIRequest.h" 40 #include "nsProperties.h" 41 #include "nsIURL.h" 42 43 #include "mozilla/IntegerPrintfMacros.h" 44 #include "mozilla/SizeOfState.h" 45 46 using namespace mozilla; 47 using namespace mozilla::image; 48 49 #define LOG_TEST(level) (MOZ_LOG_TEST(gImgLog, (level))) 50 51 NS_IMPL_ISUPPORTS(imgRequest, nsIStreamListener, nsIRequestObserver, 52 nsIThreadRetargetableStreamListener, nsIChannelEventSink, 53 nsIInterfaceRequestor, nsIAsyncVerifyRedirectCallback) 54 55 imgRequest::imgRequest(imgLoader* aLoader, const ImageCacheKey& aCacheKey) 56 : mLoader(aLoader), 57 mCacheKey(aCacheKey), 58 mLoadId(nullptr), 59 mFirstProxy(nullptr), 60 mValidator(nullptr), 61 mCORSMode(CORS_NONE), 62 mImageErrorCode(NS_OK), 63 mImageAvailable(false), 64 mIsDeniedCrossSiteCORSRequest(false), 65 mIsCrossSiteNoCORSRequest(false), 66 mShouldReportRenderTimeForLCP(false), 67 mMutex("imgRequest"), 68 mProgressTracker(new ProgressTracker()), 69 mIsMultiPartChannel(false), 70 mIsInCache(false), 71 mDecodeRequested(false), 72 mNewPartPending(false), 73 mHadInsecureRedirect(false), 74 mInnerWindowId(0) { 75 LOG_FUNC(gImgLog, "imgRequest::imgRequest()"); 76 } 77 78 imgRequest::~imgRequest() { 79 if (mLoader) { 80 mLoader->RemoveFromUncachedImages(this); 81 } 82 if (mURI) { 83 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::~imgRequest()", "keyuri", mURI); 84 } else 85 LOG_FUNC(gImgLog, "imgRequest::~imgRequest()"); 86 } 87 88 nsresult imgRequest::Init( 89 nsIURI* aURI, nsIURI* aFinalURI, bool aHadInsecureRedirect, 90 nsIRequest* aRequest, nsIChannel* aChannel, imgCacheEntry* aCacheEntry, 91 mozilla::dom::Document* aLoadingDocument, 92 nsIPrincipal* aTriggeringPrincipal, mozilla::CORSMode aCORSMode, 93 nsIReferrerInfo* aReferrerInfo) MOZ_NO_THREAD_SAFETY_ANALYSIS { 94 MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!"); 95 // Init() can only be called once, and that's before it can be used off 96 // mainthread 97 98 LOG_FUNC(gImgLog, "imgRequest::Init"); 99 100 MOZ_ASSERT(!mImage, "Multiple calls to init"); 101 MOZ_ASSERT(aURI, "No uri"); 102 MOZ_ASSERT(aFinalURI, "No final uri"); 103 MOZ_ASSERT(aRequest, "No request"); 104 MOZ_ASSERT(aChannel, "No channel"); 105 106 mProperties = new nsProperties(); 107 mURI = aURI; 108 mFinalURI = aFinalURI; 109 mRequest = aRequest; 110 mChannel = aChannel; 111 mTimedChannel = do_QueryInterface(mChannel); 112 mTriggeringPrincipal = aTriggeringPrincipal; 113 mCORSMode = aCORSMode; 114 mReferrerInfo = aReferrerInfo; 115 116 // If the original URI and the final URI are different, check whether the 117 // original URI is secure. We deliberately don't take the final URI into 118 // account, as it needs to be handled using more complicated rules than 119 // earlier elements of the redirect chain. 120 if (aURI != aFinalURI) { 121 bool schemeLocal = false; 122 if (NS_FAILED(NS_URIChainHasFlags( 123 aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &schemeLocal)) || 124 (!aURI->SchemeIs("https") && !aURI->SchemeIs("chrome") && 125 !schemeLocal)) { 126 mHadInsecureRedirect = true; 127 } 128 } 129 130 // imgCacheValidator may have handled redirects before we were created, so we 131 // allow the caller to let us know if any redirects were insecure. 132 mHadInsecureRedirect = mHadInsecureRedirect || aHadInsecureRedirect; 133 134 mChannel->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink)); 135 136 NS_ASSERTION(mPrevChannelSink != this, 137 "Initializing with a channel that already calls back to us!"); 138 139 mChannel->SetNotificationCallbacks(this); 140 141 mCacheEntry = aCacheEntry; 142 mCacheEntry->UpdateLoadTime(); 143 144 SetLoadId(aLoadingDocument); 145 146 // Grab the inner window ID of the loading document, if possible. 147 if (aLoadingDocument) { 148 mInnerWindowId = aLoadingDocument->InnerWindowID(); 149 } 150 151 return NS_OK; 152 } 153 154 bool imgRequest::CanReuseWithoutValidation(dom::Document* aDoc) const { 155 // If the request's loadId is the same as the aLoadingDocument, then it is ok 156 // to use this one because it has already been validated for this context. 157 // XXX: nullptr seems to be a 'special' key value that indicates that NO 158 // validation is required. 159 // XXX: we also check the window ID because the loadID() can return a reused 160 // pointer of a document. This can still happen for non-document image 161 // cache entries. 162 void* key = (void*)aDoc; 163 uint64_t innerWindowID = aDoc ? aDoc->InnerWindowID() : 0; 164 if (LoadId() == key && InnerWindowID() == innerWindowID) { 165 return true; 166 } 167 168 // As a special-case, if this is a print preview document, also validate on 169 // the original document. This allows to print uncacheable images. 170 if (dom::Document* original = aDoc ? aDoc->GetOriginalDocument() : nullptr) { 171 return CanReuseWithoutValidation(original); 172 } 173 174 return false; 175 } 176 177 void imgRequest::ClearLoader() { mLoader = nullptr; } 178 179 already_AddRefed<nsIPrincipal> imgRequest::GetTriggeringPrincipal() const { 180 nsCOMPtr<nsIPrincipal> principal = mTriggeringPrincipal; 181 return principal.forget(); 182 } 183 184 already_AddRefed<ProgressTracker> imgRequest::GetProgressTracker() const { 185 MutexAutoLock lock(mMutex); 186 187 if (mImage) { 188 MOZ_ASSERT(!mProgressTracker, 189 "Should have given mProgressTracker to mImage"); 190 return mImage->GetProgressTracker(); 191 } 192 MOZ_ASSERT(mProgressTracker, 193 "Should have mProgressTracker until we create mImage"); 194 RefPtr<ProgressTracker> progressTracker = mProgressTracker; 195 MOZ_ASSERT(progressTracker); 196 return progressTracker.forget(); 197 } 198 199 void imgRequest::SetCacheEntry(imgCacheEntry* entry) { mCacheEntry = entry; } 200 201 bool imgRequest::HasCacheEntry() const { return mCacheEntry != nullptr; } 202 203 void imgRequest::ResetCacheEntry() { 204 if (HasCacheEntry()) { 205 mCacheEntry->SetDataSize(0); 206 } 207 } 208 209 void imgRequest::AddProxy(imgRequestProxy* proxy) { 210 MOZ_ASSERT(proxy, "null imgRequestProxy passed in"); 211 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::AddProxy", "proxy", proxy); 212 213 if (!mFirstProxy) { 214 // Save a raw pointer to the first proxy we see, for use in the network 215 // priority logic. 216 mFirstProxy = proxy; 217 } 218 219 // If we're empty before adding, we have to tell the loader we now have 220 // proxies. 221 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 222 if (progressTracker->ObserverCount() == 0) { 223 MOZ_ASSERT(mURI, "Trying to SetHasProxies without key uri."); 224 if (mLoader) { 225 mLoader->SetHasProxies(this); 226 } 227 } 228 229 progressTracker->AddObserver(proxy); 230 } 231 232 nsresult imgRequest::RemoveProxy(imgRequestProxy* proxy, nsresult aStatus) { 233 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy", "proxy", proxy); 234 235 // This will remove our animation consumers, so after removing 236 // this proxy, we don't end up without proxies with observers, but still 237 // have animation consumers. 238 proxy->ClearAnimationConsumers(); 239 240 // Let the status tracker do its thing before we potentially call Cancel() 241 // below, because Cancel() may result in OnStopRequest being called back 242 // before Cancel() returns, leaving the image in a different state then the 243 // one it was in at this point. 244 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 245 if (!progressTracker->RemoveObserver(proxy)) { 246 return NS_OK; 247 } 248 249 if (progressTracker->ObserverCount() == 0) { 250 // If we have no observers, there's nothing holding us alive. If we haven't 251 // been cancelled and thus removed from the cache, tell the image loader so 252 // we can be evicted from the cache. 253 if (mCacheEntry) { 254 MOZ_ASSERT(mURI, "Removing last observer without key uri."); 255 256 if (mLoader) { 257 mLoader->SetHasNoProxies(this, mCacheEntry); 258 } 259 } else { 260 LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy no cache entry", 261 "uri", mURI); 262 } 263 264 /* If |aStatus| is a failure code, then cancel the load if it is still in 265 progress. Otherwise, let the load continue, keeping 'this' in the cache 266 with no observers. This way, if a proxy is destroyed without calling 267 cancel on it, it won't leak and won't leave a bad pointer in the observer 268 list. 269 */ 270 if (!(progressTracker->GetProgress() & FLAG_LAST_PART_COMPLETE) && 271 NS_FAILED(aStatus)) { 272 LOG_MSG(gImgLog, "imgRequest::RemoveProxy", 273 "load in progress. canceling"); 274 275 this->Cancel(NS_BINDING_ABORTED); 276 } 277 278 /* break the cycle from the cache entry. */ 279 mCacheEntry = nullptr; 280 } 281 282 return NS_OK; 283 } 284 285 uint64_t imgRequest::InnerWindowID() const { 286 MutexAutoLock lock(mMutex); 287 return mInnerWindowId; 288 } 289 290 void imgRequest::SetInnerWindowID(uint64_t aInnerWindowId) { 291 MutexAutoLock lock(mMutex); 292 mInnerWindowId = aInnerWindowId; 293 } 294 295 void imgRequest::CancelAndAbort(nsresult aStatus) { 296 LOG_SCOPE(gImgLog, "imgRequest::CancelAndAbort"); 297 298 Cancel(aStatus); 299 300 // It's possible for the channel to fail to open after we've set our 301 // notification callbacks. In that case, make sure to break the cycle between 302 // the channel and us, because it won't. 303 if (mChannel) { 304 mChannel->SetNotificationCallbacks(mPrevChannelSink); 305 mPrevChannelSink = nullptr; 306 } 307 } 308 309 class imgRequestMainThreadCancel : public Runnable { 310 public: 311 imgRequestMainThreadCancel(imgRequest* aImgRequest, nsresult aStatus) 312 : Runnable("imgRequestMainThreadCancel"), 313 mImgRequest(aImgRequest), 314 mStatus(aStatus) { 315 MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!"); 316 MOZ_ASSERT(aImgRequest); 317 } 318 319 NS_IMETHOD Run() override { 320 MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!"); 321 mImgRequest->ContinueCancel(mStatus); 322 return NS_OK; 323 } 324 325 private: 326 RefPtr<imgRequest> mImgRequest; 327 nsresult mStatus; 328 }; 329 330 void imgRequest::Cancel(nsresult aStatus) { 331 /* The Cancel() method here should only be called by this class. */ 332 LOG_SCOPE(gImgLog, "imgRequest::Cancel"); 333 334 if (NS_IsMainThread()) { 335 ContinueCancel(aStatus); 336 } else { 337 nsCOMPtr<nsIEventTarget> eventTarget = GetMainThreadSerialEventTarget(); 338 nsCOMPtr<nsIRunnable> ev = new imgRequestMainThreadCancel(this, aStatus); 339 eventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL); 340 } 341 } 342 343 void imgRequest::ContinueCancel(nsresult aStatus) { 344 MOZ_ASSERT(NS_IsMainThread()); 345 346 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 347 progressTracker->SyncNotifyProgress(FLAG_HAS_ERROR); 348 349 RemoveFromCache(); 350 351 if (mRequest && !(progressTracker->GetProgress() & FLAG_LAST_PART_COMPLETE)) { 352 mRequest->CancelWithReason(aStatus, "imgRequest::ContinueCancel"_ns); 353 } 354 } 355 356 class imgRequestMainThreadEvict : public Runnable { 357 public: 358 explicit imgRequestMainThreadEvict(imgRequest* aImgRequest) 359 : Runnable("imgRequestMainThreadEvict"), mImgRequest(aImgRequest) { 360 MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!"); 361 MOZ_ASSERT(aImgRequest); 362 } 363 364 NS_IMETHOD Run() override { 365 MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!"); 366 mImgRequest->ContinueEvict(); 367 return NS_OK; 368 } 369 370 private: 371 RefPtr<imgRequest> mImgRequest; 372 }; 373 374 // EvictFromCache() is written to allowed to get called from any thread 375 void imgRequest::EvictFromCache() { 376 /* The EvictFromCache() method here should only be called by this class. */ 377 LOG_SCOPE(gImgLog, "imgRequest::EvictFromCache"); 378 379 if (NS_IsMainThread()) { 380 ContinueEvict(); 381 } else { 382 NS_DispatchToMainThread(new imgRequestMainThreadEvict(this)); 383 } 384 } 385 386 // Helper-method used by EvictFromCache() 387 void imgRequest::ContinueEvict() { 388 MOZ_ASSERT(NS_IsMainThread()); 389 390 RemoveFromCache(); 391 } 392 393 void imgRequest::StartDecoding() { 394 MutexAutoLock lock(mMutex); 395 mDecodeRequested = true; 396 } 397 398 bool imgRequest::IsDecodeRequested() const { 399 MutexAutoLock lock(mMutex); 400 return mDecodeRequested; 401 } 402 403 nsresult imgRequest::GetURI(nsIURI** aURI) { 404 MOZ_ASSERT(aURI); 405 406 LOG_FUNC(gImgLog, "imgRequest::GetURI"); 407 408 if (mURI) { 409 *aURI = mURI; 410 NS_ADDREF(*aURI); 411 return NS_OK; 412 } 413 414 return NS_ERROR_FAILURE; 415 } 416 417 nsresult imgRequest::GetFinalURI(nsIURI** aURI) { 418 MOZ_ASSERT(aURI); 419 420 LOG_FUNC(gImgLog, "imgRequest::GetFinalURI"); 421 422 if (mFinalURI) { 423 *aURI = mFinalURI; 424 NS_ADDREF(*aURI); 425 return NS_OK; 426 } 427 428 return NS_ERROR_FAILURE; 429 } 430 431 bool imgRequest::IsChrome() const { return mURI->SchemeIs("chrome"); } 432 433 bool imgRequest::IsData() const { return mURI->SchemeIs("data"); } 434 435 nsresult imgRequest::GetImageErrorCode() { return mImageErrorCode; } 436 437 void imgRequest::RemoveFromCache() { 438 LOG_SCOPE(gImgLog, "imgRequest::RemoveFromCache"); 439 440 bool isInCache = false; 441 442 { 443 MutexAutoLock lock(mMutex); 444 isInCache = mIsInCache; 445 } 446 447 if (isInCache && mLoader) { 448 // mCacheEntry is nulled out when we have no more observers. 449 if (mCacheEntry) { 450 mLoader->RemoveFromCache(mCacheEntry); 451 } else { 452 mLoader->RemoveFromCache(mCacheKey); 453 } 454 } 455 456 mCacheEntry = nullptr; 457 } 458 459 bool imgRequest::HasConsumers() const { 460 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 461 return progressTracker && progressTracker->ObserverCount() > 0; 462 } 463 464 already_AddRefed<image::Image> imgRequest::GetImage() const { 465 MutexAutoLock lock(mMutex); 466 RefPtr<image::Image> image = mImage; 467 return image.forget(); 468 } 469 470 void imgRequest::GetFileName(nsACString& aFileName) { 471 nsAutoString fileName; 472 473 nsCOMPtr<nsISupportsCString> supportscstr; 474 if (NS_SUCCEEDED(mProperties->Get("content-disposition", 475 NS_GET_IID(nsISupportsCString), 476 getter_AddRefs(supportscstr))) && 477 supportscstr) { 478 nsAutoCString cdHeader; 479 supportscstr->GetData(cdHeader); 480 NS_GetFilenameFromDisposition(fileName, cdHeader); 481 } 482 483 if (fileName.IsEmpty()) { 484 nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(mURI)); 485 if (imgUrl) { 486 imgUrl->GetFileName(aFileName); 487 NS_UnescapeURL(aFileName); 488 } 489 } else { 490 aFileName = NS_ConvertUTF16toUTF8(fileName); 491 } 492 } 493 494 int32_t imgRequest::Priority() const { 495 int32_t priority = nsISupportsPriority::PRIORITY_NORMAL; 496 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest); 497 if (p) { 498 p->GetPriority(&priority); 499 } 500 return priority; 501 } 502 503 void imgRequest::AdjustPriority(imgRequestProxy* proxy, int32_t delta) { 504 // only the first proxy is allowed to modify the priority of this image load. 505 // 506 // XXX(darin): this is probably not the most optimal algorithm as we may want 507 // to increase the priority of requests that have a lot of proxies. the key 508 // concern though is that image loads remain lower priority than other pieces 509 // of content such as link clicks, CSS, and JS. 510 // 511 if (!mFirstProxy || proxy != mFirstProxy) { 512 return; 513 } 514 515 AdjustPriorityInternal(delta); 516 } 517 518 void imgRequest::AdjustPriorityInternal(int32_t aDelta) { 519 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel); 520 if (p) { 521 p->AdjustPriority(aDelta); 522 } 523 } 524 525 void imgRequest::BoostPriority(uint32_t aCategory) { 526 if (!StaticPrefs::image_layout_network_priority()) { 527 return; 528 } 529 530 uint32_t newRequestedCategory = 531 (mBoostCategoriesRequested & aCategory) ^ aCategory; 532 if (!newRequestedCategory) { 533 // priority boost for each category can only apply once. 534 return; 535 } 536 537 MOZ_LOG(gImgLog, LogLevel::Debug, 538 ("[this=%p] imgRequest::BoostPriority for category %x", this, 539 newRequestedCategory)); 540 541 int32_t delta = 0; 542 543 if (newRequestedCategory & imgIRequest::CATEGORY_FRAME_INIT) { 544 --delta; 545 } 546 547 if (newRequestedCategory & imgIRequest::CATEGORY_FRAME_STYLE) { 548 --delta; 549 } 550 551 if (newRequestedCategory & imgIRequest::CATEGORY_SIZE_QUERY) { 552 --delta; 553 } 554 555 if (newRequestedCategory & imgIRequest::CATEGORY_DISPLAY) { 556 delta += nsISupportsPriority::PRIORITY_HIGH; 557 } 558 559 AdjustPriorityInternal(delta); 560 mBoostCategoriesRequested |= newRequestedCategory; 561 } 562 563 void imgRequest::SetIsInCache(bool aInCache) { 564 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::SetIsCacheable", "aInCache", 565 aInCache); 566 MutexAutoLock lock(mMutex); 567 mIsInCache = aInCache; 568 } 569 570 void imgRequest::UpdateCacheEntrySize() { 571 if (!mCacheEntry) { 572 return; 573 } 574 575 RefPtr<Image> image = GetImage(); 576 SizeOfState state(moz_malloc_size_of); 577 size_t size = image->SizeOfSourceWithComputedFallback(state); 578 mCacheEntry->SetDataSize(size); 579 } 580 581 void imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, 582 nsIRequest* aRequest, 583 bool aForceTouch /* = false */) { 584 if (!aCacheEntry) { 585 return; 586 } 587 588 RefPtr<imgRequest> req = aCacheEntry->GetRequest(); 589 MOZ_ASSERT(req); 590 RefPtr<nsIURI> uri; 591 req->GetURI(getter_AddRefs(uri)); 592 // TODO(emilio): Seems we should be able to assert `uri` is not null, but we 593 // get here in such cases sometimes (like for some redirects, see 594 // docshell/test/chrome/test_bug89419.xhtml). 595 // 596 // We have the original URI in the cache key though, probably we should be 597 // using that instead of relying on Init() getting called. 598 auto info = nsContentUtils::GetSubresourceCacheValidationInfo(aRequest, uri); 599 600 // Expiration time defaults to 0. We set the expiration time on our entry if 601 // it hasn't been set yet. 602 if (!info.mExpirationTime) { 603 // If the channel doesn't support caching, then ensure this expires the 604 // next time it is used. 605 info.mExpirationTime.emplace(CacheExpirationTime::AlreadyExpired()); 606 } 607 aCacheEntry->AccumulateExpiryTime(*info.mExpirationTime, aForceTouch); 608 // Cache entries default to not needing to validate. We ensure that 609 // multiple calls to this function don't override an earlier decision to 610 // validate by making validation a one-way decision. 611 if (info.mMustRevalidate) { 612 aCacheEntry->SetMustValidate(info.mMustRevalidate); 613 } 614 } 615 616 bool imgRequest::GetMultipart() const { 617 MutexAutoLock lock(mMutex); 618 return mIsMultiPartChannel; 619 } 620 621 bool imgRequest::HadInsecureRedirect() const { 622 MutexAutoLock lock(mMutex); 623 return mHadInsecureRedirect; 624 } 625 626 /** nsIRequestObserver methods **/ 627 628 NS_IMETHODIMP 629 imgRequest::OnStartRequest(nsIRequest* aRequest) { 630 LOG_SCOPE(gImgLog, "imgRequest::OnStartRequest"); 631 632 RefPtr<Image> image; 633 634 if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest)) { 635 nsresult rv; 636 nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo(); 637 mIsDeniedCrossSiteCORSRequest = 638 loadInfo->GetTainting() == LoadTainting::CORS && 639 (NS_FAILED(httpChannel->GetStatus(&rv)) || NS_FAILED(rv)); 640 mIsCrossSiteNoCORSRequest = loadInfo->GetTainting() == LoadTainting::Opaque; 641 } 642 643 UpdateShouldReportRenderTimeForLCP(); 644 // Figure out if we're multipart. 645 nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest); 646 { 647 MutexAutoLock lock(mMutex); 648 649 MOZ_ASSERT(multiPartChannel || !mIsMultiPartChannel, 650 "Stopped being multipart?"); 651 652 mNewPartPending = true; 653 image = mImage; 654 mIsMultiPartChannel = bool(multiPartChannel); 655 } 656 657 // If we're not multipart, we shouldn't have an image yet. 658 if (image && !multiPartChannel) { 659 MOZ_ASSERT_UNREACHABLE("Already have an image for a non-multipart request"); 660 Cancel(NS_IMAGELIB_ERROR_FAILURE); 661 return NS_ERROR_FAILURE; 662 } 663 664 /* 665 * If mRequest is null here, then we need to set it so that we'll be able to 666 * cancel it if our Cancel() method is called. Note that this can only 667 * happen for multipart channels. We could simply not null out mRequest for 668 * non-last parts, if GetIsLastPart() were reliable, but it's not. See 669 * https://bugzilla.mozilla.org/show_bug.cgi?id=339610 670 */ 671 if (!mRequest) { 672 MOZ_ASSERT(multiPartChannel, "Should have mRequest unless we're multipart"); 673 nsCOMPtr<nsIChannel> baseChannel; 674 multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel)); 675 mRequest = baseChannel; 676 } 677 678 nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); 679 if (channel) { 680 /* Get our principal */ 681 nsCOMPtr<nsIScriptSecurityManager> secMan = 682 nsContentUtils::GetSecurityManager(); 683 if (secMan) { 684 nsresult rv = secMan->GetChannelResultPrincipal( 685 channel, getter_AddRefs(mPrincipal)); 686 if (NS_FAILED(rv)) { 687 return rv; 688 } 689 } 690 } 691 692 SetCacheValidation(mCacheEntry, aRequest, /* aForceTouch = */ true); 693 694 // Shouldn't we be dead already if this gets hit? 695 // Probably multipart/x-mixed-replace... 696 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 697 if (progressTracker->ObserverCount() == 0) { 698 this->Cancel(NS_IMAGELIB_ERROR_FAILURE); 699 } 700 701 // Try to retarget OnDataAvailable to a decode thread. We must process data 702 // URIs synchronously as per the spec however. 703 if (!channel || IsData()) { 704 return NS_OK; 705 } 706 707 nsCOMPtr<nsIThreadRetargetableRequest> retargetable = 708 do_QueryInterface(aRequest); 709 if (retargetable) { 710 nsAutoCString mimeType; 711 nsresult rv = channel->GetContentType(mimeType); 712 if (NS_SUCCEEDED(rv) && !mimeType.EqualsLiteral(IMAGE_SVG_XML)) { 713 mOffMainThreadData = true; 714 // Retarget OnDataAvailable to the DecodePool's IO thread. 715 nsCOMPtr<nsISerialEventTarget> target = 716 DecodePool::Singleton()->GetIOEventTarget(); 717 rv = retargetable->RetargetDeliveryTo(target); 718 MOZ_LOG(gImgLog, LogLevel::Warning, 719 ("[this=%p] imgRequest::OnStartRequest -- " 720 "RetargetDeliveryTo rv %" PRIu32 "=%s\n", 721 this, static_cast<uint32_t>(rv), 722 NS_SUCCEEDED(rv) ? "succeeded" : "failed")); 723 } 724 } 725 726 return NS_OK; 727 } 728 729 NS_IMETHODIMP 730 imgRequest::OnStopRequest(nsIRequest* aRequest, nsresult status) { 731 LOG_FUNC(gImgLog, "imgRequest::OnStopRequest"); 732 MOZ_ASSERT(NS_IsMainThread(), "Can't send notifications off-main-thread"); 733 734 RefPtr<imgRequest> strongThis = this; 735 736 bool isMultipart = false; 737 bool newPartPending = false; 738 { 739 MutexAutoLock lock(mMutex); 740 isMultipart = mIsMultiPartChannel; 741 newPartPending = mNewPartPending; 742 } 743 if (isMultipart && newPartPending) { 744 OnDataAvailable(aRequest, nullptr, 0, 0); 745 } 746 747 // Get this after OnDataAvailable because that might have created the image. 748 RefPtr<Image> image = GetImage(); 749 750 // XXXldb What if this is a non-last part of a multipart request? 751 // xxx before we release our reference to mRequest, lets 752 // save the last status that we saw so that the 753 // imgRequestProxy will have access to it. 754 if (mRequest) { 755 mRequest = nullptr; // we no longer need the request 756 } 757 758 // stop holding a ref to the channel, since we don't need it anymore 759 if (mChannel) { 760 mChannel->SetNotificationCallbacks(mPrevChannelSink); 761 mPrevChannelSink = nullptr; 762 mChannel = nullptr; 763 } 764 765 bool lastPart = true; 766 nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest)); 767 if (mpchan) { 768 mpchan->GetIsLastPart(&lastPart); 769 } 770 771 bool isPartial = false; 772 if (image && (status == NS_ERROR_NET_PARTIAL_TRANSFER)) { 773 isPartial = true; 774 status = NS_OK; // fake happy face 775 } 776 777 // Tell the image that it has all of the source data. Note that this can 778 // trigger a failure, since the image might be waiting for more non-optional 779 // data and this is the point where we break the news that it's not coming. 780 if (image) { 781 nsresult rv = image->OnImageDataComplete(aRequest, status, lastPart); 782 783 // If we got an error in the OnImageDataComplete() call, we don't want to 784 // proceed as if nothing bad happened. However, we also want to give 785 // precedence to failure status codes from necko, since presumably they're 786 // more meaningful. 787 if (NS_FAILED(rv) && NS_SUCCEEDED(status)) { 788 status = rv; 789 } 790 } 791 792 // If the request went through, update the cache entry size. Otherwise, 793 // cancel the request, which removes us from the cache. 794 if (image && NS_SUCCEEDED(status) && !isPartial) { 795 // We update the cache entry size here because this is where we finish 796 // loading compressed source data, which is part of our size calculus. 797 UpdateCacheEntrySize(); 798 799 } else if (isPartial) { 800 // Remove the partial image from the cache. 801 this->EvictFromCache(); 802 803 } else { 804 mImageErrorCode = status; 805 806 // if the error isn't "just" a partial transfer 807 // stops animations, removes from cache 808 this->Cancel(status); 809 } 810 811 if (!image) { 812 // We have to fire the OnStopRequest notifications ourselves because there's 813 // no image capable of doing so. 814 Progress progress = 815 LoadCompleteProgress(lastPart, /* aError = */ false, status); 816 817 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 818 progressTracker->SyncNotifyProgress(progress); 819 } 820 821 mTimedChannel = nullptr; 822 return NS_OK; 823 } 824 825 struct mimetype_closure { 826 nsACString* newType; 827 }; 828 829 /* prototype for these defined below */ 830 static nsresult sniff_mimetype_callback(nsIInputStream* in, void* closure, 831 const char* fromRawSegment, 832 uint32_t toOffset, uint32_t count, 833 uint32_t* writeCount); 834 835 /** nsThreadRetargetableStreamListener methods **/ 836 NS_IMETHODIMP 837 imgRequest::CheckListenerChain() { 838 return mOffMainThreadData ? NS_OK : NS_ERROR_NO_INTERFACE; 839 } 840 841 NS_IMETHODIMP 842 imgRequest::OnDataFinished(nsresult) { return NS_OK; } 843 844 /** nsIStreamListener methods **/ 845 846 struct NewPartResult final { 847 explicit NewPartResult(image::Image* aExistingImage) 848 : mImage(aExistingImage), 849 mIsFirstPart(!aExistingImage), 850 mSucceeded(false), 851 mShouldResetCacheEntry(false) {} 852 853 nsAutoCString mContentType; 854 nsAutoCString mContentDisposition; 855 RefPtr<image::Image> mImage; 856 const bool mIsFirstPart; 857 bool mSucceeded; 858 bool mShouldResetCacheEntry; 859 }; 860 861 static NewPartResult PrepareForNewPart(nsIRequest* aRequest, 862 nsIInputStream* aInStr, uint32_t aCount, 863 nsIURI* aURI, bool aIsMultipart, 864 image::Image* aExistingImage, 865 ProgressTracker* aProgressTracker, 866 uint64_t aInnerWindowId) { 867 NewPartResult result(aExistingImage); 868 869 if (aInStr) { 870 mimetype_closure closure; 871 closure.newType = &result.mContentType; 872 873 // Look at the first few bytes and see if we can tell what the data is from 874 // that since servers tend to lie. :( 875 uint32_t out; 876 aInStr->ReadSegments(sniff_mimetype_callback, &closure, aCount, &out); 877 } 878 879 nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest)); 880 if (result.mContentType.IsEmpty()) { 881 nsresult rv = 882 chan ? chan->GetContentType(result.mContentType) : NS_ERROR_FAILURE; 883 if (NS_FAILED(rv)) { 884 MOZ_LOG(gImgLog, LogLevel::Error, 885 ("imgRequest::PrepareForNewPart -- " 886 "Content type unavailable from the channel\n")); 887 if (!aIsMultipart) { 888 return result; 889 } 890 } 891 } 892 893 if (chan) { 894 chan->GetContentDispositionHeader(result.mContentDisposition); 895 } 896 897 MOZ_LOG(gImgLog, LogLevel::Debug, 898 ("imgRequest::PrepareForNewPart -- Got content type %s\n", 899 result.mContentType.get())); 900 901 // XXX If server lied about mimetype and it's SVG, we may need to copy 902 // the data and dispatch back to the main thread, AND tell the channel to 903 // dispatch there in the future. 904 905 // Create the new image and give it ownership of our ProgressTracker. 906 if (aIsMultipart) { 907 // Create the ProgressTracker and image for this part. 908 RefPtr<ProgressTracker> progressTracker = new ProgressTracker(); 909 RefPtr<image::Image> partImage = image::ImageFactory::CreateImage( 910 aRequest, progressTracker, result.mContentType, aURI, 911 /* aIsMultipart = */ true, aInnerWindowId); 912 913 if (result.mIsFirstPart) { 914 // First part for a multipart channel. Create the MultipartImage wrapper. 915 MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet"); 916 aProgressTracker->SetIsMultipart(); 917 result.mImage = image::ImageFactory::CreateMultipartImage( 918 partImage, aProgressTracker); 919 } else { 920 // Transition to the new part. 921 auto multipartImage = static_cast<MultipartImage*>(aExistingImage); 922 multipartImage->BeginTransitionToPart(partImage); 923 924 // Reset our cache entry size so it doesn't keep growing without bound. 925 result.mShouldResetCacheEntry = true; 926 } 927 } else { 928 MOZ_ASSERT(!aExistingImage, "New part for non-multipart channel?"); 929 MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet"); 930 931 // Create an image using our progress tracker. 932 result.mImage = image::ImageFactory::CreateImage( 933 aRequest, aProgressTracker, result.mContentType, aURI, 934 /* aIsMultipart = */ false, aInnerWindowId); 935 } 936 937 MOZ_ASSERT(result.mImage); 938 if (!result.mImage->HasError() || aIsMultipart) { 939 // We allow multipart images to fail to initialize (which generally 940 // indicates a bad content type) without cancelling the load, because 941 // subsequent parts might be fine. 942 result.mSucceeded = true; 943 } 944 945 return result; 946 } 947 948 class FinishPreparingForNewPartRunnable final : public Runnable { 949 public: 950 FinishPreparingForNewPartRunnable(imgRequest* aImgRequest, 951 NewPartResult&& aResult) 952 : Runnable("FinishPreparingForNewPartRunnable"), 953 mImgRequest(aImgRequest), 954 mResult(aResult) { 955 MOZ_ASSERT(aImgRequest); 956 } 957 958 NS_IMETHOD Run() override { 959 mImgRequest->FinishPreparingForNewPart(mResult); 960 return NS_OK; 961 } 962 963 private: 964 RefPtr<imgRequest> mImgRequest; 965 NewPartResult mResult; 966 }; 967 968 void imgRequest::FinishPreparingForNewPart(const NewPartResult& aResult) { 969 MOZ_ASSERT(NS_IsMainThread()); 970 971 mContentType = aResult.mContentType; 972 973 SetProperties(aResult.mContentType, aResult.mContentDisposition); 974 975 if (aResult.mIsFirstPart) { 976 // Notify listeners that we have an image. 977 mImageAvailable = true; 978 RefPtr<ProgressTracker> progressTracker = GetProgressTracker(); 979 progressTracker->OnImageAvailable(); 980 MOZ_ASSERT(progressTracker->HasImage()); 981 } 982 983 if (aResult.mShouldResetCacheEntry) { 984 ResetCacheEntry(); 985 } 986 987 if (IsDecodeRequested()) { 988 aResult.mImage->StartDecoding(imgIContainer::FLAG_NONE); 989 } 990 } 991 992 bool imgRequest::ImageAvailable() const { return mImageAvailable; } 993 994 NS_IMETHODIMP 995 imgRequest::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInStr, 996 uint64_t aOffset, uint32_t aCount) { 997 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::OnDataAvailable", "count", aCount); 998 999 NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!"); 1000 1001 RefPtr<Image> image; 1002 RefPtr<ProgressTracker> progressTracker; 1003 bool isMultipart = false; 1004 bool newPartPending = false; 1005 uint64_t innerWindowId = 0; 1006 1007 // Retrieve and update our state. 1008 { 1009 MutexAutoLock lock(mMutex); 1010 image = mImage; 1011 progressTracker = mProgressTracker; 1012 isMultipart = mIsMultiPartChannel; 1013 newPartPending = mNewPartPending; 1014 mNewPartPending = false; 1015 innerWindowId = mInnerWindowId; 1016 } 1017 1018 // If this is a new part, we need to sniff its content type and create an 1019 // appropriate image. 1020 if (newPartPending) { 1021 NewPartResult result = 1022 PrepareForNewPart(aRequest, aInStr, aCount, mURI, isMultipart, image, 1023 progressTracker, innerWindowId); 1024 bool succeeded = result.mSucceeded; 1025 1026 if (result.mImage) { 1027 image = result.mImage; 1028 1029 // Update our state to reflect this new part. 1030 { 1031 MutexAutoLock lock(mMutex); 1032 mImage = image; 1033 1034 mProgressTracker = nullptr; 1035 } 1036 1037 // We only get an event target if we are not on the main thread, because 1038 // we have to dispatch in that case. 1039 nsCOMPtr<nsIEventTarget> eventTarget; 1040 if (!NS_IsMainThread()) { 1041 eventTarget = GetMainThreadSerialEventTarget(); 1042 MOZ_ASSERT(eventTarget); 1043 } 1044 1045 // Some property objects are not threadsafe, and we need to send 1046 // OnImageAvailable on the main thread, so finish on the main thread. 1047 if (!eventTarget) { 1048 MOZ_ASSERT(NS_IsMainThread()); 1049 FinishPreparingForNewPart(result); 1050 } else { 1051 nsCOMPtr<nsIRunnable> runnable = 1052 new FinishPreparingForNewPartRunnable(this, std::move(result)); 1053 eventTarget->Dispatch(CreateRenderBlockingRunnable(runnable.forget()), 1054 NS_DISPATCH_NORMAL); 1055 } 1056 } 1057 1058 if (!succeeded) { 1059 // Something went wrong; probably a content type issue. 1060 Cancel(NS_IMAGELIB_ERROR_FAILURE); 1061 return NS_BINDING_ABORTED; 1062 } 1063 } 1064 1065 // Notify the image that it has new data. 1066 if (aInStr) { 1067 nsresult rv = 1068 image->OnImageDataAvailable(aRequest, aInStr, aOffset, aCount); 1069 1070 if (NS_FAILED(rv)) { 1071 MOZ_LOG(gImgLog, LogLevel::Warning, 1072 ("[this=%p] imgRequest::OnDataAvailable -- " 1073 "copy to RasterImage failed\n", 1074 this)); 1075 Cancel(NS_IMAGELIB_ERROR_FAILURE); 1076 return NS_BINDING_ABORTED; 1077 } 1078 } 1079 1080 return NS_OK; 1081 } 1082 1083 void imgRequest::SetProperties(const nsACString& aContentType, 1084 const nsACString& aContentDisposition) { 1085 /* set our mimetype as a property */ 1086 nsCOMPtr<nsISupportsCString> contentType = 1087 do_CreateInstance("@mozilla.org/supports-cstring;1"); 1088 if (contentType) { 1089 contentType->SetData(aContentType); 1090 mProperties->Set("type", contentType); 1091 } 1092 1093 /* set our content disposition as a property */ 1094 if (!aContentDisposition.IsEmpty()) { 1095 nsCOMPtr<nsISupportsCString> contentDisposition = 1096 do_CreateInstance("@mozilla.org/supports-cstring;1"); 1097 if (contentDisposition) { 1098 contentDisposition->SetData(aContentDisposition); 1099 mProperties->Set("content-disposition", contentDisposition); 1100 } 1101 } 1102 } 1103 1104 static nsresult sniff_mimetype_callback(nsIInputStream* in, void* data, 1105 const char* fromRawSegment, 1106 uint32_t toOffset, uint32_t count, 1107 uint32_t* writeCount) { 1108 mimetype_closure* closure = static_cast<mimetype_closure*>(data); 1109 1110 NS_ASSERTION(closure, "closure is null!"); 1111 1112 if (count > 0) { 1113 imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *closure->newType); 1114 } 1115 1116 *writeCount = 0; 1117 return NS_ERROR_FAILURE; 1118 } 1119 1120 /** nsIInterfaceRequestor methods **/ 1121 1122 NS_IMETHODIMP 1123 imgRequest::GetInterface(const nsIID& aIID, void** aResult) { 1124 if (!mPrevChannelSink || aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { 1125 return QueryInterface(aIID, aResult); 1126 } 1127 1128 NS_ASSERTION( 1129 mPrevChannelSink != this, 1130 "Infinite recursion - don't keep track of channel sinks that are us!"); 1131 return mPrevChannelSink->GetInterface(aIID, aResult); 1132 } 1133 1134 /** nsIChannelEventSink methods **/ 1135 NS_IMETHODIMP 1136 imgRequest::AsyncOnChannelRedirect(nsIChannel* oldChannel, 1137 nsIChannel* newChannel, uint32_t flags, 1138 nsIAsyncVerifyRedirectCallback* callback) { 1139 NS_ASSERTION(mRequest && mChannel, 1140 "Got a channel redirect after we nulled out mRequest!"); 1141 NS_ASSERTION(mChannel == oldChannel, 1142 "Got a channel redirect for an unknown channel!"); 1143 NS_ASSERTION(newChannel, "Got a redirect to a NULL channel!"); 1144 1145 SetCacheValidation(mCacheEntry, oldChannel); 1146 1147 // Prepare for callback 1148 mRedirectCallback = callback; 1149 mNewRedirectChannel = newChannel; 1150 1151 nsCOMPtr<nsIChannelEventSink> sink(do_GetInterface(mPrevChannelSink)); 1152 if (sink) { 1153 nsresult rv = 1154 sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); 1155 if (NS_FAILED(rv)) { 1156 mRedirectCallback = nullptr; 1157 mNewRedirectChannel = nullptr; 1158 } 1159 return rv; 1160 } 1161 1162 (void)OnRedirectVerifyCallback(NS_OK); 1163 return NS_OK; 1164 } 1165 1166 NS_IMETHODIMP 1167 imgRequest::OnRedirectVerifyCallback(nsresult result) { 1168 NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback"); 1169 NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback"); 1170 1171 if (NS_FAILED(result)) { 1172 mRedirectCallback->OnRedirectVerifyCallback(result); 1173 mRedirectCallback = nullptr; 1174 mNewRedirectChannel = nullptr; 1175 return NS_OK; 1176 } 1177 1178 mChannel = mNewRedirectChannel; 1179 mTimedChannel = do_QueryInterface(mChannel); 1180 mNewRedirectChannel = nullptr; 1181 1182 if (LOG_TEST(LogLevel::Debug)) { 1183 LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::OnChannelRedirect", "old", 1184 mFinalURI ? mFinalURI->GetSpecOrDefault().get() : ""); 1185 } 1186 1187 // If the previous URI is a non-HTTPS URI, record that fact for later use by 1188 // security code, which needs to know whether there is an insecure load at any 1189 // point in the redirect chain. 1190 bool schemeLocal = false; 1191 if (NS_FAILED(NS_URIChainHasFlags(mFinalURI, 1192 nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, 1193 &schemeLocal)) || 1194 (!mFinalURI->SchemeIs("https") && !mFinalURI->SchemeIs("chrome") && 1195 !schemeLocal)) { 1196 MutexAutoLock lock(mMutex); 1197 1198 // The csp directive upgrade-insecure-requests performs an internal redirect 1199 // to upgrade all requests from http to https before any data is fetched 1200 // from the network. Do not pollute mHadInsecureRedirect in case of such an 1201 // internal redirect. 1202 nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); 1203 bool upgradeInsecureRequests = 1204 loadInfo ? loadInfo->GetUpgradeInsecureRequests() || 1205 loadInfo->GetBrowserUpgradeInsecureRequests() 1206 : false; 1207 if (!upgradeInsecureRequests) { 1208 mHadInsecureRedirect = true; 1209 } 1210 } 1211 1212 // Update the final URI. 1213 mChannel->GetURI(getter_AddRefs(mFinalURI)); 1214 1215 if (LOG_TEST(LogLevel::Debug)) { 1216 LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::OnChannelRedirect", "new", 1217 mFinalURI ? mFinalURI->GetSpecOrDefault().get() : ""); 1218 } 1219 1220 // Make sure we have a protocol that returns data rather than opens an 1221 // external application, e.g. 'mailto:'. 1222 if (nsContentUtils::IsExternalProtocol(mFinalURI)) { 1223 mRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_ABORT); 1224 mRedirectCallback = nullptr; 1225 return NS_OK; 1226 } 1227 1228 mRedirectCallback->OnRedirectVerifyCallback(NS_OK); 1229 mRedirectCallback = nullptr; 1230 return NS_OK; 1231 } 1232 1233 void imgRequest::UpdateShouldReportRenderTimeForLCP() { 1234 if (mTimedChannel) { 1235 bool allRedirectPassTAO = false; 1236 mTimedChannel->GetAllRedirectsPassTimingAllowCheck(&allRedirectPassTAO); 1237 mShouldReportRenderTimeForLCP = 1238 mTimedChannel->TimingAllowCheck(mTriggeringPrincipal) && 1239 allRedirectPassTAO; 1240 } 1241 }