nsImageLoadingContent.cpp (66705B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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 /* 8 * A base class which implements nsIImageLoadingContent and can be 9 * subclassed by various content nodes that want to provide image 10 * loading functionality (eg <img>, <object>, etc). 11 */ 12 13 #include "nsImageLoadingContent.h" 14 15 #include "Orientation.h" 16 #include "imgIContainer.h" 17 #include "imgLoader.h" 18 #include "imgRequestProxy.h" 19 #include "mozAutoDocUpdate.h" 20 #include "mozilla/AsyncEventDispatcher.h" 21 #include "mozilla/AutoRestore.h" 22 #include "mozilla/CycleCollectedJSContext.h" 23 #include "mozilla/EventStateManager.h" 24 #include "mozilla/PageloadEvent.h" 25 #include "mozilla/Preferences.h" 26 #include "mozilla/PresShell.h" 27 #include "mozilla/SVGImageFrame.h" 28 #include "mozilla/SVGObserverUtils.h" 29 #include "mozilla/StaticPrefs_image.h" 30 #include "mozilla/StaticPrefs_svg.h" 31 #include "mozilla/dom/BindContext.h" 32 #include "mozilla/dom/Document.h" 33 #include "mozilla/dom/Element.h" 34 #include "mozilla/dom/FetchPriority.h" 35 #include "mozilla/dom/HTMLImageElement.h" 36 #include "mozilla/dom/ImageTextBinding.h" 37 #include "mozilla/dom/LargestContentfulPaint.h" 38 #include "mozilla/dom/PContent.h" // For TextRecognitionResult 39 #include "mozilla/dom/PageLoadEventUtils.h" 40 #include "mozilla/dom/ReferrerInfo.h" 41 #include "mozilla/dom/ResponsiveImageSelector.h" 42 #include "mozilla/dom/ScriptSettings.h" 43 #include "mozilla/intl/Locale.h" 44 #include "mozilla/intl/LocaleService.h" 45 #include "mozilla/net/UrlClassifierFeatureFactory.h" 46 #include "mozilla/widget/TextRecognition.h" 47 #include "nsContentList.h" 48 #include "nsContentPolicyUtils.h" 49 #include "nsContentUtils.h" 50 #include "nsError.h" 51 #include "nsIChannel.h" 52 #include "nsIContent.h" 53 #include "nsIContentPolicy.h" 54 #include "nsIFrame.h" 55 #include "nsIScriptGlobalObject.h" 56 #include "nsIStreamListener.h" 57 #include "nsIURI.h" 58 #include "nsImageFrame.h" 59 #include "nsLayoutUtils.h" 60 #include "nsNetUtil.h" 61 #include "nsServiceManagerUtils.h" 62 #include "nsThreadUtils.h" 63 64 #ifdef LoadImage 65 // Undefine LoadImage to prevent naming conflict with Windows. 66 # undef LoadImage 67 #endif 68 69 using namespace mozilla; 70 using namespace mozilla::dom; 71 72 #ifdef DEBUG_chb 73 static void PrintReqURL(imgIRequest* req) { 74 if (!req) { 75 printf("(null req)\n"); 76 return; 77 } 78 79 nsCOMPtr<nsIURI> uri; 80 req->GetURI(getter_AddRefs(uri)); 81 if (!uri) { 82 printf("(null uri)\n"); 83 return; 84 } 85 86 nsAutoCString spec; 87 uri->GetSpec(spec); 88 printf("spec='%s'\n", spec.get()); 89 } 90 #endif /* DEBUG_chb */ 91 92 class ImageLoadTask : public MicroTaskRunnable { 93 public: 94 ImageLoadTask(nsImageLoadingContent* aElement, bool aAlwaysLoad, 95 bool aUseUrgentStartForChannel) 96 : mElement(aElement), 97 mDocument(aElement->AsContent()->OwnerDoc()), 98 mAlwaysLoad(aAlwaysLoad), 99 mUseUrgentStartForChannel(aUseUrgentStartForChannel) { 100 mDocument->BlockOnload(); 101 } 102 103 void Run(AutoSlowOperation& aAso) override { 104 if (mElement->mPendingImageLoadTask == this) { 105 JSCallingLocation::AutoFallback fallback(&mCallingLocation); 106 mElement->mUseUrgentStartForChannel = mUseUrgentStartForChannel; 107 mElement->ClearImageLoadTask(); 108 mElement->LoadSelectedImage(mAlwaysLoad, /* aStopLazyLoading = */ false); 109 } 110 mDocument->UnblockOnload(false); 111 } 112 113 bool Suppressed() override { 114 nsIGlobalObject* global = mElement->AsContent()->GetOwnerGlobal(); 115 return global && global->IsInSyncOperation(); 116 } 117 118 bool AlwaysLoad() const { return mAlwaysLoad; } 119 120 private: 121 ~ImageLoadTask() = default; 122 const RefPtr<nsImageLoadingContent> mElement; 123 const RefPtr<dom::Document> mDocument; 124 const JSCallingLocation mCallingLocation{JSCallingLocation::Get()}; 125 const bool mAlwaysLoad; 126 // True if we want to set nsIClassOfService::UrgentStart to the channel to get 127 // the response ASAP for better user responsiveness. 128 const bool mUseUrgentStartForChannel; 129 }; 130 131 nsImageLoadingContent::nsImageLoadingContent() : mObserverList(nullptr) { 132 if (!nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) { 133 mLoadingEnabled = false; 134 } 135 136 mMostRecentRequestChange = TimeStamp::ProcessCreation(); 137 } 138 139 void nsImageLoadingContent::Destroy() { 140 // Cancel our requests so they won't hold stale refs to us 141 // NB: Don't ask to discard the images here. 142 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST); 143 ClearCurrentRequest(NS_BINDING_ABORTED); 144 ClearPendingRequest(NS_BINDING_ABORTED); 145 } 146 147 nsImageLoadingContent::~nsImageLoadingContent() { 148 MOZ_ASSERT(!mCurrentRequest && !mPendingRequest, "Destroy not called"); 149 MOZ_ASSERT(!mObserverList.mObserver && !mObserverList.mNext, 150 "Observers still registered?"); 151 MOZ_ASSERT(mScriptedObservers.IsEmpty(), 152 "Scripted observers still registered?"); 153 MOZ_ASSERT(mOutstandingDecodePromises == 0, 154 "Decode promises still unfulfilled?"); 155 MOZ_ASSERT(mDecodePromises.IsEmpty(), "Decode promises still unfulfilled?"); 156 } 157 158 void nsImageLoadingContent::QueueImageTask( 159 nsIURI* aSrcURI, nsIPrincipal* aSrcTriggeringPrincipal, bool aForceAsync, 160 bool aAlwaysLoad, bool aNotify) { 161 // If loading is temporarily disabled, we don't want to queue tasks that may 162 // then run when loading is re-enabled. 163 // Roughly step 1 and 2. 164 // FIXME(emilio): Would be great to do this more per-spec. We don't cancel 165 // existing loads etc. 166 if (!LoadingEnabled() || !GetOurOwnerDoc()->ShouldLoadImages()) { 167 return; 168 } 169 170 // Ensure that we don't overwrite a previous load request that requires 171 // a complete load to occur. 172 const bool alwaysLoad = aAlwaysLoad || (mPendingImageLoadTask && 173 mPendingImageLoadTask->AlwaysLoad()); 174 175 // Steps 5 and 7 (sync cache check for src). 176 const bool shouldLoadSync = [&] { 177 if (aForceAsync) { 178 return false; 179 } 180 if (!aSrcURI) { 181 // NOTE(emilio): we need to also do a sync check for empty / invalid src, 182 // see https://github.com/whatwg/html/issues/2429 183 // But do it sync only when there's a current request. 184 return !!mCurrentRequest; 185 } 186 if (AsContent()->IsSVGElement()) { 187 if (GetOurOwnerDoc()->IsBeingUsedAsImage()) { 188 return true; 189 } 190 if (StaticPrefs::svg_image_element_force_sync_load()) { 191 return true; 192 } 193 } 194 return nsContentUtils::IsImageAvailable( 195 AsContent(), aSrcURI, aSrcTriggeringPrincipal, GetCORSMode()); 196 }(); 197 198 if (shouldLoadSync) { 199 if (!nsContentUtils::IsSafeToRunScript()) { 200 // If not safe to run script, we should do the sync load task as soon as 201 // possible instead. This prevents unsound state changes from frame 202 // construction and such. 203 void (nsImageLoadingContent::*fp)(nsIURI*, nsIPrincipal*, bool, bool, 204 bool) = 205 &nsImageLoadingContent::QueueImageTask; 206 nsContentUtils::AddScriptRunner( 207 NewRunnableMethod<nsIURI*, nsIPrincipal*, bool, bool, bool>( 208 "nsImageLoadingContent::QueueImageTask", this, fp, aSrcURI, 209 aSrcTriggeringPrincipal, aForceAsync, aAlwaysLoad, 210 /* aNotify = */ true)); 211 return; 212 } 213 214 ClearImageLoadTask(); 215 LoadSelectedImage(alwaysLoad, mLazyLoading && aSrcURI); 216 return; 217 } 218 219 if (mLazyLoading) { 220 // This check is not in the spec, but it is just a performance optimization. 221 // The reasoning for why it is sound is that we early-return from the image 222 // task when lazy loading, and that StopLazyLoading makes us queue a new 223 // task (which will implicitly cancel all the pre-existing tasks). 224 return; 225 } 226 227 RefPtr task = new ImageLoadTask(this, alwaysLoad, mUseUrgentStartForChannel); 228 mPendingImageLoadTask = task; 229 // We might have just become non-broken. 230 UpdateImageState(aNotify); 231 // The task checks this to determine if it was the last queued event, and so 232 // earlier tasks are implicitly canceled. 233 CycleCollectedJSContext::Get()->DispatchToMicroTask(task.forget()); 234 } 235 236 void nsImageLoadingContent::ClearImageLoadTask() { 237 mPendingImageLoadTask = nullptr; 238 } 239 240 /* 241 * imgINotificationObserver impl 242 */ 243 void nsImageLoadingContent::Notify(imgIRequest* aRequest, int32_t aType, 244 const nsIntRect* aData) { 245 MOZ_ASSERT(aRequest, "no request?"); 246 MOZ_ASSERT(aRequest == mCurrentRequest || aRequest == mPendingRequest, 247 "Forgot to cancel a previous request?"); 248 249 if (aType == imgINotificationObserver::IS_ANIMATED) { 250 return OnImageIsAnimated(aRequest); 251 } 252 253 if (aType == imgINotificationObserver::UNLOCKED_DRAW) { 254 return OnUnlockedDraw(); 255 } 256 257 { 258 // Calling Notify on observers can modify the list of observers so make 259 // a local copy. 260 AutoTArray<nsCOMPtr<imgINotificationObserver>, 2> observers; 261 for (ImageObserver *observer = &mObserverList, *next; observer; 262 observer = next) { 263 next = observer->mNext; 264 if (observer->mObserver) { 265 observers.AppendElement(observer->mObserver); 266 } 267 } 268 269 nsAutoScriptBlocker scriptBlocker; 270 271 for (auto& observer : observers) { 272 observer->Notify(aRequest, aType, aData); 273 } 274 } 275 276 if (aType == imgINotificationObserver::LOAD_COMPLETE) { 277 uint32_t reqStatus; 278 aRequest->GetImageStatus(&reqStatus); 279 /* triage STATUS_ERROR */ 280 if (reqStatus & imgIRequest::STATUS_ERROR) { 281 nsresult errorCode = NS_OK; 282 aRequest->GetImageErrorCode(&errorCode); 283 284 /* Handle image not loading error because source was a tracking URL (or 285 * fingerprinting, cryptomining, etc). 286 * We make a note of this image node by including it in a dedicated 287 * array of blocked tracking nodes under its parent document. 288 */ 289 if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode( 290 errorCode)) { 291 Document* doc = GetOurOwnerDoc(); 292 doc->AddBlockedNodeByClassifier(AsContent()); 293 } 294 } 295 return OnLoadComplete(aRequest, reqStatus); 296 } 297 298 if ((aType == imgINotificationObserver::FRAME_COMPLETE || 299 aType == imgINotificationObserver::FRAME_UPDATE) && 300 mCurrentRequest == aRequest) { 301 MaybeResolveDecodePromises(); 302 } 303 304 if (aType == imgINotificationObserver::DECODE_COMPLETE) { 305 nsCOMPtr<imgIContainer> container; 306 aRequest->GetImage(getter_AddRefs(container)); 307 if (container) { 308 container->PropagateUseCounters(GetOurOwnerDoc()); 309 } 310 UpdateImageState(true); 311 } 312 } 313 314 void nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest, 315 uint32_t aImageStatus) { 316 // XXXjdm This occurs when we have a pending request created, then another 317 // pending request replaces it before the first one is finished. 318 // This begs the question of what the correct behaviour is; we used 319 // to not have to care because we ran this code in OnStopDecode which 320 // wasn't called when the first request was cancelled. For now, I choose 321 // to punt when the given request doesn't appear to have terminated in 322 // an expected state. 323 if (!(aImageStatus & 324 (imgIRequest::STATUS_ERROR | imgIRequest::STATUS_LOAD_COMPLETE))) { 325 return; 326 } 327 328 // If the pending request is loaded, switch to it. 329 if (aRequest == mPendingRequest) { 330 MakePendingRequestCurrent(); 331 } 332 MOZ_ASSERT(aRequest == mCurrentRequest, 333 "One way or another, we should be current by now"); 334 335 // Fire the appropriate DOM event. 336 if (!(aImageStatus & imgIRequest::STATUS_ERROR)) { 337 FireEvent(u"load"_ns); 338 } else { 339 FireEvent(u"error"_ns); 340 } 341 342 Element* element = AsContent()->AsElement(); 343 SVGObserverUtils::InvalidateDirectRenderingObservers(element); 344 MaybeResolveDecodePromises(); 345 LargestContentfulPaint::MaybeProcessImageForElementTiming(mCurrentRequest, 346 element); 347 UpdateImageState(true); 348 } 349 350 void nsImageLoadingContent::OnUnlockedDraw() { 351 // This notification is only sent for animated images. It's OK for 352 // non-animated images to wait until the next frame visibility update to 353 // become locked. (And that's preferable, since in the case of scrolling it 354 // keeps memory usage minimal.) 355 // 356 // For animated images, though, we want to mark them visible right away so we 357 // can call IncrementAnimationConsumers() on them and they'll start animating. 358 359 nsIFrame* frame = GetOurPrimaryImageFrame(); 360 if (!frame) { 361 return; 362 } 363 364 if (frame->GetVisibility() == Visibility::ApproximatelyVisible) { 365 // This frame is already marked visible; there's nothing to do. 366 return; 367 } 368 369 nsPresContext* presContext = frame->PresContext(); 370 if (!presContext) { 371 return; 372 } 373 374 PresShell* presShell = presContext->GetPresShell(); 375 if (!presShell) { 376 return; 377 } 378 379 presShell->EnsureFrameInApproximatelyVisibleList(frame); 380 } 381 382 void nsImageLoadingContent::OnImageIsAnimated(imgIRequest* aRequest) { 383 bool* requestFlag = nullptr; 384 if (aRequest == mCurrentRequest) { 385 requestFlag = &mCurrentRequestRegistered; 386 } else if (aRequest == mPendingRequest) { 387 requestFlag = &mPendingRequestRegistered; 388 } else { 389 MOZ_ASSERT_UNREACHABLE("Which image is this?"); 390 return; 391 } 392 nsLayoutUtils::RegisterImageRequest(GetFramePresContext(), aRequest, 393 requestFlag); 394 } 395 396 static bool IsOurImageFrame(nsIFrame* aFrame) { 397 if (nsImageFrame* f = do_QueryFrame(aFrame)) { 398 return f->IsForImageLoadingContent(); 399 } 400 return aFrame->IsSVGImageFrame() || aFrame->IsSVGFEImageFrame(); 401 } 402 403 nsIFrame* nsImageLoadingContent::GetOurPrimaryImageFrame() { 404 nsIFrame* frame = AsContent()->GetPrimaryFrame(); 405 if (!frame || !IsOurImageFrame(frame)) { 406 return nullptr; 407 } 408 return frame; 409 } 410 411 /* 412 * nsIImageLoadingContent impl 413 */ 414 415 void nsImageLoadingContent::SetLoadingEnabled(bool aLoadingEnabled) { 416 if (nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) { 417 mLoadingEnabled = aLoadingEnabled; 418 } 419 } 420 421 nsresult nsImageLoadingContent::GetSyncDecodingHint(bool* aHint) { 422 *aHint = mSyncDecodingHint; 423 return NS_OK; 424 } 425 426 already_AddRefed<Promise> nsImageLoadingContent::QueueDecodeAsync( 427 ErrorResult& aRv) { 428 Document* doc = GetOurOwnerDoc(); 429 RefPtr<Promise> promise = Promise::Create(doc->GetScopeObject(), aRv); 430 if (aRv.Failed()) { 431 return nullptr; 432 } 433 434 class QueueDecodeTask final : public MicroTaskRunnable { 435 public: 436 QueueDecodeTask(nsImageLoadingContent* aOwner, Promise* aPromise, 437 uint32_t aRequestGeneration) 438 : mOwner(aOwner), 439 mPromise(aPromise), 440 mRequestGeneration(aRequestGeneration) {} 441 442 virtual void Run(AutoSlowOperation& aAso) override { 443 mOwner->DecodeAsync(std::move(mPromise), mRequestGeneration); 444 } 445 446 virtual bool Suppressed() override { 447 nsIGlobalObject* global = mOwner->GetOurOwnerDoc()->GetScopeObject(); 448 return global && global->IsInSyncOperation(); 449 } 450 451 private: 452 RefPtr<nsImageLoadingContent> mOwner; 453 RefPtr<Promise> mPromise; 454 uint32_t mRequestGeneration; 455 }; 456 457 if (++mOutstandingDecodePromises == 1) { 458 MOZ_ASSERT(mDecodePromises.IsEmpty()); 459 doc->RegisterActivityObserver(AsContent()->AsElement()); 460 } 461 462 auto task = MakeRefPtr<QueueDecodeTask>(this, promise, mRequestGeneration); 463 CycleCollectedJSContext::Get()->DispatchToMicroTask(task.forget()); 464 return promise.forget(); 465 } 466 467 void nsImageLoadingContent::DecodeAsync(RefPtr<Promise>&& aPromise, 468 uint32_t aRequestGeneration) { 469 MOZ_ASSERT(aPromise); 470 MOZ_ASSERT(mOutstandingDecodePromises > mDecodePromises.Length()); 471 472 // The request may have gotten updated since the decode call was issued. 473 if (aRequestGeneration != mRequestGeneration) { 474 aPromise->MaybeReject(NS_ERROR_DOM_IMAGE_INVALID_REQUEST); 475 // We never got placed in mDecodePromises, so we must ensure we decrement 476 // the counter explicitly. 477 --mOutstandingDecodePromises; 478 MaybeDeregisterActivityObserver(); 479 return; 480 } 481 482 bool wasEmpty = mDecodePromises.IsEmpty(); 483 mDecodePromises.AppendElement(std::move(aPromise)); 484 if (wasEmpty) { 485 MaybeResolveDecodePromises(); 486 } 487 } 488 489 void nsImageLoadingContent::MaybeResolveDecodePromises() { 490 if (mDecodePromises.IsEmpty()) { 491 return; 492 } 493 494 if (!mCurrentRequest) { 495 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST); 496 return; 497 } 498 499 // Only can resolve if our document is the active document. If not we are 500 // supposed to reject the promise, even if it was fulfilled successfully. 501 if (!GetOurOwnerDoc()->IsCurrentActiveDocument()) { 502 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT); 503 return; 504 } 505 506 // If any error occurred while decoding, we need to reject first. 507 uint32_t status = imgIRequest::STATUS_NONE; 508 mCurrentRequest->GetImageStatus(&status); 509 if (status & imgIRequest::STATUS_ERROR) { 510 RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN); 511 return; 512 } 513 514 // We need the size to bother with requesting a decode, as we are either 515 // blocked on validation or metadata decoding. 516 if (!(status & imgIRequest::STATUS_SIZE_AVAILABLE)) { 517 return; 518 } 519 520 // Check the surface cache status and/or request decoding begin. We do this 521 // before LOAD_COMPLETE because we want to start as soon as possible. 522 uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING | 523 imgIContainer::FLAG_AVOID_REDECODE_FOR_SIZE; 524 imgIContainer::DecodeResult decodeResult = 525 mCurrentRequest->RequestDecodeWithResult(flags); 526 if (decodeResult == imgIContainer::DECODE_REQUESTED) { 527 return; 528 } 529 if (decodeResult == imgIContainer::DECODE_REQUEST_FAILED) { 530 RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN); 531 return; 532 } 533 MOZ_ASSERT(decodeResult == imgIContainer::DECODE_SURFACE_AVAILABLE); 534 535 // We can only fulfill the promises once we have all the data. 536 if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) { 537 return; 538 } 539 540 for (auto& promise : mDecodePromises) { 541 promise->MaybeResolveWithUndefined(); 542 } 543 544 MOZ_ASSERT(mOutstandingDecodePromises >= mDecodePromises.Length()); 545 mOutstandingDecodePromises -= mDecodePromises.Length(); 546 mDecodePromises.Clear(); 547 MaybeDeregisterActivityObserver(); 548 } 549 550 void nsImageLoadingContent::RejectDecodePromises(nsresult aStatus) { 551 if (mDecodePromises.IsEmpty()) { 552 return; 553 } 554 555 for (auto& promise : mDecodePromises) { 556 promise->MaybeReject(aStatus); 557 } 558 559 MOZ_ASSERT(mOutstandingDecodePromises >= mDecodePromises.Length()); 560 mOutstandingDecodePromises -= mDecodePromises.Length(); 561 mDecodePromises.Clear(); 562 MaybeDeregisterActivityObserver(); 563 } 564 565 void nsImageLoadingContent::MaybeAgeRequestGeneration(nsIURI* aNewURI) { 566 MOZ_ASSERT(mCurrentRequest); 567 568 // If the current request is about to change, we need to verify if the new 569 // URI matches the existing current request's URI. If it doesn't, we need to 570 // reject any outstanding promises due to the current request mutating as per 571 // step 2.2 of the decode API requirements. 572 // 573 // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode 574 if (aNewURI) { 575 nsCOMPtr<nsIURI> currentURI; 576 mCurrentRequest->GetURI(getter_AddRefs(currentURI)); 577 578 bool equal = false; 579 if (NS_SUCCEEDED(aNewURI->Equals(currentURI, &equal)) && equal) { 580 return; 581 } 582 } 583 584 ++mRequestGeneration; 585 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST); 586 } 587 588 void nsImageLoadingContent::MaybeDeregisterActivityObserver() { 589 if (mOutstandingDecodePromises == 0) { 590 MOZ_ASSERT(mDecodePromises.IsEmpty()); 591 GetOurOwnerDoc()->UnregisterActivityObserver(AsContent()->AsElement()); 592 } 593 } 594 595 void nsImageLoadingContent::SetSyncDecodingHint(bool aHint) { 596 if (mSyncDecodingHint == aHint) { 597 return; 598 } 599 600 mSyncDecodingHint = aHint; 601 MaybeForceSyncDecoding(/* aPrepareNextRequest */ false); 602 } 603 604 void nsImageLoadingContent::MaybeForceSyncDecoding( 605 bool aPrepareNextRequest, nsIFrame* aFrame /* = nullptr */) { 606 // GetOurPrimaryImageFrame() might not return the frame during frame init. 607 nsIFrame* frame = aFrame ? aFrame : GetOurPrimaryImageFrame(); 608 if (!frame) { 609 return; 610 } 611 612 bool forceSync = mSyncDecodingHint; 613 if (!forceSync && aPrepareNextRequest) { 614 // Detect JavaScript-based animations created by changing the |src| 615 // attribute on a timer. 616 TimeStamp now = TimeStamp::Now(); 617 TimeDuration threshold = TimeDuration::FromMilliseconds( 618 StaticPrefs::image_infer_src_animation_threshold_ms()); 619 620 // If the length of time between request changes is less than the threshold, 621 // then force sync decoding to eliminate flicker from the animation. 622 forceSync = (now - mMostRecentRequestChange < threshold); 623 mMostRecentRequestChange = now; 624 } 625 626 if (nsImageFrame* imageFrame = do_QueryFrame(frame)) { 627 imageFrame->SetForceSyncDecoding(forceSync); 628 } else if (SVGImageFrame* svgImageFrame = do_QueryFrame(frame)) { 629 svgImageFrame->SetForceSyncDecoding(forceSync); 630 } 631 } 632 633 static void ReplayImageStatus(imgIRequest* aRequest, 634 imgINotificationObserver* aObserver) { 635 if (!aRequest) { 636 return; 637 } 638 639 uint32_t status = 0; 640 nsresult rv = aRequest->GetImageStatus(&status); 641 if (NS_FAILED(rv)) { 642 return; 643 } 644 645 if (status & imgIRequest::STATUS_SIZE_AVAILABLE) { 646 aObserver->Notify(aRequest, imgINotificationObserver::SIZE_AVAILABLE, 647 nullptr); 648 } 649 if (status & imgIRequest::STATUS_FRAME_COMPLETE) { 650 aObserver->Notify(aRequest, imgINotificationObserver::FRAME_COMPLETE, 651 nullptr); 652 } 653 if (status & imgIRequest::STATUS_HAS_TRANSPARENCY) { 654 aObserver->Notify(aRequest, imgINotificationObserver::HAS_TRANSPARENCY, 655 nullptr); 656 } 657 if (status & imgIRequest::STATUS_IS_ANIMATED) { 658 aObserver->Notify(aRequest, imgINotificationObserver::IS_ANIMATED, nullptr); 659 } 660 if (status & imgIRequest::STATUS_DECODE_COMPLETE) { 661 aObserver->Notify(aRequest, imgINotificationObserver::DECODE_COMPLETE, 662 nullptr); 663 } 664 if (status & imgIRequest::STATUS_LOAD_COMPLETE) { 665 aObserver->Notify(aRequest, imgINotificationObserver::LOAD_COMPLETE, 666 nullptr); 667 } 668 } 669 670 void nsImageLoadingContent::AddNativeObserver( 671 imgINotificationObserver* aObserver) { 672 if (NS_WARN_IF(!aObserver)) { 673 return; 674 } 675 676 if (!mObserverList.mObserver) { 677 // Don't touch the linking of the list! 678 mObserverList.mObserver = aObserver; 679 680 ReplayImageStatus(mCurrentRequest, aObserver); 681 ReplayImageStatus(mPendingRequest, aObserver); 682 683 return; 684 } 685 686 // otherwise we have to create a new entry 687 688 ImageObserver* observer = &mObserverList; 689 while (observer->mNext) { 690 observer = observer->mNext; 691 } 692 693 observer->mNext = new ImageObserver(aObserver); 694 ReplayImageStatus(mCurrentRequest, aObserver); 695 ReplayImageStatus(mPendingRequest, aObserver); 696 } 697 698 void nsImageLoadingContent::RemoveNativeObserver( 699 imgINotificationObserver* aObserver) { 700 if (NS_WARN_IF(!aObserver)) { 701 return; 702 } 703 704 if (mObserverList.mObserver == aObserver) { 705 mObserverList.mObserver = nullptr; 706 // Don't touch the linking of the list! 707 return; 708 } 709 710 // otherwise have to find it and splice it out 711 ImageObserver* observer = &mObserverList; 712 while (observer->mNext && observer->mNext->mObserver != aObserver) { 713 observer = observer->mNext; 714 } 715 716 // At this point, we are pointing to the list element whose mNext is 717 // the right observer (assuming of course that mNext is not null) 718 if (observer->mNext) { 719 // splice it out 720 ImageObserver* oldObserver = observer->mNext; 721 observer->mNext = oldObserver->mNext; 722 oldObserver->mNext = nullptr; // so we don't destroy them all 723 delete oldObserver; 724 } 725 #ifdef DEBUG 726 else { 727 NS_WARNING("Asked to remove nonexistent observer"); 728 } 729 #endif 730 } 731 732 void nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver) { 733 if (NS_WARN_IF(!aObserver)) { 734 return; 735 } 736 737 RefPtr<imgRequestProxy> currentReq; 738 if (mCurrentRequest) { 739 // Scripted observers may not belong to the same document as us, so when we 740 // create the imgRequestProxy, we shouldn't use any. This allows the request 741 // to dispatch notifications from the correct scheduler group. 742 nsresult rv = 743 mCurrentRequest->Clone(aObserver, nullptr, getter_AddRefs(currentReq)); 744 if (NS_FAILED(rv)) { 745 return; 746 } 747 } 748 749 RefPtr<imgRequestProxy> pendingReq; 750 if (mPendingRequest) { 751 // See above for why we don't use the loading document. 752 nsresult rv = 753 mPendingRequest->Clone(aObserver, nullptr, getter_AddRefs(pendingReq)); 754 if (NS_FAILED(rv)) { 755 mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); 756 return; 757 } 758 } 759 760 mScriptedObservers.AppendElement(new ScriptedImageObserver( 761 aObserver, std::move(currentReq), std::move(pendingReq))); 762 } 763 764 void nsImageLoadingContent::RemoveObserver( 765 imgINotificationObserver* aObserver) { 766 if (NS_WARN_IF(!aObserver)) { 767 return; 768 } 769 770 if (NS_WARN_IF(mScriptedObservers.IsEmpty())) { 771 return; 772 } 773 774 RefPtr<ScriptedImageObserver> observer; 775 auto i = mScriptedObservers.Length(); 776 do { 777 --i; 778 if (mScriptedObservers[i]->mObserver == aObserver) { 779 observer = std::move(mScriptedObservers[i]); 780 mScriptedObservers.RemoveElementAt(i); 781 break; 782 } 783 } while (i > 0); 784 785 if (NS_WARN_IF(!observer)) { 786 return; 787 } 788 789 // If the cancel causes a mutation, it will be harmless, because we have 790 // already removed the observer from the list. 791 observer->CancelRequests(); 792 } 793 794 void nsImageLoadingContent::ClearScriptedRequests(int32_t aRequestType, 795 nsresult aReason) { 796 if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) { 797 return; 798 } 799 800 nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone()); 801 auto i = observers.Length(); 802 do { 803 --i; 804 805 RefPtr<imgRequestProxy> req; 806 switch (aRequestType) { 807 case CURRENT_REQUEST: 808 req = std::move(observers[i]->mCurrentRequest); 809 break; 810 case PENDING_REQUEST: 811 req = std::move(observers[i]->mPendingRequest); 812 break; 813 default: 814 NS_ERROR("Unknown request type"); 815 return; 816 } 817 818 if (req) { 819 req->CancelAndForgetObserver(aReason); 820 } 821 } while (i > 0); 822 } 823 824 void nsImageLoadingContent::CloneScriptedRequests(imgRequestProxy* aRequest) { 825 MOZ_ASSERT(aRequest); 826 827 if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) { 828 return; 829 } 830 831 bool current; 832 if (aRequest == mCurrentRequest) { 833 current = true; 834 } else if (aRequest == mPendingRequest) { 835 current = false; 836 } else { 837 MOZ_ASSERT_UNREACHABLE("Unknown request type"); 838 return; 839 } 840 841 nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone()); 842 auto i = observers.Length(); 843 do { 844 --i; 845 846 ScriptedImageObserver* observer = observers[i]; 847 RefPtr<imgRequestProxy>& req = 848 current ? observer->mCurrentRequest : observer->mPendingRequest; 849 if (NS_WARN_IF(req)) { 850 MOZ_ASSERT_UNREACHABLE("Should have cancelled original request"); 851 req->CancelAndForgetObserver(NS_BINDING_ABORTED); 852 req = nullptr; 853 } 854 855 nsresult rv = 856 aRequest->Clone(observer->mObserver, nullptr, getter_AddRefs(req)); 857 (void)NS_WARN_IF(NS_FAILED(rv)); 858 } while (i > 0); 859 } 860 861 void nsImageLoadingContent::MakePendingScriptedRequestsCurrent() { 862 if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) { 863 return; 864 } 865 866 nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone()); 867 auto i = observers.Length(); 868 do { 869 --i; 870 871 ScriptedImageObserver* observer = observers[i]; 872 if (observer->mCurrentRequest) { 873 observer->mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); 874 } 875 observer->mCurrentRequest = std::move(observer->mPendingRequest); 876 } while (i > 0); 877 } 878 879 already_AddRefed<imgIRequest> nsImageLoadingContent::GetRequest( 880 int32_t aRequestType, ErrorResult& aError) { 881 nsCOMPtr<imgIRequest> request; 882 switch (aRequestType) { 883 case CURRENT_REQUEST: 884 request = mCurrentRequest; 885 break; 886 case PENDING_REQUEST: 887 request = mPendingRequest; 888 break; 889 default: 890 NS_ERROR("Unknown request type"); 891 aError.Throw(NS_ERROR_UNEXPECTED); 892 } 893 894 return request.forget(); 895 } 896 897 NS_IMETHODIMP 898 nsImageLoadingContent::GetRequest(int32_t aRequestType, 899 imgIRequest** aRequest) { 900 NS_ENSURE_ARG_POINTER(aRequest); 901 902 ErrorResult result; 903 *aRequest = GetRequest(aRequestType, result).take(); 904 905 return result.StealNSResult(); 906 } 907 908 NS_IMETHODIMP_(void) 909 nsImageLoadingContent::FrameCreated(nsIFrame* aFrame) { 910 MOZ_ASSERT(aFrame, "aFrame is null"); 911 MOZ_ASSERT(IsOurImageFrame(aFrame)); 912 913 MaybeForceSyncDecoding(/* aPrepareNextRequest */ false, aFrame); 914 TrackImage(mCurrentRequest, aFrame); 915 TrackImage(mPendingRequest, aFrame); 916 917 // We need to make sure that our image request is registered, if it should 918 // be registered. 919 nsPresContext* presContext = aFrame->PresContext(); 920 if (mCurrentRequest) { 921 nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mCurrentRequest, 922 &mCurrentRequestRegistered); 923 } 924 925 if (mPendingRequest) { 926 nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mPendingRequest, 927 &mPendingRequestRegistered); 928 } 929 } 930 931 NS_IMETHODIMP_(void) 932 nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame) { 933 NS_ASSERTION(aFrame, "aFrame is null"); 934 935 // We need to make sure that our image request is deregistered. 936 nsPresContext* presContext = GetFramePresContext(); 937 if (mCurrentRequest) { 938 nsLayoutUtils::DeregisterImageRequest(presContext, mCurrentRequest, 939 &mCurrentRequestRegistered); 940 } 941 942 if (mPendingRequest) { 943 nsLayoutUtils::DeregisterImageRequest(presContext, mPendingRequest, 944 &mPendingRequestRegistered); 945 } 946 947 UntrackImage(mCurrentRequest); 948 UntrackImage(mPendingRequest); 949 950 PresShell* presShell = presContext ? presContext->GetPresShell() : nullptr; 951 if (presShell) { 952 presShell->RemoveFrameFromApproximatelyVisibleList(aFrame); 953 } 954 } 955 956 /* static */ 957 nsContentPolicyType nsImageLoadingContent::PolicyTypeForLoad( 958 ImageLoadType aImageLoadType) { 959 if (aImageLoadType == eImageLoadType_Imageset) { 960 return nsIContentPolicy::TYPE_IMAGESET; 961 } 962 963 MOZ_ASSERT(aImageLoadType == eImageLoadType_Normal, 964 "Unknown ImageLoadType type in PolicyTypeForLoad"); 965 return nsIContentPolicy::TYPE_INTERNAL_IMAGE; 966 } 967 968 int32_t nsImageLoadingContent::GetRequestType(imgIRequest* aRequest, 969 ErrorResult& aError) { 970 if (aRequest == mCurrentRequest) { 971 return CURRENT_REQUEST; 972 } 973 974 if (aRequest == mPendingRequest) { 975 return PENDING_REQUEST; 976 } 977 978 NS_ERROR("Unknown request"); 979 aError.Throw(NS_ERROR_UNEXPECTED); 980 return UNKNOWN_REQUEST; 981 } 982 983 NS_IMETHODIMP 984 nsImageLoadingContent::GetRequestType(imgIRequest* aRequest, 985 int32_t* aRequestType) { 986 MOZ_ASSERT(aRequestType, "Null out param"); 987 988 ErrorResult result; 989 *aRequestType = GetRequestType(aRequest, result); 990 return result.StealNSResult(); 991 } 992 993 already_AddRefed<nsIURI> nsImageLoadingContent::GetCurrentURI() { 994 nsCOMPtr<nsIURI> uri; 995 if (mCurrentRequest) { 996 mCurrentRequest->GetURI(getter_AddRefs(uri)); 997 } else { 998 uri = mCurrentURI; 999 } 1000 1001 return uri.forget(); 1002 } 1003 1004 NS_IMETHODIMP 1005 nsImageLoadingContent::GetCurrentURI(nsIURI** aURI) { 1006 NS_ENSURE_ARG_POINTER(aURI); 1007 *aURI = GetCurrentURI().take(); 1008 return NS_OK; 1009 } 1010 1011 already_AddRefed<nsIURI> nsImageLoadingContent::GetCurrentRequestFinalURI() { 1012 nsCOMPtr<nsIURI> uri; 1013 if (mCurrentRequest) { 1014 mCurrentRequest->GetFinalURI(getter_AddRefs(uri)); 1015 } 1016 return uri.forget(); 1017 } 1018 1019 NS_IMETHODIMP 1020 nsImageLoadingContent::LoadImageWithChannel(nsIChannel* aChannel, 1021 nsIStreamListener** aListener) { 1022 imgLoader* loader = 1023 nsContentUtils::GetImgLoaderForChannel(aChannel, GetOurOwnerDoc()); 1024 if (!loader) { 1025 return NS_ERROR_NULL_POINTER; 1026 } 1027 1028 nsCOMPtr<Document> doc = GetOurOwnerDoc(); 1029 if (!doc) { 1030 // Don't bother 1031 *aListener = nullptr; 1032 return NS_OK; 1033 } 1034 1035 // XXX what should we do with content policies here, if anything? 1036 // Shouldn't that be done before the start of the load? 1037 // XXX what about shouldProcess? 1038 1039 // Our state might change. Watch it. 1040 auto updateStateOnExit = MakeScopeExit([&] { UpdateImageState(true); }); 1041 // Do the load. 1042 nsCOMPtr<nsIURI> uri; 1043 aChannel->GetOriginalURI(getter_AddRefs(uri)); 1044 RefPtr<imgRequestProxy>& req = PrepareNextRequest(eImageLoadType_Normal, uri); 1045 nsresult rv = loader->LoadImageWithChannel(aChannel, this, doc, aListener, 1046 getter_AddRefs(req)); 1047 if (NS_SUCCEEDED(rv)) { 1048 CloneScriptedRequests(req); 1049 TrackImage(req); 1050 return NS_OK; 1051 } 1052 1053 MOZ_ASSERT(!req, "Shouldn't have non-null request here"); 1054 // If we don't have a current URI, we might as well store this URI so people 1055 // know what we tried (and failed) to load. 1056 if (!mCurrentRequest) aChannel->GetURI(getter_AddRefs(mCurrentURI)); 1057 1058 FireEvent(u"error"_ns); 1059 return rv; 1060 } 1061 1062 void nsImageLoadingContent::ForceReload(bool aNotify, ErrorResult& aError) { 1063 nsCOMPtr<nsIURI> currentURI; 1064 GetCurrentURI(getter_AddRefs(currentURI)); 1065 if (!currentURI) { 1066 aError.Throw(NS_ERROR_NOT_AVAILABLE); 1067 return; 1068 } 1069 1070 // We keep this flag around along with the old URI even for failed requests 1071 // without a live request object 1072 ImageLoadType loadType = (mCurrentRequestFlags & REQUEST_IS_IMAGESET) 1073 ? eImageLoadType_Imageset 1074 : eImageLoadType_Normal; 1075 nsresult rv = LoadImage(currentURI, true, aNotify, loadType, 1076 nsIRequest::VALIDATE_ALWAYS | LoadFlags()); 1077 if (NS_FAILED(rv)) { 1078 aError.Throw(rv); 1079 } 1080 } 1081 1082 /* 1083 * Non-interface methods 1084 */ 1085 1086 nsresult nsImageLoadingContent::LoadImage(const nsAString& aNewURI, bool aForce, 1087 bool aNotify, 1088 ImageLoadType aImageLoadType, 1089 nsIPrincipal* aTriggeringPrincipal) { 1090 // First, get a document (needed for security checks and the like) 1091 Document* doc = GetOurOwnerDoc(); 1092 if (!doc) { 1093 // No reason to bother, I think... 1094 return NS_OK; 1095 } 1096 1097 // Parse the URI string to get image URI 1098 nsCOMPtr<nsIURI> imageURI; 1099 if (!aNewURI.IsEmpty()) { 1100 (void)StringToURI(aNewURI, doc, getter_AddRefs(imageURI)); 1101 } 1102 1103 return LoadImage(imageURI, aForce, aNotify, aImageLoadType, LoadFlags(), doc, 1104 aTriggeringPrincipal); 1105 } 1106 1107 nsresult nsImageLoadingContent::LoadImage(nsIURI* aNewURI, bool aForce, 1108 bool aNotify, 1109 ImageLoadType aImageLoadType, 1110 nsLoadFlags aLoadFlags, 1111 Document* aDocument, 1112 nsIPrincipal* aTriggeringPrincipal) { 1113 // Pending load/error events need to be canceled in some situations. This 1114 // is not documented in the spec, but can cause site compat problems if not 1115 // done. See bug 1309461 and https://github.com/whatwg/html/issues/1872. 1116 CancelPendingEvent(); 1117 1118 if (!aNewURI) { 1119 // Cancel image requests and then fire only error event per spec. 1120 CancelImageRequests(aNotify); 1121 if (aImageLoadType == eImageLoadType_Normal) { 1122 // Mark error event as cancelable only for src="" case, since only this 1123 // error causes site compat problem (bug 1308069) for now. 1124 FireEvent(u"error"_ns, true); 1125 } 1126 return NS_OK; 1127 } 1128 1129 if (!mLoadingEnabled) { 1130 // XXX Why fire an error here? seems like the callers to SetLoadingEnabled 1131 // don't want/need it. 1132 FireEvent(u"error"_ns); 1133 return NS_OK; 1134 } 1135 1136 NS_ASSERTION(!aDocument || aDocument == GetOurOwnerDoc(), 1137 "Bogus document passed in"); 1138 // First, get a document (needed for security checks and the like) 1139 if (!aDocument) { 1140 aDocument = GetOurOwnerDoc(); 1141 if (!aDocument) { 1142 // No reason to bother, I think... 1143 return NS_OK; 1144 } 1145 } 1146 1147 // Data documents, or documents from DOMParser shouldn't perform image 1148 // loading. 1149 // 1150 // FIXME(emilio): Shouldn't this check be part of 1151 // Document::ShouldLoadImages()? Or alternatively check ShouldLoadImages here 1152 // instead? (It seems we only check ShouldLoadImages in HTMLImageElement, 1153 // which seems wrong...) 1154 if (aDocument->IsLoadedAsData() && !aDocument->IsStaticDocument()) { 1155 // Clear our pending request if we do have one. 1156 ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages)); 1157 1158 FireEvent(u"error"_ns); 1159 return NS_OK; 1160 } 1161 1162 // URI equality check. 1163 // 1164 // We skip the equality check if we don't have a current image, since in that 1165 // case we really do want to try loading again. 1166 if (!aForce && mCurrentRequest) { 1167 nsCOMPtr<nsIURI> currentURI; 1168 GetCurrentURI(getter_AddRefs(currentURI)); 1169 bool equal; 1170 if (currentURI && NS_SUCCEEDED(currentURI->Equals(aNewURI, &equal)) && 1171 equal) { 1172 // Nothing to do here. 1173 return NS_OK; 1174 } 1175 } 1176 1177 // From this point on, our image state could change. Watch it. 1178 auto updateStateOnExit = MakeScopeExit([&] { UpdateImageState(aNotify); }); 1179 1180 // Sanity check. 1181 // 1182 // We use the principal of aDocument to avoid having to QI |this| an extra 1183 // time. It should always be the same as the principal of this node. 1184 Element* element = AsContent()->AsElement(); 1185 MOZ_ASSERT(element->NodePrincipal() == aDocument->NodePrincipal(), 1186 "Principal mismatch?"); 1187 1188 nsLoadFlags loadFlags = 1189 aLoadFlags | nsContentUtils::CORSModeToLoadImageFlags(GetCORSMode()); 1190 1191 RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType, aNewURI); 1192 1193 nsCOMPtr<nsIPrincipal> triggeringPrincipal; 1194 bool result = nsContentUtils::QueryTriggeringPrincipal( 1195 element, aTriggeringPrincipal, getter_AddRefs(triggeringPrincipal)); 1196 1197 // If result is true, which means this node has specified 1198 // 'triggeringprincipal' attribute on it, so we use favicon as the policy 1199 // type. 1200 nsContentPolicyType policyType = 1201 result ? nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON 1202 : PolicyTypeForLoad(aImageLoadType); 1203 1204 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*element); 1205 auto fetchPriority = GetFetchPriorityForImage(); 1206 nsresult rv = nsContentUtils::LoadImage( 1207 aNewURI, element, aDocument, triggeringPrincipal, 0, referrerInfo, this, 1208 loadFlags, element->LocalName(), getter_AddRefs(req), policyType, 1209 mUseUrgentStartForChannel, /* aLinkPreload */ false, 1210 /* aEarlyHintPreloaderId */ 0, fetchPriority); 1211 1212 if (fetchPriority != FetchPriority::Auto) { 1213 aDocument->SetPageloadEventFeature( 1214 performance::pageload_event::DocumentFeature::FETCH_PRIORITY_IMAGES); 1215 } 1216 1217 // Reset the flag to avoid loading from XPCOM or somewhere again else without 1218 // initiated by user interaction. 1219 mUseUrgentStartForChannel = false; 1220 1221 // Tell the document to forget about the image preload, if any, for 1222 // this URI, now that we might have another imgRequestProxy for it. 1223 // That way if we get canceled later the image load won't continue. 1224 aDocument->ForgetImagePreload(aNewURI); 1225 1226 if (NS_SUCCEEDED(rv)) { 1227 // Based on performance testing unsuppressing painting soon after the page 1228 // has gotten an image may improve visual metrics. 1229 if (Document* doc = element->GetComposedDoc()) { 1230 if (PresShell* shell = doc->GetPresShell()) { 1231 shell->TryUnsuppressPaintingSoon(); 1232 } 1233 } 1234 1235 CloneScriptedRequests(req); 1236 TrackImage(req); 1237 1238 // Handle cases when we just ended up with a request but it's already done. 1239 // In that situation we have to synchronously switch that request to being 1240 // the current request, because websites depend on that behavior. 1241 { 1242 uint32_t loadStatus; 1243 if (NS_SUCCEEDED(req->GetImageStatus(&loadStatus)) && 1244 (loadStatus & imgIRequest::STATUS_LOAD_COMPLETE)) { 1245 if (req == mPendingRequest) { 1246 MakePendingRequestCurrent(); 1247 } 1248 MOZ_ASSERT(mCurrentRequest, 1249 "How could we not have a current request here?"); 1250 1251 if (nsImageFrame* f = do_QueryFrame(GetOurPrimaryImageFrame())) { 1252 f->NotifyNewCurrentRequest(mCurrentRequest); 1253 } 1254 } 1255 } 1256 } else { 1257 MOZ_ASSERT(!req, "Shouldn't have non-null request here"); 1258 // If we don't have a current URI, we might as well store this URI so people 1259 // know what we tried (and failed) to load. 1260 if (!mCurrentRequest) { 1261 mCurrentURI = aNewURI; 1262 } 1263 1264 FireEvent(u"error"_ns); 1265 } 1266 1267 return NS_OK; 1268 } 1269 1270 already_AddRefed<Promise> nsImageLoadingContent::RecognizeCurrentImageText( 1271 ErrorResult& aRv) { 1272 using widget::TextRecognition; 1273 1274 if (!mCurrentRequest) { 1275 aRv.ThrowInvalidStateError("No current request"); 1276 return nullptr; 1277 } 1278 nsCOMPtr<imgIContainer> image; 1279 mCurrentRequest->GetImage(getter_AddRefs(image)); 1280 if (!image) { 1281 aRv.ThrowInvalidStateError("No image"); 1282 return nullptr; 1283 } 1284 1285 RefPtr<Promise> domPromise = 1286 Promise::Create(GetOurOwnerDoc()->GetScopeObject(), aRv); 1287 if (aRv.Failed()) { 1288 return nullptr; 1289 } 1290 1291 // The list of ISO 639-1 language tags to pass to the text recognition API. 1292 AutoTArray<nsCString, 4> languages; 1293 { 1294 // The document's locale should be the top language to use. Parse the BCP 47 1295 // locale and extract the ISO 639-1 language tag. e.g. "en-US" -> "en". 1296 nsAutoCString elementLanguage; 1297 nsAtom* imgLanguage = AsContent()->GetLang(); 1298 intl::Locale locale; 1299 if (imgLanguage) { 1300 imgLanguage->ToUTF8String(elementLanguage); 1301 auto result = intl::LocaleParser::TryParse(elementLanguage, locale); 1302 if (result.isOk()) { 1303 languages.AppendElement(locale.Language().Span()); 1304 } 1305 } 1306 } 1307 1308 { 1309 // The app locales should also be included after the document's locales. 1310 // Extract the language tag like above. 1311 nsTArray<nsCString> appLocales; 1312 intl::LocaleService::GetInstance()->GetAppLocalesAsBCP47(appLocales); 1313 1314 for (const auto& localeString : appLocales) { 1315 intl::Locale locale; 1316 auto result = intl::LocaleParser::TryParse(localeString, locale); 1317 if (result.isErr()) { 1318 NS_WARNING("Could not parse an app locale string, ignoring it."); 1319 continue; 1320 } 1321 languages.AppendElement(locale.Language().Span()); 1322 } 1323 } 1324 1325 TextRecognition::FindText(*image, languages) 1326 ->Then( 1327 GetCurrentSerialEventTarget(), __func__, 1328 [weak = RefPtr{do_GetWeakReference(this)}, 1329 request = RefPtr{mCurrentRequest}, domPromise]( 1330 TextRecognition::NativePromise::ResolveOrRejectValue&& aValue) { 1331 if (aValue.IsReject()) { 1332 domPromise->MaybeRejectWithNotSupportedError( 1333 aValue.RejectValue()); 1334 return; 1335 } 1336 RefPtr<nsIImageLoadingContent> iilc = do_QueryReferent(weak.get()); 1337 if (!iilc) { 1338 domPromise->MaybeRejectWithInvalidStateError( 1339 "Element was dead when we got the results"); 1340 return; 1341 } 1342 auto* ilc = static_cast<nsImageLoadingContent*>(iilc.get()); 1343 if (ilc->mCurrentRequest != request) { 1344 domPromise->MaybeRejectWithInvalidStateError( 1345 "Request not current"); 1346 return; 1347 } 1348 auto& textRecognitionResult = aValue.ResolveValue(); 1349 Element* el = ilc->AsContent()->AsElement(); 1350 1351 // When enabled, this feature will place the recognized text as 1352 // spans inside of the shadow dom of the img element. These are then 1353 // positioned so that the user can select the text. 1354 if (Preferences::GetBool("dom.text-recognition.shadow-dom-enabled", 1355 false)) { 1356 el->AttachAndSetUAShadowRoot(Element::NotifyUAWidgetSetup::Yes); 1357 TextRecognition::FillShadow(*el->GetShadowRoot(), 1358 textRecognitionResult); 1359 el->NotifyUAWidgetSetupOrChange(); 1360 } 1361 1362 nsTArray<ImageText> imageTexts( 1363 textRecognitionResult.quads().Length()); 1364 nsIGlobalObject* global = el->OwnerDoc()->GetOwnerGlobal(); 1365 1366 for (const auto& quad : textRecognitionResult.quads()) { 1367 NotNull<ImageText*> imageText = imageTexts.AppendElement(); 1368 1369 // Note: These points are not actually CSSPixels, but a DOMQuad is 1370 // a conveniently similar structure that can store these values. 1371 CSSPoint points[4]; 1372 points[0] = CSSPoint(quad.points()[0].x, quad.points()[0].y); 1373 points[1] = CSSPoint(quad.points()[1].x, quad.points()[1].y); 1374 points[2] = CSSPoint(quad.points()[2].x, quad.points()[2].y); 1375 points[3] = CSSPoint(quad.points()[3].x, quad.points()[3].y); 1376 1377 imageText->mQuad = new DOMQuad(global, points); 1378 imageText->mConfidence = quad.confidence(); 1379 imageText->mString = quad.string(); 1380 } 1381 domPromise->MaybeResolve(std::move(imageTexts)); 1382 }); 1383 return domPromise.forget(); 1384 } 1385 1386 CSSIntSize nsImageLoadingContent::NaturalSize( 1387 DoDensityCorrection aDensityCorrection) { 1388 if (!mCurrentRequest) { 1389 return {}; 1390 } 1391 1392 nsCOMPtr<imgIContainer> image; 1393 mCurrentRequest->GetImage(getter_AddRefs(image)); 1394 if (!image) { 1395 return {}; 1396 } 1397 1398 mozilla::image::ImageIntrinsicSize intrinsicSize; 1399 nsresult rv = image->GetIntrinsicSize(&intrinsicSize); 1400 if (NS_FAILED(rv)) { 1401 return {}; 1402 } 1403 1404 CSSIntSize size; // defaults to 0,0 1405 if (!StaticPrefs::image_natural_size_fallback_enabled()) { 1406 size.width = intrinsicSize.mWidth.valueOr(0); 1407 size.height = intrinsicSize.mHeight.valueOr(0); 1408 } else { 1409 // Fallback case, for web-compatibility! 1410 // See https://github.com/whatwg/html/issues/11287 and bug 1935269. 1411 // If we lack an intrinsic size in either axis, then use the fallback size, 1412 // unless we can transfer the size through the aspect ratio. 1413 // (And if we *only* have an intrinsic aspect ratio, use the fallback width 1414 // and transfer that through the aspect ratio to produce a height.) 1415 size.width = intrinsicSize.mWidth.valueOr(kFallbackIntrinsicWidthInPixels); 1416 size.height = 1417 intrinsicSize.mHeight.valueOr(kFallbackIntrinsicHeightInPixels); 1418 AspectRatio ratio = image->GetIntrinsicRatio(); 1419 if (ratio) { 1420 if (!intrinsicSize.mHeight) { 1421 // Compute the height from the width & ratio. (Note that the width we 1422 // use here might be kFallbackIntrinsicWidthInPixels, and that's fine.) 1423 size.height = ratio.Inverted().ApplyTo(size.width); 1424 } else if (!intrinsicSize.mWidth) { 1425 // Compute the width from the height & ratio. 1426 size.width = ratio.ApplyTo(size.height); 1427 } 1428 } 1429 } 1430 1431 ImageResolution resolution = image->GetResolution(); 1432 if (aDensityCorrection == DoDensityCorrection::Yes) { 1433 // NOTE(emilio): What we implement here matches the image-set() spec, but 1434 // it's unclear whether this is the right thing to do, see 1435 // https://github.com/whatwg/html/pull/5574#issuecomment-826335244. 1436 if (auto* image = HTMLImageElement::FromNode(AsContent())) { 1437 if (auto* sel = image->GetResponsiveImageSelector()) { 1438 float density = sel->GetSelectedImageDensity(); 1439 MOZ_ASSERT(density >= 0.0); 1440 resolution.ScaleBy(density); 1441 } 1442 } 1443 } 1444 1445 resolution.ApplyTo(size.width, size.height); 1446 return size; 1447 } 1448 1449 CSSIntSize nsImageLoadingContent::GetWidthHeightForImage() { 1450 Element* element = AsContent()->AsElement(); 1451 if (nsIFrame* frame = element->GetPrimaryFrame(FlushType::Layout)) { 1452 return CSSIntSize::FromAppUnitsRounded(frame->GetContentRect().Size()); 1453 } 1454 1455 CSSIntSize size; 1456 nsCOMPtr<imgIContainer> image; 1457 if (StaticPrefs::image_natural_size_fallback_enabled()) { 1458 // Our image is not rendered (we don't have any frame); so we should should 1459 // return the natural size, per: 1460 // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-width 1461 // 1462 // Note that the spec says to use the "density-corrected natural width and 1463 // height of the image", but we don't do that -- we specifically request 1464 // the NaturalSize *without* density-correction here. This handles a case 1465 // where browsers deviate from the spec in an interoperable way, which 1466 // hopefully we'll address in the spec soon. See case (2) in this comment 1467 // for more: 1468 // https://github.com/whatwg/html/issues/11287#issuecomment-2923467541 1469 size = NaturalSize(DoDensityCorrection::No); 1470 } else if (mCurrentRequest) { 1471 mCurrentRequest->GetImage(getter_AddRefs(image)); 1472 } 1473 1474 // If we have width or height attrs, we'll let those stomp on whatever 1475 // NaturalSize we may have gotten above. This handles a case where browsers 1476 // deviate from the spec in an interoperable way, which hopefully we'll 1477 // address in the spec soon. See case (1) in this comment for more: 1478 // https://github.com/whatwg/html/issues/11287#issuecomment-2923467541 1479 const nsAttrValue* value; 1480 if ((value = element->GetParsedAttr(nsGkAtoms::width)) && 1481 value->Type() == nsAttrValue::eInteger) { 1482 size.width = value->GetIntegerValue(); 1483 } else if (image) { 1484 image->GetWidth(&size.width); 1485 } 1486 1487 if ((value = element->GetParsedAttr(nsGkAtoms::height)) && 1488 value->Type() == nsAttrValue::eInteger) { 1489 size.height = value->GetIntegerValue(); 1490 } else if (image) { 1491 image->GetHeight(&size.height); 1492 } 1493 1494 NS_ASSERTION(size.width >= 0, "negative width"); 1495 NS_ASSERTION(size.height >= 0, "negative height"); 1496 return size; 1497 } 1498 1499 void nsImageLoadingContent::UpdateImageState(bool aNotify) { 1500 Element* thisElement = AsContent()->AsElement(); 1501 const bool isBroken = [&] { 1502 if (mLazyLoading || mPendingImageLoadTask) { 1503 return false; 1504 } 1505 if (!mCurrentRequest) { 1506 return true; 1507 } 1508 uint32_t currentLoadStatus; 1509 nsresult rv = mCurrentRequest->GetImageStatus(¤tLoadStatus); 1510 return NS_FAILED(rv) || currentLoadStatus & imgIRequest::STATUS_ERROR; 1511 }(); 1512 thisElement->SetStates(ElementState::BROKEN, isBroken, aNotify); 1513 if (isBroken) { 1514 RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN); 1515 } 1516 } 1517 1518 void nsImageLoadingContent::CancelImageRequests(bool aNotify) { 1519 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST); 1520 ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages)); 1521 ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages)); 1522 UpdateImageState(aNotify); 1523 } 1524 1525 Document* nsImageLoadingContent::GetOurOwnerDoc() { 1526 return AsContent()->OwnerDoc(); 1527 } 1528 1529 Document* nsImageLoadingContent::GetOurCurrentDoc() { 1530 return AsContent()->GetComposedDoc(); 1531 } 1532 1533 nsPresContext* nsImageLoadingContent::GetFramePresContext() { 1534 nsIFrame* frame = GetOurPrimaryImageFrame(); 1535 if (!frame) { 1536 return nullptr; 1537 } 1538 return frame->PresContext(); 1539 } 1540 1541 nsresult nsImageLoadingContent::StringToURI(const nsAString& aSpec, 1542 Document* aDocument, 1543 nsIURI** aURI) { 1544 MOZ_ASSERT(aDocument, "Must have a document"); 1545 MOZ_ASSERT(aURI, "Null out param"); 1546 1547 // (1) Get the base URI 1548 nsIContent* thisContent = AsContent(); 1549 nsIURI* baseURL = thisContent->GetBaseURI(); 1550 1551 // (2) Get the charset 1552 auto encoding = aDocument->GetDocumentCharacterSet(); 1553 1554 // (3) Construct the silly thing 1555 return NS_NewURI(aURI, aSpec, encoding, baseURL); 1556 } 1557 1558 nsresult nsImageLoadingContent::FireEvent(const nsAString& aEventType, 1559 bool aIsCancelable) { 1560 if (nsContentUtils::DocumentInactiveForImageLoads(GetOurOwnerDoc())) { 1561 // Don't bother to fire any events, especially error events. 1562 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT); 1563 return NS_OK; 1564 } 1565 1566 // We have to fire the event asynchronously so that we won't go into infinite 1567 // loops in cases when onLoad handlers reset the src and the new src is in 1568 // cache. 1569 1570 nsCOMPtr<nsINode> thisNode = AsContent(); 1571 1572 RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher = 1573 new LoadBlockingAsyncEventDispatcher(thisNode, aEventType, CanBubble::eNo, 1574 ChromeOnlyDispatch::eNo); 1575 loadBlockingAsyncDispatcher->PostDOMEvent(); 1576 1577 if (aIsCancelable) { 1578 mPendingEvent = loadBlockingAsyncDispatcher; 1579 } 1580 1581 return NS_OK; 1582 } 1583 1584 void nsImageLoadingContent::AsyncEventRunning(AsyncEventDispatcher* aEvent) { 1585 if (mPendingEvent == aEvent) { 1586 mPendingEvent = nullptr; 1587 } 1588 } 1589 1590 void nsImageLoadingContent::CancelPendingEvent() { 1591 if (mPendingEvent) { 1592 mPendingEvent->Cancel(); 1593 mPendingEvent = nullptr; 1594 } 1595 } 1596 1597 RefPtr<imgRequestProxy>& nsImageLoadingContent::PrepareNextRequest( 1598 ImageLoadType aImageLoadType, nsIURI* aNewURI) { 1599 MaybeForceSyncDecoding(/* aPrepareNextRequest */ true); 1600 1601 // We only want to cancel the existing current request if size is not 1602 // available. bz says the web depends on this behavior. 1603 // Otherwise, we get rid of any half-baked request that might be sitting there 1604 // and make this one current. 1605 return HaveSize(mCurrentRequest) 1606 ? PreparePendingRequest(aImageLoadType) 1607 : PrepareCurrentRequest(aImageLoadType, aNewURI); 1608 } 1609 1610 RefPtr<imgRequestProxy>& nsImageLoadingContent::PrepareCurrentRequest( 1611 ImageLoadType aImageLoadType, nsIURI* aNewURI) { 1612 if (mCurrentRequest) { 1613 MaybeAgeRequestGeneration(aNewURI); 1614 } 1615 // Get rid of anything that was there previously. 1616 ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages)); 1617 1618 if (aImageLoadType == eImageLoadType_Imageset) { 1619 mCurrentRequestFlags |= REQUEST_IS_IMAGESET; 1620 } 1621 1622 // Return a reference. 1623 return mCurrentRequest; 1624 } 1625 1626 RefPtr<imgRequestProxy>& nsImageLoadingContent::PreparePendingRequest( 1627 ImageLoadType aImageLoadType) { 1628 // Get rid of anything that was there previously. 1629 ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages)); 1630 1631 if (aImageLoadType == eImageLoadType_Imageset) { 1632 mPendingRequestFlags |= REQUEST_IS_IMAGESET; 1633 } 1634 1635 // Return a reference. 1636 return mPendingRequest; 1637 } 1638 1639 namespace { 1640 1641 class ImageRequestAutoLock { 1642 public: 1643 explicit ImageRequestAutoLock(imgIRequest* aRequest) : mRequest(aRequest) { 1644 if (mRequest) { 1645 mRequest->LockImage(); 1646 } 1647 } 1648 1649 ~ImageRequestAutoLock() { 1650 if (mRequest) { 1651 mRequest->UnlockImage(); 1652 } 1653 } 1654 1655 private: 1656 nsCOMPtr<imgIRequest> mRequest; 1657 }; 1658 1659 } // namespace 1660 1661 void nsImageLoadingContent::MakePendingRequestCurrent() { 1662 MOZ_ASSERT(mPendingRequest); 1663 1664 // If we have a pending request, we know that there is an existing current 1665 // request with size information. If the pending request is for a different 1666 // URI, then we need to reject any outstanding promises. 1667 nsCOMPtr<nsIURI> uri; 1668 mPendingRequest->GetURI(getter_AddRefs(uri)); 1669 1670 // Lock mCurrentRequest for the duration of this method. We do this because 1671 // PrepareCurrentRequest() might unlock mCurrentRequest. If mCurrentRequest 1672 // and mPendingRequest are both requests for the same image, unlocking 1673 // mCurrentRequest before we lock mPendingRequest can cause the lock count 1674 // to go to 0 and the image to be discarded! 1675 ImageRequestAutoLock autoLock(mCurrentRequest); 1676 1677 ImageLoadType loadType = (mPendingRequestFlags & REQUEST_IS_IMAGESET) 1678 ? eImageLoadType_Imageset 1679 : eImageLoadType_Normal; 1680 1681 PrepareCurrentRequest(loadType, uri) = mPendingRequest; 1682 MakePendingScriptedRequestsCurrent(); 1683 mPendingRequest = nullptr; 1684 mCurrentRequestFlags = mPendingRequestFlags; 1685 mPendingRequestFlags = 0; 1686 mCurrentRequestRegistered = mPendingRequestRegistered; 1687 mPendingRequestRegistered = false; 1688 } 1689 1690 void nsImageLoadingContent::ClearCurrentRequest( 1691 nsresult aReason, const Maybe<OnNonvisible>& aNonvisibleAction) { 1692 if (!mCurrentRequest) { 1693 // Even if we didn't have a current request, we might have been keeping 1694 // a URI and flags as a placeholder for a failed load. Clear that now. 1695 mCurrentURI = nullptr; 1696 mCurrentRequestFlags = 0; 1697 return; 1698 } 1699 MOZ_ASSERT(!mCurrentURI, 1700 "Shouldn't have both mCurrentRequest and mCurrentURI!"); 1701 1702 // Deregister this image from the refresh driver so it no longer receives 1703 // notifications. 1704 nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mCurrentRequest, 1705 &mCurrentRequestRegistered); 1706 1707 // Clean up the request. 1708 UntrackImage(mCurrentRequest, aNonvisibleAction); 1709 ClearScriptedRequests(CURRENT_REQUEST, aReason); 1710 mCurrentRequest->CancelAndForgetObserver(aReason); 1711 mCurrentRequest = nullptr; 1712 mCurrentRequestFlags = 0; 1713 } 1714 1715 void nsImageLoadingContent::ClearPendingRequest( 1716 nsresult aReason, const Maybe<OnNonvisible>& aNonvisibleAction) { 1717 if (!mPendingRequest) return; 1718 1719 // Deregister this image from the refresh driver so it no longer receives 1720 // notifications. 1721 nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest, 1722 &mPendingRequestRegistered); 1723 1724 UntrackImage(mPendingRequest, aNonvisibleAction); 1725 ClearScriptedRequests(PENDING_REQUEST, aReason); 1726 mPendingRequest->CancelAndForgetObserver(aReason); 1727 mPendingRequest = nullptr; 1728 mPendingRequestFlags = 0; 1729 } 1730 1731 bool nsImageLoadingContent::HaveSize(imgIRequest* aImage) { 1732 // Handle the null case 1733 if (!aImage) return false; 1734 1735 // Query the image 1736 uint32_t status; 1737 nsresult rv = aImage->GetImageStatus(&status); 1738 return (NS_SUCCEEDED(rv) && (status & imgIRequest::STATUS_SIZE_AVAILABLE)); 1739 } 1740 1741 void nsImageLoadingContent::NotifyOwnerDocumentActivityChanged() { 1742 if (!GetOurOwnerDoc()->IsCurrentActiveDocument()) { 1743 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT); 1744 } 1745 } 1746 1747 void nsImageLoadingContent::BindToTree(BindContext& aContext, 1748 nsINode& aParent) { 1749 // We may be getting connected, if so our image should be tracked, 1750 if (aContext.InComposedDoc()) { 1751 TrackImage(mCurrentRequest); 1752 TrackImage(mPendingRequest); 1753 } 1754 } 1755 1756 void nsImageLoadingContent::UnbindFromTree() { 1757 // We may be leaving the document, so if our image is tracked, untrack it. 1758 nsCOMPtr<Document> doc = GetOurCurrentDoc(); 1759 if (!doc) { 1760 return; 1761 } 1762 1763 UntrackImage(mCurrentRequest); 1764 UntrackImage(mPendingRequest); 1765 } 1766 1767 void nsImageLoadingContent::OnVisibilityChange( 1768 Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) { 1769 switch (aNewVisibility) { 1770 case Visibility::ApproximatelyVisible: 1771 TrackImage(mCurrentRequest); 1772 TrackImage(mPendingRequest); 1773 break; 1774 1775 case Visibility::ApproximatelyNonVisible: 1776 UntrackImage(mCurrentRequest, aNonvisibleAction); 1777 UntrackImage(mPendingRequest, aNonvisibleAction); 1778 break; 1779 1780 case Visibility::Untracked: 1781 MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility"); 1782 break; 1783 } 1784 } 1785 1786 void nsImageLoadingContent::TrackImage(imgIRequest* aImage, 1787 nsIFrame* aFrame /*= nullptr */) { 1788 if (!aImage) return; 1789 1790 MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest, 1791 "Why haven't we heard of this request?"); 1792 1793 Document* doc = GetOurCurrentDoc(); 1794 if (!doc) { 1795 return; 1796 } 1797 1798 if (!aFrame) { 1799 aFrame = GetOurPrimaryImageFrame(); 1800 } 1801 1802 /* This line is deceptively simple. It hides a lot of subtlety. Before we 1803 * create an nsImageFrame we call nsImageFrame::ShouldCreateImageFrameFor 1804 * to determine if we should create an nsImageFrame or create a frame based 1805 * on the display of the element (ie inline, block, etc). Inline, block, etc 1806 * frames don't register for visibility tracking so they will return UNTRACKED 1807 * from GetVisibility(). So this line is choosing to mark such images as 1808 * visible. Once the image loads we will get an nsImageFrame and the proper 1809 * visibility. This is a pitfall of tracking the visibility on the frames 1810 * instead of the content node. 1811 */ 1812 if (!aFrame || 1813 aFrame->GetVisibility() == Visibility::ApproximatelyNonVisible) { 1814 return; 1815 } 1816 1817 if (aImage == mCurrentRequest && 1818 !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) { 1819 mCurrentRequestFlags |= REQUEST_IS_TRACKED; 1820 doc->TrackImage(mCurrentRequest); 1821 } 1822 if (aImage == mPendingRequest && 1823 !(mPendingRequestFlags & REQUEST_IS_TRACKED)) { 1824 mPendingRequestFlags |= REQUEST_IS_TRACKED; 1825 doc->TrackImage(mPendingRequest); 1826 } 1827 } 1828 1829 void nsImageLoadingContent::UntrackImage( 1830 imgIRequest* aImage, const Maybe<OnNonvisible>& aNonvisibleAction 1831 /* = Nothing() */) { 1832 if (!aImage) return; 1833 1834 MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest, 1835 "Why haven't we heard of this request?"); 1836 1837 // We may not be in the document. If we outlived our document that's fine, 1838 // because the document empties out the tracker and unlocks all locked images 1839 // on destruction. But if we were never in the document we may need to force 1840 // discarding the image here, since this is the only chance we have. 1841 Document* doc = GetOurCurrentDoc(); 1842 if (aImage == mCurrentRequest) { 1843 if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) { 1844 mCurrentRequestFlags &= ~REQUEST_IS_TRACKED; 1845 doc->UntrackImage(mCurrentRequest, 1846 aNonvisibleAction == Some(OnNonvisible::DiscardImages) 1847 ? Document::RequestDiscard::Yes 1848 : Document::RequestDiscard::No); 1849 } else if (aNonvisibleAction == Some(OnNonvisible::DiscardImages)) { 1850 // If we're not in the document we may still need to be discarded. 1851 aImage->RequestDiscard(); 1852 } 1853 } 1854 if (aImage == mPendingRequest) { 1855 if (doc && (mPendingRequestFlags & REQUEST_IS_TRACKED)) { 1856 mPendingRequestFlags &= ~REQUEST_IS_TRACKED; 1857 doc->UntrackImage(mPendingRequest, 1858 aNonvisibleAction == Some(OnNonvisible::DiscardImages) 1859 ? Document::RequestDiscard::Yes 1860 : Document::RequestDiscard::No); 1861 } else if (aNonvisibleAction == Some(OnNonvisible::DiscardImages)) { 1862 // If we're not in the document we may still need to be discarded. 1863 aImage->RequestDiscard(); 1864 } 1865 } 1866 } 1867 1868 CORSMode nsImageLoadingContent::GetCORSMode() { return CORS_NONE; } 1869 1870 nsImageLoadingContent::ImageObserver::ImageObserver( 1871 imgINotificationObserver* aObserver) 1872 : mObserver(aObserver), mNext(nullptr) { 1873 MOZ_COUNT_CTOR(ImageObserver); 1874 } 1875 1876 nsImageLoadingContent::ImageObserver::~ImageObserver() { 1877 MOZ_COUNT_DTOR(ImageObserver); 1878 NS_CONTENT_DELETE_LIST_MEMBER(ImageObserver, this, mNext); 1879 } 1880 1881 nsImageLoadingContent::ScriptedImageObserver::ScriptedImageObserver( 1882 imgINotificationObserver* aObserver, 1883 RefPtr<imgRequestProxy>&& aCurrentRequest, 1884 RefPtr<imgRequestProxy>&& aPendingRequest) 1885 : mObserver(aObserver), 1886 mCurrentRequest(aCurrentRequest), 1887 mPendingRequest(aPendingRequest) {} 1888 1889 nsImageLoadingContent::ScriptedImageObserver::~ScriptedImageObserver() { 1890 // We should have cancelled any requests before getting released. 1891 DebugOnly<bool> cancel = CancelRequests(); 1892 MOZ_ASSERT(!cancel, "Still have requests in ~ScriptedImageObserver!"); 1893 } 1894 1895 bool nsImageLoadingContent::ScriptedImageObserver::CancelRequests() { 1896 bool cancelled = false; 1897 if (mCurrentRequest) { 1898 mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); 1899 mCurrentRequest = nullptr; 1900 cancelled = true; 1901 } 1902 if (mPendingRequest) { 1903 mPendingRequest->CancelAndForgetObserver(NS_BINDING_ABORTED); 1904 mPendingRequest = nullptr; 1905 cancelled = true; 1906 } 1907 return cancelled; 1908 } 1909 1910 Element* nsImageLoadingContent::FindImageMap() { 1911 return FindImageMap(AsContent()->AsElement()); 1912 } 1913 1914 /* static */ Element* nsImageLoadingContent::FindImageMap(Element* aElement) { 1915 nsAutoString useMap; 1916 aElement->GetAttr(nsGkAtoms::usemap, useMap); 1917 if (useMap.IsEmpty()) { 1918 return nullptr; 1919 } 1920 1921 nsAString::const_iterator start, end; 1922 useMap.BeginReading(start); 1923 useMap.EndReading(end); 1924 1925 int32_t hash = useMap.FindChar('#'); 1926 if (hash < 0) { 1927 return nullptr; 1928 } 1929 // useMap contains a '#', set start to point right after the '#' 1930 start.advance(hash + 1); 1931 1932 if (start == end) { 1933 return nullptr; // useMap == "#" 1934 } 1935 1936 RefPtr<nsContentList> imageMapList; 1937 if (aElement->IsInUncomposedDoc()) { 1938 // Optimize the common case and use document level image map. 1939 imageMapList = aElement->OwnerDoc()->ImageMapList(); 1940 } else { 1941 // Per HTML spec image map should be searched in the element's scope, 1942 // so using SubtreeRoot() here. 1943 // Because this is a temporary list, we don't need to make it live. 1944 imageMapList = 1945 new nsContentList(aElement->SubtreeRoot(), kNameSpaceID_XHTML, 1946 nsGkAtoms::map, nsGkAtoms::map, true, /* deep */ 1947 false /* live */); 1948 } 1949 1950 nsAutoString mapName(Substring(start, end)); 1951 1952 uint32_t i, n = imageMapList->Length(true); 1953 for (i = 0; i < n; ++i) { 1954 nsIContent* map = imageMapList->Item(i); 1955 if (map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, mapName, 1956 eCaseMatters) || 1957 map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, 1958 mapName, eCaseMatters)) { 1959 return map->AsElement(); 1960 } 1961 } 1962 1963 return nullptr; 1964 } 1965 1966 nsLoadFlags nsImageLoadingContent::LoadFlags() { 1967 auto* image = HTMLImageElement::FromNode(AsContent()); 1968 if (image && image->OwnerDoc()->IsScriptEnabled() && 1969 !image->OwnerDoc()->IsStaticDocument() && 1970 image->LoadingState() == Element::Loading::Lazy) { 1971 // Note that LOAD_BACKGROUND is not about priority of the load, but about 1972 // whether it blocks the load event (by bypassing the loadgroup). 1973 return nsIRequest::LOAD_BACKGROUND; 1974 } 1975 return nsIRequest::LOAD_NORMAL; 1976 } 1977 1978 FetchPriority nsImageLoadingContent::GetFetchPriorityForImage() const { 1979 return FetchPriority::Auto; 1980 }