Backed out changeset 9d8b4c0b99ed (bug 1945683) for causing btime failures. CLOSED...
[gecko.git] / dom / base / nsImageLoadingContent.cpp
blobf9f43c8c5305673eea73b17de0fab724ecda4e13
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/. */
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).
13 #include "nsImageLoadingContent.h"
14 #include "nsError.h"
15 #include "nsIContent.h"
16 #include "nsIScriptGlobalObject.h"
17 #include "nsServiceManagerUtils.h"
18 #include "nsContentList.h"
19 #include "nsContentPolicyUtils.h"
20 #include "nsIURI.h"
21 #include "imgIContainer.h"
22 #include "imgLoader.h"
23 #include "imgRequestProxy.h"
24 #include "nsThreadUtils.h"
25 #include "nsNetUtil.h"
26 #include "nsImageFrame.h"
28 #include "nsIChannel.h"
29 #include "nsIStreamListener.h"
31 #include "nsIFrame.h"
33 #include "nsContentUtils.h"
34 #include "nsLayoutUtils.h"
35 #include "nsIContentPolicy.h"
37 #include "mozAutoDocUpdate.h"
38 #include "mozilla/AsyncEventDispatcher.h"
39 #include "mozilla/AutoRestore.h"
40 #include "mozilla/CycleCollectedJSContext.h"
41 #include "mozilla/EventStateManager.h"
42 #include "mozilla/Preferences.h"
43 #include "mozilla/PresShell.h"
44 #include "mozilla/StaticPrefs_image.h"
45 #include "mozilla/SVGImageFrame.h"
46 #include "mozilla/SVGObserverUtils.h"
47 #include "mozilla/dom/BindContext.h"
48 #include "mozilla/dom/Document.h"
49 #include "mozilla/dom/Element.h"
50 #include "mozilla/dom/FetchPriority.h"
51 #include "mozilla/dom/PContent.h" // For TextRecognitionResult
52 #include "mozilla/dom/HTMLImageElement.h"
53 #include "mozilla/dom/ImageTextBinding.h"
54 #include "mozilla/dom/ImageTracker.h"
55 #include "mozilla/dom/PageLoadEventUtils.h"
56 #include "mozilla/dom/ReferrerInfo.h"
57 #include "mozilla/dom/ScriptSettings.h"
58 #include "mozilla/intl/LocaleService.h"
59 #include "mozilla/intl/Locale.h"
60 #include "mozilla/dom/LargestContentfulPaint.h"
61 #include "mozilla/net/UrlClassifierFeatureFactory.h"
62 #include "mozilla/widget/TextRecognition.h"
64 #include "Orientation.h"
66 #ifdef LoadImage
67 // Undefine LoadImage to prevent naming conflict with Windows.
68 # undef LoadImage
69 #endif
71 using namespace mozilla;
72 using namespace mozilla::dom;
74 #ifdef DEBUG_chb
75 static void PrintReqURL(imgIRequest* req) {
76 if (!req) {
77 printf("(null req)\n");
78 return;
81 nsCOMPtr<nsIURI> uri;
82 req->GetURI(getter_AddRefs(uri));
83 if (!uri) {
84 printf("(null uri)\n");
85 return;
88 nsAutoCString spec;
89 uri->GetSpec(spec);
90 printf("spec='%s'\n", spec.get());
92 #endif /* DEBUG_chb */
94 const nsAttrValue::EnumTable nsImageLoadingContent::kDecodingTable[] = {
95 {"auto", nsImageLoadingContent::ImageDecodingType::Auto},
96 {"async", nsImageLoadingContent::ImageDecodingType::Async},
97 {"sync", nsImageLoadingContent::ImageDecodingType::Sync},
98 {nullptr, 0}};
100 const nsAttrValue::EnumTable* nsImageLoadingContent::kDecodingTableDefault =
101 &nsImageLoadingContent::kDecodingTable[0];
103 nsImageLoadingContent::nsImageLoadingContent()
104 : mObserverList(nullptr),
105 mOutstandingDecodePromises(0),
106 mRequestGeneration(0),
107 mLoadingEnabled(true),
108 mUseUrgentStartForChannel(false),
109 mLazyLoading(false),
110 mHasPendingLoadTask(false),
111 mSyncDecodingHint(false),
112 mInDocResponsiveContent(false),
113 mCurrentRequestRegistered(false),
114 mPendingRequestRegistered(false) {
115 if (!nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
116 mLoadingEnabled = false;
119 mMostRecentRequestChange = TimeStamp::ProcessCreation();
122 void nsImageLoadingContent::Destroy() {
123 // Cancel our requests so they won't hold stale refs to us
124 // NB: Don't ask to discard the images here.
125 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
126 ClearCurrentRequest(NS_BINDING_ABORTED);
127 ClearPendingRequest(NS_BINDING_ABORTED);
130 nsImageLoadingContent::~nsImageLoadingContent() {
131 MOZ_ASSERT(!mCurrentRequest && !mPendingRequest, "Destroy not called");
132 MOZ_ASSERT(!mObserverList.mObserver && !mObserverList.mNext,
133 "Observers still registered?");
134 MOZ_ASSERT(mScriptedObservers.IsEmpty(),
135 "Scripted observers still registered?");
136 MOZ_ASSERT(mOutstandingDecodePromises == 0,
137 "Decode promises still unfulfilled?");
138 MOZ_ASSERT(mDecodePromises.IsEmpty(), "Decode promises still unfulfilled?");
142 * imgINotificationObserver impl
144 void nsImageLoadingContent::Notify(imgIRequest* aRequest, int32_t aType,
145 const nsIntRect* aData) {
146 MOZ_ASSERT(aRequest, "no request?");
147 MOZ_ASSERT(aRequest == mCurrentRequest || aRequest == mPendingRequest,
148 "Forgot to cancel a previous request?");
150 if (aType == imgINotificationObserver::IS_ANIMATED) {
151 return OnImageIsAnimated(aRequest);
154 if (aType == imgINotificationObserver::UNLOCKED_DRAW) {
155 return OnUnlockedDraw();
159 // Calling Notify on observers can modify the list of observers so make
160 // a local copy.
161 AutoTArray<nsCOMPtr<imgINotificationObserver>, 2> observers;
162 for (ImageObserver *observer = &mObserverList, *next; observer;
163 observer = next) {
164 next = observer->mNext;
165 if (observer->mObserver) {
166 observers.AppendElement(observer->mObserver);
170 nsAutoScriptBlocker scriptBlocker;
172 for (auto& observer : observers) {
173 observer->Notify(aRequest, aType, aData);
177 if (aType == imgINotificationObserver::LOAD_COMPLETE) {
178 uint32_t reqStatus;
179 aRequest->GetImageStatus(&reqStatus);
180 /* triage STATUS_ERROR */
181 if (reqStatus & imgIRequest::STATUS_ERROR) {
182 nsresult errorCode = NS_OK;
183 aRequest->GetImageErrorCode(&errorCode);
185 /* Handle image not loading error because source was a tracking URL (or
186 * fingerprinting, cryptomining, etc).
187 * We make a note of this image node by including it in a dedicated
188 * array of blocked tracking nodes under its parent document.
190 if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
191 errorCode)) {
192 Document* doc = GetOurOwnerDoc();
193 doc->AddBlockedNodeByClassifier(AsContent());
196 return OnLoadComplete(aRequest, reqStatus);
199 if ((aType == imgINotificationObserver::FRAME_COMPLETE ||
200 aType == imgINotificationObserver::FRAME_UPDATE) &&
201 mCurrentRequest == aRequest) {
202 MaybeResolveDecodePromises();
205 if (aType == imgINotificationObserver::DECODE_COMPLETE) {
206 nsCOMPtr<imgIContainer> container;
207 aRequest->GetImage(getter_AddRefs(container));
208 if (container) {
209 container->PropagateUseCounters(GetOurOwnerDoc());
211 UpdateImageState(true);
215 void nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest,
216 uint32_t aImageStatus) {
217 // XXXjdm This occurs when we have a pending request created, then another
218 // pending request replaces it before the first one is finished.
219 // This begs the question of what the correct behaviour is; we used
220 // to not have to care because we ran this code in OnStopDecode which
221 // wasn't called when the first request was cancelled. For now, I choose
222 // to punt when the given request doesn't appear to have terminated in
223 // an expected state.
224 if (!(aImageStatus &
225 (imgIRequest::STATUS_ERROR | imgIRequest::STATUS_LOAD_COMPLETE))) {
226 return;
229 // If the pending request is loaded, switch to it.
230 if (aRequest == mPendingRequest) {
231 MakePendingRequestCurrent();
233 MOZ_ASSERT(aRequest == mCurrentRequest,
234 "One way or another, we should be current by now");
236 // Fire the appropriate DOM event.
237 if (!(aImageStatus & imgIRequest::STATUS_ERROR)) {
238 FireEvent(u"load"_ns);
239 } else {
240 FireEvent(u"error"_ns);
243 Element* element = AsContent()->AsElement();
244 SVGObserverUtils::InvalidateDirectRenderingObservers(element);
245 MaybeResolveDecodePromises();
246 LargestContentfulPaint::MaybeProcessImageForElementTiming(mCurrentRequest,
247 element);
248 UpdateImageState(true);
251 void nsImageLoadingContent::OnUnlockedDraw() {
252 // This notification is only sent for animated images. It's OK for
253 // non-animated images to wait until the next frame visibility update to
254 // become locked. (And that's preferable, since in the case of scrolling it
255 // keeps memory usage minimal.)
257 // For animated images, though, we want to mark them visible right away so we
258 // can call IncrementAnimationConsumers() on them and they'll start animating.
260 nsIFrame* frame = GetOurPrimaryImageFrame();
261 if (!frame) {
262 return;
265 if (frame->GetVisibility() == Visibility::ApproximatelyVisible) {
266 // This frame is already marked visible; there's nothing to do.
267 return;
270 nsPresContext* presContext = frame->PresContext();
271 if (!presContext) {
272 return;
275 PresShell* presShell = presContext->GetPresShell();
276 if (!presShell) {
277 return;
280 presShell->EnsureFrameInApproximatelyVisibleList(frame);
283 void nsImageLoadingContent::OnImageIsAnimated(imgIRequest* aRequest) {
284 bool* requestFlag = nullptr;
285 if (aRequest == mCurrentRequest) {
286 requestFlag = &mCurrentRequestRegistered;
287 } else if (aRequest == mPendingRequest) {
288 requestFlag = &mPendingRequestRegistered;
289 } else {
290 MOZ_ASSERT_UNREACHABLE("Which image is this?");
291 return;
293 nsLayoutUtils::RegisterImageRequest(GetFramePresContext(), aRequest,
294 requestFlag);
297 static bool IsOurImageFrame(nsIFrame* aFrame) {
298 if (nsImageFrame* f = do_QueryFrame(aFrame)) {
299 return f->IsForImageLoadingContent();
301 return aFrame->IsSVGImageFrame() || aFrame->IsSVGFEImageFrame();
304 nsIFrame* nsImageLoadingContent::GetOurPrimaryImageFrame() {
305 nsIFrame* frame = AsContent()->GetPrimaryFrame();
306 if (!frame || !IsOurImageFrame(frame)) {
307 return nullptr;
309 return frame;
313 * nsIImageLoadingContent impl
316 void nsImageLoadingContent::SetLoadingEnabled(bool aLoadingEnabled) {
317 if (nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) {
318 mLoadingEnabled = aLoadingEnabled;
322 nsresult nsImageLoadingContent::GetSyncDecodingHint(bool* aHint) {
323 *aHint = mSyncDecodingHint;
324 return NS_OK;
327 already_AddRefed<Promise> nsImageLoadingContent::QueueDecodeAsync(
328 ErrorResult& aRv) {
329 Document* doc = GetOurOwnerDoc();
330 RefPtr<Promise> promise = Promise::Create(doc->GetScopeObject(), aRv);
331 if (aRv.Failed()) {
332 return nullptr;
335 class QueueDecodeTask final : public MicroTaskRunnable {
336 public:
337 QueueDecodeTask(nsImageLoadingContent* aOwner, Promise* aPromise,
338 uint32_t aRequestGeneration)
339 : mOwner(aOwner),
340 mPromise(aPromise),
341 mRequestGeneration(aRequestGeneration) {}
343 virtual void Run(AutoSlowOperation& aAso) override {
344 mOwner->DecodeAsync(std::move(mPromise), mRequestGeneration);
347 virtual bool Suppressed() override {
348 nsIGlobalObject* global = mOwner->GetOurOwnerDoc()->GetScopeObject();
349 return global && global->IsInSyncOperation();
352 private:
353 RefPtr<nsImageLoadingContent> mOwner;
354 RefPtr<Promise> mPromise;
355 uint32_t mRequestGeneration;
358 if (++mOutstandingDecodePromises == 1) {
359 MOZ_ASSERT(mDecodePromises.IsEmpty());
360 doc->RegisterActivityObserver(AsContent()->AsElement());
363 auto task = MakeRefPtr<QueueDecodeTask>(this, promise, mRequestGeneration);
364 CycleCollectedJSContext::Get()->DispatchToMicroTask(task.forget());
365 return promise.forget();
368 void nsImageLoadingContent::DecodeAsync(RefPtr<Promise>&& aPromise,
369 uint32_t aRequestGeneration) {
370 MOZ_ASSERT(aPromise);
371 MOZ_ASSERT(mOutstandingDecodePromises > mDecodePromises.Length());
373 // The request may have gotten updated since the decode call was issued.
374 if (aRequestGeneration != mRequestGeneration) {
375 aPromise->MaybeReject(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
376 // We never got placed in mDecodePromises, so we must ensure we decrement
377 // the counter explicitly.
378 --mOutstandingDecodePromises;
379 MaybeDeregisterActivityObserver();
380 return;
383 bool wasEmpty = mDecodePromises.IsEmpty();
384 mDecodePromises.AppendElement(std::move(aPromise));
385 if (wasEmpty) {
386 MaybeResolveDecodePromises();
390 void nsImageLoadingContent::MaybeResolveDecodePromises() {
391 if (mDecodePromises.IsEmpty()) {
392 return;
395 if (!mCurrentRequest) {
396 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
397 return;
400 // Only can resolve if our document is the active document. If not we are
401 // supposed to reject the promise, even if it was fulfilled successfully.
402 if (!GetOurOwnerDoc()->IsCurrentActiveDocument()) {
403 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT);
404 return;
407 // If any error occurred while decoding, we need to reject first.
408 uint32_t status = imgIRequest::STATUS_NONE;
409 mCurrentRequest->GetImageStatus(&status);
410 if (status & imgIRequest::STATUS_ERROR) {
411 RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN);
412 return;
415 // We need the size to bother with requesting a decode, as we are either
416 // blocked on validation or metadata decoding.
417 if (!(status & imgIRequest::STATUS_SIZE_AVAILABLE)) {
418 return;
421 // Check the surface cache status and/or request decoding begin. We do this
422 // before LOAD_COMPLETE because we want to start as soon as possible.
423 uint32_t flags = imgIContainer::FLAG_HIGH_QUALITY_SCALING |
424 imgIContainer::FLAG_AVOID_REDECODE_FOR_SIZE;
425 imgIContainer::DecodeResult decodeResult =
426 mCurrentRequest->RequestDecodeWithResult(flags);
427 if (decodeResult == imgIContainer::DECODE_REQUESTED) {
428 return;
430 if (decodeResult == imgIContainer::DECODE_REQUEST_FAILED) {
431 RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN);
432 return;
434 MOZ_ASSERT(decodeResult == imgIContainer::DECODE_SURFACE_AVAILABLE);
436 // We can only fulfill the promises once we have all the data.
437 if (!(status & imgIRequest::STATUS_LOAD_COMPLETE)) {
438 return;
441 for (auto& promise : mDecodePromises) {
442 promise->MaybeResolveWithUndefined();
445 MOZ_ASSERT(mOutstandingDecodePromises >= mDecodePromises.Length());
446 mOutstandingDecodePromises -= mDecodePromises.Length();
447 mDecodePromises.Clear();
448 MaybeDeregisterActivityObserver();
451 void nsImageLoadingContent::RejectDecodePromises(nsresult aStatus) {
452 if (mDecodePromises.IsEmpty()) {
453 return;
456 for (auto& promise : mDecodePromises) {
457 promise->MaybeReject(aStatus);
460 MOZ_ASSERT(mOutstandingDecodePromises >= mDecodePromises.Length());
461 mOutstandingDecodePromises -= mDecodePromises.Length();
462 mDecodePromises.Clear();
463 MaybeDeregisterActivityObserver();
466 void nsImageLoadingContent::MaybeAgeRequestGeneration(nsIURI* aNewURI) {
467 MOZ_ASSERT(mCurrentRequest);
469 // If the current request is about to change, we need to verify if the new
470 // URI matches the existing current request's URI. If it doesn't, we need to
471 // reject any outstanding promises due to the current request mutating as per
472 // step 2.2 of the decode API requirements.
474 // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-decode
475 if (aNewURI) {
476 nsCOMPtr<nsIURI> currentURI;
477 mCurrentRequest->GetURI(getter_AddRefs(currentURI));
479 bool equal = false;
480 if (NS_SUCCEEDED(aNewURI->Equals(currentURI, &equal)) && equal) {
481 return;
485 ++mRequestGeneration;
486 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
489 void nsImageLoadingContent::MaybeDeregisterActivityObserver() {
490 if (mOutstandingDecodePromises == 0) {
491 MOZ_ASSERT(mDecodePromises.IsEmpty());
492 GetOurOwnerDoc()->UnregisterActivityObserver(AsContent()->AsElement());
496 void nsImageLoadingContent::SetSyncDecodingHint(bool aHint) {
497 if (mSyncDecodingHint == aHint) {
498 return;
501 mSyncDecodingHint = aHint;
502 MaybeForceSyncDecoding(/* aPrepareNextRequest */ false);
505 void nsImageLoadingContent::MaybeForceSyncDecoding(
506 bool aPrepareNextRequest, nsIFrame* aFrame /* = nullptr */) {
507 // GetOurPrimaryImageFrame() might not return the frame during frame init.
508 nsIFrame* frame = aFrame ? aFrame : GetOurPrimaryImageFrame();
509 if (!frame) {
510 return;
513 bool forceSync = mSyncDecodingHint;
514 if (!forceSync && aPrepareNextRequest) {
515 // Detect JavaScript-based animations created by changing the |src|
516 // attribute on a timer.
517 TimeStamp now = TimeStamp::Now();
518 TimeDuration threshold = TimeDuration::FromMilliseconds(
519 StaticPrefs::image_infer_src_animation_threshold_ms());
521 // If the length of time between request changes is less than the threshold,
522 // then force sync decoding to eliminate flicker from the animation.
523 forceSync = (now - mMostRecentRequestChange < threshold);
524 mMostRecentRequestChange = now;
527 if (nsImageFrame* imageFrame = do_QueryFrame(frame)) {
528 imageFrame->SetForceSyncDecoding(forceSync);
529 } else if (SVGImageFrame* svgImageFrame = do_QueryFrame(frame)) {
530 svgImageFrame->SetForceSyncDecoding(forceSync);
534 static void ReplayImageStatus(imgIRequest* aRequest,
535 imgINotificationObserver* aObserver) {
536 if (!aRequest) {
537 return;
540 uint32_t status = 0;
541 nsresult rv = aRequest->GetImageStatus(&status);
542 if (NS_FAILED(rv)) {
543 return;
546 if (status & imgIRequest::STATUS_SIZE_AVAILABLE) {
547 aObserver->Notify(aRequest, imgINotificationObserver::SIZE_AVAILABLE,
548 nullptr);
550 if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
551 aObserver->Notify(aRequest, imgINotificationObserver::FRAME_COMPLETE,
552 nullptr);
554 if (status & imgIRequest::STATUS_HAS_TRANSPARENCY) {
555 aObserver->Notify(aRequest, imgINotificationObserver::HAS_TRANSPARENCY,
556 nullptr);
558 if (status & imgIRequest::STATUS_IS_ANIMATED) {
559 aObserver->Notify(aRequest, imgINotificationObserver::IS_ANIMATED, nullptr);
561 if (status & imgIRequest::STATUS_DECODE_COMPLETE) {
562 aObserver->Notify(aRequest, imgINotificationObserver::DECODE_COMPLETE,
563 nullptr);
565 if (status & imgIRequest::STATUS_LOAD_COMPLETE) {
566 aObserver->Notify(aRequest, imgINotificationObserver::LOAD_COMPLETE,
567 nullptr);
571 void nsImageLoadingContent::AddNativeObserver(
572 imgINotificationObserver* aObserver) {
573 if (NS_WARN_IF(!aObserver)) {
574 return;
577 if (!mObserverList.mObserver) {
578 // Don't touch the linking of the list!
579 mObserverList.mObserver = aObserver;
581 ReplayImageStatus(mCurrentRequest, aObserver);
582 ReplayImageStatus(mPendingRequest, aObserver);
584 return;
587 // otherwise we have to create a new entry
589 ImageObserver* observer = &mObserverList;
590 while (observer->mNext) {
591 observer = observer->mNext;
594 observer->mNext = new ImageObserver(aObserver);
595 ReplayImageStatus(mCurrentRequest, aObserver);
596 ReplayImageStatus(mPendingRequest, aObserver);
599 void nsImageLoadingContent::RemoveNativeObserver(
600 imgINotificationObserver* aObserver) {
601 if (NS_WARN_IF(!aObserver)) {
602 return;
605 if (mObserverList.mObserver == aObserver) {
606 mObserverList.mObserver = nullptr;
607 // Don't touch the linking of the list!
608 return;
611 // otherwise have to find it and splice it out
612 ImageObserver* observer = &mObserverList;
613 while (observer->mNext && observer->mNext->mObserver != aObserver) {
614 observer = observer->mNext;
617 // At this point, we are pointing to the list element whose mNext is
618 // the right observer (assuming of course that mNext is not null)
619 if (observer->mNext) {
620 // splice it out
621 ImageObserver* oldObserver = observer->mNext;
622 observer->mNext = oldObserver->mNext;
623 oldObserver->mNext = nullptr; // so we don't destroy them all
624 delete oldObserver;
626 #ifdef DEBUG
627 else {
628 NS_WARNING("Asked to remove nonexistent observer");
630 #endif
633 void nsImageLoadingContent::AddObserver(imgINotificationObserver* aObserver) {
634 if (NS_WARN_IF(!aObserver)) {
635 return;
638 RefPtr<imgRequestProxy> currentReq;
639 if (mCurrentRequest) {
640 // Scripted observers may not belong to the same document as us, so when we
641 // create the imgRequestProxy, we shouldn't use any. This allows the request
642 // to dispatch notifications from the correct scheduler group.
643 nsresult rv =
644 mCurrentRequest->Clone(aObserver, nullptr, getter_AddRefs(currentReq));
645 if (NS_FAILED(rv)) {
646 return;
650 RefPtr<imgRequestProxy> pendingReq;
651 if (mPendingRequest) {
652 // See above for why we don't use the loading document.
653 nsresult rv =
654 mPendingRequest->Clone(aObserver, nullptr, getter_AddRefs(pendingReq));
655 if (NS_FAILED(rv)) {
656 mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
657 return;
661 mScriptedObservers.AppendElement(new ScriptedImageObserver(
662 aObserver, std::move(currentReq), std::move(pendingReq)));
665 void nsImageLoadingContent::RemoveObserver(
666 imgINotificationObserver* aObserver) {
667 if (NS_WARN_IF(!aObserver)) {
668 return;
671 if (NS_WARN_IF(mScriptedObservers.IsEmpty())) {
672 return;
675 RefPtr<ScriptedImageObserver> observer;
676 auto i = mScriptedObservers.Length();
677 do {
678 --i;
679 if (mScriptedObservers[i]->mObserver == aObserver) {
680 observer = std::move(mScriptedObservers[i]);
681 mScriptedObservers.RemoveElementAt(i);
682 break;
684 } while (i > 0);
686 if (NS_WARN_IF(!observer)) {
687 return;
690 // If the cancel causes a mutation, it will be harmless, because we have
691 // already removed the observer from the list.
692 observer->CancelRequests();
695 void nsImageLoadingContent::ClearScriptedRequests(int32_t aRequestType,
696 nsresult aReason) {
697 if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) {
698 return;
701 nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone());
702 auto i = observers.Length();
703 do {
704 --i;
706 RefPtr<imgRequestProxy> req;
707 switch (aRequestType) {
708 case CURRENT_REQUEST:
709 req = std::move(observers[i]->mCurrentRequest);
710 break;
711 case PENDING_REQUEST:
712 req = std::move(observers[i]->mPendingRequest);
713 break;
714 default:
715 NS_ERROR("Unknown request type");
716 return;
719 if (req) {
720 req->CancelAndForgetObserver(aReason);
722 } while (i > 0);
725 void nsImageLoadingContent::CloneScriptedRequests(imgRequestProxy* aRequest) {
726 MOZ_ASSERT(aRequest);
728 if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) {
729 return;
732 bool current;
733 if (aRequest == mCurrentRequest) {
734 current = true;
735 } else if (aRequest == mPendingRequest) {
736 current = false;
737 } else {
738 MOZ_ASSERT_UNREACHABLE("Unknown request type");
739 return;
742 nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone());
743 auto i = observers.Length();
744 do {
745 --i;
747 ScriptedImageObserver* observer = observers[i];
748 RefPtr<imgRequestProxy>& req =
749 current ? observer->mCurrentRequest : observer->mPendingRequest;
750 if (NS_WARN_IF(req)) {
751 MOZ_ASSERT_UNREACHABLE("Should have cancelled original request");
752 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
753 req = nullptr;
756 nsresult rv =
757 aRequest->Clone(observer->mObserver, nullptr, getter_AddRefs(req));
758 Unused << NS_WARN_IF(NS_FAILED(rv));
759 } while (i > 0);
762 void nsImageLoadingContent::MakePendingScriptedRequestsCurrent() {
763 if (MOZ_LIKELY(mScriptedObservers.IsEmpty())) {
764 return;
767 nsTArray<RefPtr<ScriptedImageObserver>> observers(mScriptedObservers.Clone());
768 auto i = observers.Length();
769 do {
770 --i;
772 ScriptedImageObserver* observer = observers[i];
773 if (observer->mCurrentRequest) {
774 observer->mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
776 observer->mCurrentRequest = std::move(observer->mPendingRequest);
777 } while (i > 0);
780 already_AddRefed<imgIRequest> nsImageLoadingContent::GetRequest(
781 int32_t aRequestType, ErrorResult& aError) {
782 nsCOMPtr<imgIRequest> request;
783 switch (aRequestType) {
784 case CURRENT_REQUEST:
785 request = mCurrentRequest;
786 break;
787 case PENDING_REQUEST:
788 request = mPendingRequest;
789 break;
790 default:
791 NS_ERROR("Unknown request type");
792 aError.Throw(NS_ERROR_UNEXPECTED);
795 return request.forget();
798 NS_IMETHODIMP
799 nsImageLoadingContent::GetRequest(int32_t aRequestType,
800 imgIRequest** aRequest) {
801 NS_ENSURE_ARG_POINTER(aRequest);
803 ErrorResult result;
804 *aRequest = GetRequest(aRequestType, result).take();
806 return result.StealNSResult();
809 NS_IMETHODIMP_(void)
810 nsImageLoadingContent::FrameCreated(nsIFrame* aFrame) {
811 MOZ_ASSERT(aFrame, "aFrame is null");
812 MOZ_ASSERT(IsOurImageFrame(aFrame));
814 MaybeForceSyncDecoding(/* aPrepareNextRequest */ false, aFrame);
815 TrackImage(mCurrentRequest, aFrame);
816 TrackImage(mPendingRequest, aFrame);
818 // We need to make sure that our image request is registered, if it should
819 // be registered.
820 nsPresContext* presContext = aFrame->PresContext();
821 if (mCurrentRequest) {
822 nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mCurrentRequest,
823 &mCurrentRequestRegistered);
826 if (mPendingRequest) {
827 nsLayoutUtils::RegisterImageRequestIfAnimated(presContext, mPendingRequest,
828 &mPendingRequestRegistered);
832 NS_IMETHODIMP_(void)
833 nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame) {
834 NS_ASSERTION(aFrame, "aFrame is null");
836 // We need to make sure that our image request is deregistered.
837 nsPresContext* presContext = GetFramePresContext();
838 if (mCurrentRequest) {
839 nsLayoutUtils::DeregisterImageRequest(presContext, mCurrentRequest,
840 &mCurrentRequestRegistered);
843 if (mPendingRequest) {
844 nsLayoutUtils::DeregisterImageRequest(presContext, mPendingRequest,
845 &mPendingRequestRegistered);
848 UntrackImage(mCurrentRequest);
849 UntrackImage(mPendingRequest);
851 PresShell* presShell = presContext ? presContext->GetPresShell() : nullptr;
852 if (presShell) {
853 presShell->RemoveFrameFromApproximatelyVisibleList(aFrame);
857 /* static */
858 nsContentPolicyType nsImageLoadingContent::PolicyTypeForLoad(
859 ImageLoadType aImageLoadType) {
860 if (aImageLoadType == eImageLoadType_Imageset) {
861 return nsIContentPolicy::TYPE_IMAGESET;
864 MOZ_ASSERT(aImageLoadType == eImageLoadType_Normal,
865 "Unknown ImageLoadType type in PolicyTypeForLoad");
866 return nsIContentPolicy::TYPE_INTERNAL_IMAGE;
869 int32_t nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
870 ErrorResult& aError) {
871 if (aRequest == mCurrentRequest) {
872 return CURRENT_REQUEST;
875 if (aRequest == mPendingRequest) {
876 return PENDING_REQUEST;
879 NS_ERROR("Unknown request");
880 aError.Throw(NS_ERROR_UNEXPECTED);
881 return UNKNOWN_REQUEST;
884 NS_IMETHODIMP
885 nsImageLoadingContent::GetRequestType(imgIRequest* aRequest,
886 int32_t* aRequestType) {
887 MOZ_ASSERT(aRequestType, "Null out param");
889 ErrorResult result;
890 *aRequestType = GetRequestType(aRequest, result);
891 return result.StealNSResult();
894 already_AddRefed<nsIURI> nsImageLoadingContent::GetCurrentURI() {
895 nsCOMPtr<nsIURI> uri;
896 if (mCurrentRequest) {
897 mCurrentRequest->GetURI(getter_AddRefs(uri));
898 } else {
899 uri = mCurrentURI;
902 return uri.forget();
905 NS_IMETHODIMP
906 nsImageLoadingContent::GetCurrentURI(nsIURI** aURI) {
907 NS_ENSURE_ARG_POINTER(aURI);
908 *aURI = GetCurrentURI().take();
909 return NS_OK;
912 already_AddRefed<nsIURI> nsImageLoadingContent::GetCurrentRequestFinalURI() {
913 nsCOMPtr<nsIURI> uri;
914 if (mCurrentRequest) {
915 mCurrentRequest->GetFinalURI(getter_AddRefs(uri));
917 return uri.forget();
920 NS_IMETHODIMP
921 nsImageLoadingContent::LoadImageWithChannel(nsIChannel* aChannel,
922 nsIStreamListener** aListener) {
923 imgLoader* loader =
924 nsContentUtils::GetImgLoaderForChannel(aChannel, GetOurOwnerDoc());
925 if (!loader) {
926 return NS_ERROR_NULL_POINTER;
929 nsCOMPtr<Document> doc = GetOurOwnerDoc();
930 if (!doc) {
931 // Don't bother
932 *aListener = nullptr;
933 return NS_OK;
936 // XXX what should we do with content policies here, if anything?
937 // Shouldn't that be done before the start of the load?
938 // XXX what about shouldProcess?
940 // Our state might change. Watch it.
941 auto updateStateOnExit = MakeScopeExit([&] { UpdateImageState(true); });
942 // Do the load.
943 nsCOMPtr<nsIURI> uri;
944 aChannel->GetOriginalURI(getter_AddRefs(uri));
945 RefPtr<imgRequestProxy>& req = PrepareNextRequest(eImageLoadType_Normal, uri);
946 nsresult rv = loader->LoadImageWithChannel(aChannel, this, doc, aListener,
947 getter_AddRefs(req));
948 if (NS_SUCCEEDED(rv)) {
949 CloneScriptedRequests(req);
950 TrackImage(req);
951 return NS_OK;
954 MOZ_ASSERT(!req, "Shouldn't have non-null request here");
955 // If we don't have a current URI, we might as well store this URI so people
956 // know what we tried (and failed) to load.
957 if (!mCurrentRequest) aChannel->GetURI(getter_AddRefs(mCurrentURI));
959 FireEvent(u"error"_ns);
960 return rv;
963 void nsImageLoadingContent::ForceReload(bool aNotify, ErrorResult& aError) {
964 nsCOMPtr<nsIURI> currentURI;
965 GetCurrentURI(getter_AddRefs(currentURI));
966 if (!currentURI) {
967 aError.Throw(NS_ERROR_NOT_AVAILABLE);
968 return;
971 // We keep this flag around along with the old URI even for failed requests
972 // without a live request object
973 ImageLoadType loadType = (mCurrentRequestFlags & REQUEST_IS_IMAGESET)
974 ? eImageLoadType_Imageset
975 : eImageLoadType_Normal;
976 nsresult rv = LoadImage(currentURI, true, aNotify, loadType,
977 nsIRequest::VALIDATE_ALWAYS | LoadFlags());
978 if (NS_FAILED(rv)) {
979 aError.Throw(rv);
984 * Non-interface methods
987 nsresult nsImageLoadingContent::LoadImage(const nsAString& aNewURI, bool aForce,
988 bool aNotify,
989 ImageLoadType aImageLoadType,
990 nsIPrincipal* aTriggeringPrincipal) {
991 // First, get a document (needed for security checks and the like)
992 Document* doc = GetOurOwnerDoc();
993 if (!doc) {
994 // No reason to bother, I think...
995 return NS_OK;
998 // Parse the URI string to get image URI
999 nsCOMPtr<nsIURI> imageURI;
1000 if (!aNewURI.IsEmpty()) {
1001 Unused << StringToURI(aNewURI, doc, getter_AddRefs(imageURI));
1004 return LoadImage(imageURI, aForce, aNotify, aImageLoadType, LoadFlags(), doc,
1005 aTriggeringPrincipal);
1008 nsresult nsImageLoadingContent::LoadImage(nsIURI* aNewURI, bool aForce,
1009 bool aNotify,
1010 ImageLoadType aImageLoadType,
1011 nsLoadFlags aLoadFlags,
1012 Document* aDocument,
1013 nsIPrincipal* aTriggeringPrincipal) {
1014 // Pending load/error events need to be canceled in some situations. This
1015 // is not documented in the spec, but can cause site compat problems if not
1016 // done. See bug 1309461 and https://github.com/whatwg/html/issues/1872.
1017 CancelPendingEvent();
1019 if (!aNewURI) {
1020 // Cancel image requests and then fire only error event per spec.
1021 CancelImageRequests(aNotify);
1022 if (aImageLoadType == eImageLoadType_Normal) {
1023 // Mark error event as cancelable only for src="" case, since only this
1024 // error causes site compat problem (bug 1308069) for now.
1025 FireEvent(u"error"_ns, true);
1027 return NS_OK;
1030 if (!mLoadingEnabled) {
1031 // XXX Why fire an error here? seems like the callers to SetLoadingEnabled
1032 // don't want/need it.
1033 FireEvent(u"error"_ns);
1034 return NS_OK;
1037 NS_ASSERTION(!aDocument || aDocument == GetOurOwnerDoc(),
1038 "Bogus document passed in");
1039 // First, get a document (needed for security checks and the like)
1040 if (!aDocument) {
1041 aDocument = GetOurOwnerDoc();
1042 if (!aDocument) {
1043 // No reason to bother, I think...
1044 return NS_OK;
1048 // Data documents, or documents from DOMParser shouldn't perform image
1049 // loading.
1051 // FIXME(emilio): Shouldn't this check be part of
1052 // Document::ShouldLoadImages()? Or alternatively check ShouldLoadImages here
1053 // instead? (It seems we only check ShouldLoadImages in HTMLImageElement,
1054 // which seems wrong...)
1055 if (aDocument->IsLoadedAsData() && !aDocument->IsStaticDocument()) {
1056 // Clear our pending request if we do have one.
1057 ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
1059 FireEvent(u"error"_ns);
1060 return NS_OK;
1063 // URI equality check.
1065 // We skip the equality check if we don't have a current image, since in that
1066 // case we really do want to try loading again.
1067 if (!aForce && mCurrentRequest) {
1068 nsCOMPtr<nsIURI> currentURI;
1069 GetCurrentURI(getter_AddRefs(currentURI));
1070 bool equal;
1071 if (currentURI && NS_SUCCEEDED(currentURI->Equals(aNewURI, &equal)) &&
1072 equal) {
1073 // Nothing to do here.
1074 return NS_OK;
1078 // From this point on, our image state could change. Watch it.
1079 auto updateStateOnExit = MakeScopeExit([&] { UpdateImageState(aNotify); });
1081 // Sanity check.
1083 // We use the principal of aDocument to avoid having to QI |this| an extra
1084 // time. It should always be the same as the principal of this node.
1085 Element* element = AsContent()->AsElement();
1086 MOZ_ASSERT(element->NodePrincipal() == aDocument->NodePrincipal(),
1087 "Principal mismatch?");
1089 nsLoadFlags loadFlags =
1090 aLoadFlags | nsContentUtils::CORSModeToLoadImageFlags(GetCORSMode());
1092 RefPtr<imgRequestProxy>& req = PrepareNextRequest(aImageLoadType, aNewURI);
1094 nsCOMPtr<nsIPrincipal> triggeringPrincipal;
1095 bool result = nsContentUtils::QueryTriggeringPrincipal(
1096 element, aTriggeringPrincipal, getter_AddRefs(triggeringPrincipal));
1098 // If result is true, which means this node has specified
1099 // 'triggeringprincipal' attribute on it, so we use favicon as the policy
1100 // type.
1101 nsContentPolicyType policyType =
1102 result ? nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON
1103 : PolicyTypeForLoad(aImageLoadType);
1105 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*element);
1106 auto fetchPriority = GetFetchPriorityForImage();
1107 nsresult rv = nsContentUtils::LoadImage(
1108 aNewURI, element, aDocument, triggeringPrincipal, 0, referrerInfo, this,
1109 loadFlags, element->LocalName(), getter_AddRefs(req), policyType,
1110 mUseUrgentStartForChannel, /* aLinkPreload */ false,
1111 /* aEarlyHintPreloaderId */ 0, fetchPriority);
1113 if (fetchPriority != FetchPriority::Auto) {
1114 aDocument->SetPageloadEventFeature(
1115 pageload_event::FeatureBits::FETCH_PRIORITY_IMAGES);
1118 // Reset the flag to avoid loading from XPCOM or somewhere again else without
1119 // initiated by user interaction.
1120 mUseUrgentStartForChannel = false;
1122 // Tell the document to forget about the image preload, if any, for
1123 // this URI, now that we might have another imgRequestProxy for it.
1124 // That way if we get canceled later the image load won't continue.
1125 aDocument->ForgetImagePreload(aNewURI);
1127 if (NS_SUCCEEDED(rv)) {
1128 // Based on performance testing unsuppressing painting soon after the page
1129 // has gotten an image may improve visual metrics.
1130 if (Document* doc = element->GetComposedDoc()) {
1131 if (PresShell* shell = doc->GetPresShell()) {
1132 shell->TryUnsuppressPaintingSoon();
1136 CloneScriptedRequests(req);
1137 TrackImage(req);
1139 // Handle cases when we just ended up with a request but it's already done.
1140 // In that situation we have to synchronously switch that request to being
1141 // the current request, because websites depend on that behavior.
1143 uint32_t loadStatus;
1144 if (NS_SUCCEEDED(req->GetImageStatus(&loadStatus)) &&
1145 (loadStatus & imgIRequest::STATUS_LOAD_COMPLETE)) {
1146 if (req == mPendingRequest) {
1147 MakePendingRequestCurrent();
1149 MOZ_ASSERT(mCurrentRequest,
1150 "How could we not have a current request here?");
1152 if (nsImageFrame* f = do_QueryFrame(GetOurPrimaryImageFrame())) {
1153 f->NotifyNewCurrentRequest(mCurrentRequest);
1157 } else {
1158 MOZ_ASSERT(!req, "Shouldn't have non-null request here");
1159 // If we don't have a current URI, we might as well store this URI so people
1160 // know what we tried (and failed) to load.
1161 if (!mCurrentRequest) {
1162 mCurrentURI = aNewURI;
1165 FireEvent(u"error"_ns);
1168 return NS_OK;
1171 already_AddRefed<Promise> nsImageLoadingContent::RecognizeCurrentImageText(
1172 ErrorResult& aRv) {
1173 using widget::TextRecognition;
1175 if (!mCurrentRequest) {
1176 aRv.ThrowInvalidStateError("No current request");
1177 return nullptr;
1179 nsCOMPtr<imgIContainer> image;
1180 mCurrentRequest->GetImage(getter_AddRefs(image));
1181 if (!image) {
1182 aRv.ThrowInvalidStateError("No image");
1183 return nullptr;
1186 RefPtr<Promise> domPromise =
1187 Promise::Create(GetOurOwnerDoc()->GetScopeObject(), aRv);
1188 if (aRv.Failed()) {
1189 return nullptr;
1192 // The list of ISO 639-1 language tags to pass to the text recognition API.
1193 AutoTArray<nsCString, 4> languages;
1195 // The document's locale should be the top language to use. Parse the BCP 47
1196 // locale and extract the ISO 639-1 language tag. e.g. "en-US" -> "en".
1197 nsAutoCString elementLanguage;
1198 nsAtom* imgLanguage = AsContent()->GetLang();
1199 intl::Locale locale;
1200 if (imgLanguage) {
1201 imgLanguage->ToUTF8String(elementLanguage);
1202 auto result = intl::LocaleParser::TryParse(elementLanguage, locale);
1203 if (result.isOk()) {
1204 languages.AppendElement(locale.Language().Span());
1210 // The app locales should also be included after the document's locales.
1211 // Extract the language tag like above.
1212 nsTArray<nsCString> appLocales;
1213 intl::LocaleService::GetInstance()->GetAppLocalesAsBCP47(appLocales);
1215 for (const auto& localeString : appLocales) {
1216 intl::Locale locale;
1217 auto result = intl::LocaleParser::TryParse(localeString, locale);
1218 if (result.isErr()) {
1219 NS_WARNING("Could not parse an app locale string, ignoring it.");
1220 continue;
1222 languages.AppendElement(locale.Language().Span());
1226 TextRecognition::FindText(*image, languages)
1227 ->Then(
1228 GetCurrentSerialEventTarget(), __func__,
1229 [weak = RefPtr{do_GetWeakReference(this)},
1230 request = RefPtr{mCurrentRequest}, domPromise](
1231 TextRecognition::NativePromise::ResolveOrRejectValue&& aValue) {
1232 if (aValue.IsReject()) {
1233 domPromise->MaybeRejectWithNotSupportedError(
1234 aValue.RejectValue());
1235 return;
1237 RefPtr<nsIImageLoadingContent> iilc = do_QueryReferent(weak.get());
1238 if (!iilc) {
1239 domPromise->MaybeRejectWithInvalidStateError(
1240 "Element was dead when we got the results");
1241 return;
1243 auto* ilc = static_cast<nsImageLoadingContent*>(iilc.get());
1244 if (ilc->mCurrentRequest != request) {
1245 domPromise->MaybeRejectWithInvalidStateError(
1246 "Request not current");
1247 return;
1249 auto& textRecognitionResult = aValue.ResolveValue();
1250 Element* el = ilc->AsContent()->AsElement();
1252 // When enabled, this feature will place the recognized text as
1253 // spans inside of the shadow dom of the img element. These are then
1254 // positioned so that the user can select the text.
1255 if (Preferences::GetBool("dom.text-recognition.shadow-dom-enabled",
1256 false)) {
1257 el->AttachAndSetUAShadowRoot(Element::NotifyUAWidgetSetup::Yes);
1258 TextRecognition::FillShadow(*el->GetShadowRoot(),
1259 textRecognitionResult);
1260 el->NotifyUAWidgetSetupOrChange();
1263 nsTArray<ImageText> imageTexts(
1264 textRecognitionResult.quads().Length());
1265 nsIGlobalObject* global = el->OwnerDoc()->GetOwnerGlobal();
1267 for (const auto& quad : textRecognitionResult.quads()) {
1268 NotNull<ImageText*> imageText = imageTexts.AppendElement();
1270 // Note: These points are not actually CSSPixels, but a DOMQuad is
1271 // a conveniently similar structure that can store these values.
1272 CSSPoint points[4];
1273 points[0] = CSSPoint(quad.points()[0].x, quad.points()[0].y);
1274 points[1] = CSSPoint(quad.points()[1].x, quad.points()[1].y);
1275 points[2] = CSSPoint(quad.points()[2].x, quad.points()[2].y);
1276 points[3] = CSSPoint(quad.points()[3].x, quad.points()[3].y);
1278 imageText->mQuad = new DOMQuad(global, points);
1279 imageText->mConfidence = quad.confidence();
1280 imageText->mString = quad.string();
1282 domPromise->MaybeResolve(std::move(imageTexts));
1284 return domPromise.forget();
1287 CSSIntSize nsImageLoadingContent::GetWidthHeightForImage() {
1288 Element* element = AsContent()->AsElement();
1289 if (nsIFrame* frame = element->GetPrimaryFrame(FlushType::Layout)) {
1290 return CSSIntSize::FromAppUnitsRounded(frame->GetContentRect().Size());
1292 const nsAttrValue* value;
1293 nsCOMPtr<imgIContainer> image;
1294 if (mCurrentRequest) {
1295 mCurrentRequest->GetImage(getter_AddRefs(image));
1298 CSSIntSize size;
1299 if ((value = element->GetParsedAttr(nsGkAtoms::width)) &&
1300 value->Type() == nsAttrValue::eInteger) {
1301 size.width = value->GetIntegerValue();
1302 } else if (image) {
1303 image->GetWidth(&size.width);
1306 if ((value = element->GetParsedAttr(nsGkAtoms::height)) &&
1307 value->Type() == nsAttrValue::eInteger) {
1308 size.height = value->GetIntegerValue();
1309 } else if (image) {
1310 image->GetHeight(&size.height);
1313 NS_ASSERTION(size.width >= 0, "negative width");
1314 NS_ASSERTION(size.height >= 0, "negative height");
1315 return size;
1318 void nsImageLoadingContent::UpdateImageState(bool aNotify) {
1319 Element* thisElement = AsContent()->AsElement();
1320 const bool isBroken = [&] {
1321 if (mLazyLoading || mHasPendingLoadTask) {
1322 return false;
1324 if (!mCurrentRequest) {
1325 return true;
1327 uint32_t currentLoadStatus;
1328 nsresult rv = mCurrentRequest->GetImageStatus(&currentLoadStatus);
1329 return NS_FAILED(rv) || currentLoadStatus & imgIRequest::STATUS_ERROR;
1330 }();
1331 thisElement->SetStates(ElementState::BROKEN, isBroken, aNotify);
1332 if (isBroken) {
1333 RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN);
1337 void nsImageLoadingContent::CancelImageRequests(bool aNotify) {
1338 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST);
1339 ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
1340 ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
1341 UpdateImageState(aNotify);
1344 Document* nsImageLoadingContent::GetOurOwnerDoc() {
1345 return AsContent()->OwnerDoc();
1348 Document* nsImageLoadingContent::GetOurCurrentDoc() {
1349 return AsContent()->GetComposedDoc();
1352 nsPresContext* nsImageLoadingContent::GetFramePresContext() {
1353 nsIFrame* frame = GetOurPrimaryImageFrame();
1354 if (!frame) {
1355 return nullptr;
1357 return frame->PresContext();
1360 nsresult nsImageLoadingContent::StringToURI(const nsAString& aSpec,
1361 Document* aDocument,
1362 nsIURI** aURI) {
1363 MOZ_ASSERT(aDocument, "Must have a document");
1364 MOZ_ASSERT(aURI, "Null out param");
1366 // (1) Get the base URI
1367 nsIContent* thisContent = AsContent();
1368 nsIURI* baseURL = thisContent->GetBaseURI();
1370 // (2) Get the charset
1371 auto encoding = aDocument->GetDocumentCharacterSet();
1373 // (3) Construct the silly thing
1374 return NS_NewURI(aURI, aSpec, encoding, baseURL);
1377 nsresult nsImageLoadingContent::FireEvent(const nsAString& aEventType,
1378 bool aIsCancelable) {
1379 if (nsContentUtils::DocumentInactiveForImageLoads(GetOurOwnerDoc())) {
1380 // Don't bother to fire any events, especially error events.
1381 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT);
1382 return NS_OK;
1385 // We have to fire the event asynchronously so that we won't go into infinite
1386 // loops in cases when onLoad handlers reset the src and the new src is in
1387 // cache.
1389 nsCOMPtr<nsINode> thisNode = AsContent();
1391 RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
1392 new LoadBlockingAsyncEventDispatcher(thisNode, aEventType, CanBubble::eNo,
1393 ChromeOnlyDispatch::eNo);
1394 loadBlockingAsyncDispatcher->PostDOMEvent();
1396 if (aIsCancelable) {
1397 mPendingEvent = loadBlockingAsyncDispatcher;
1400 return NS_OK;
1403 void nsImageLoadingContent::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
1404 if (mPendingEvent == aEvent) {
1405 mPendingEvent = nullptr;
1409 void nsImageLoadingContent::CancelPendingEvent() {
1410 if (mPendingEvent) {
1411 mPendingEvent->Cancel();
1412 mPendingEvent = nullptr;
1416 RefPtr<imgRequestProxy>& nsImageLoadingContent::PrepareNextRequest(
1417 ImageLoadType aImageLoadType, nsIURI* aNewURI) {
1418 MaybeForceSyncDecoding(/* aPrepareNextRequest */ true);
1420 // We only want to cancel the existing current request if size is not
1421 // available. bz says the web depends on this behavior.
1422 // Otherwise, we get rid of any half-baked request that might be sitting there
1423 // and make this one current.
1424 return HaveSize(mCurrentRequest)
1425 ? PreparePendingRequest(aImageLoadType)
1426 : PrepareCurrentRequest(aImageLoadType, aNewURI);
1429 RefPtr<imgRequestProxy>& nsImageLoadingContent::PrepareCurrentRequest(
1430 ImageLoadType aImageLoadType, nsIURI* aNewURI) {
1431 if (mCurrentRequest) {
1432 MaybeAgeRequestGeneration(aNewURI);
1434 // Get rid of anything that was there previously.
1435 ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
1437 if (aImageLoadType == eImageLoadType_Imageset) {
1438 mCurrentRequestFlags |= REQUEST_IS_IMAGESET;
1441 // Return a reference.
1442 return mCurrentRequest;
1445 RefPtr<imgRequestProxy>& nsImageLoadingContent::PreparePendingRequest(
1446 ImageLoadType aImageLoadType) {
1447 // Get rid of anything that was there previously.
1448 ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DiscardImages));
1450 if (aImageLoadType == eImageLoadType_Imageset) {
1451 mPendingRequestFlags |= REQUEST_IS_IMAGESET;
1454 // Return a reference.
1455 return mPendingRequest;
1458 namespace {
1460 class ImageRequestAutoLock {
1461 public:
1462 explicit ImageRequestAutoLock(imgIRequest* aRequest) : mRequest(aRequest) {
1463 if (mRequest) {
1464 mRequest->LockImage();
1468 ~ImageRequestAutoLock() {
1469 if (mRequest) {
1470 mRequest->UnlockImage();
1474 private:
1475 nsCOMPtr<imgIRequest> mRequest;
1478 } // namespace
1480 void nsImageLoadingContent::MakePendingRequestCurrent() {
1481 MOZ_ASSERT(mPendingRequest);
1483 // If we have a pending request, we know that there is an existing current
1484 // request with size information. If the pending request is for a different
1485 // URI, then we need to reject any outstanding promises.
1486 nsCOMPtr<nsIURI> uri;
1487 mPendingRequest->GetURI(getter_AddRefs(uri));
1489 // Lock mCurrentRequest for the duration of this method. We do this because
1490 // PrepareCurrentRequest() might unlock mCurrentRequest. If mCurrentRequest
1491 // and mPendingRequest are both requests for the same image, unlocking
1492 // mCurrentRequest before we lock mPendingRequest can cause the lock count
1493 // to go to 0 and the image to be discarded!
1494 ImageRequestAutoLock autoLock(mCurrentRequest);
1496 ImageLoadType loadType = (mPendingRequestFlags & REQUEST_IS_IMAGESET)
1497 ? eImageLoadType_Imageset
1498 : eImageLoadType_Normal;
1500 PrepareCurrentRequest(loadType, uri) = mPendingRequest;
1501 MakePendingScriptedRequestsCurrent();
1502 mPendingRequest = nullptr;
1503 mCurrentRequestFlags = mPendingRequestFlags;
1504 mPendingRequestFlags = 0;
1505 mCurrentRequestRegistered = mPendingRequestRegistered;
1506 mPendingRequestRegistered = false;
1509 void nsImageLoadingContent::ClearCurrentRequest(
1510 nsresult aReason, const Maybe<OnNonvisible>& aNonvisibleAction) {
1511 if (!mCurrentRequest) {
1512 // Even if we didn't have a current request, we might have been keeping
1513 // a URI and flags as a placeholder for a failed load. Clear that now.
1514 mCurrentURI = nullptr;
1515 mCurrentRequestFlags = 0;
1516 return;
1518 MOZ_ASSERT(!mCurrentURI,
1519 "Shouldn't have both mCurrentRequest and mCurrentURI!");
1521 // Deregister this image from the refresh driver so it no longer receives
1522 // notifications.
1523 nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mCurrentRequest,
1524 &mCurrentRequestRegistered);
1526 // Clean up the request.
1527 UntrackImage(mCurrentRequest, aNonvisibleAction);
1528 ClearScriptedRequests(CURRENT_REQUEST, aReason);
1529 mCurrentRequest->CancelAndForgetObserver(aReason);
1530 mCurrentRequest = nullptr;
1531 mCurrentRequestFlags = 0;
1534 void nsImageLoadingContent::ClearPendingRequest(
1535 nsresult aReason, const Maybe<OnNonvisible>& aNonvisibleAction) {
1536 if (!mPendingRequest) return;
1538 // Deregister this image from the refresh driver so it no longer receives
1539 // notifications.
1540 nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest,
1541 &mPendingRequestRegistered);
1543 UntrackImage(mPendingRequest, aNonvisibleAction);
1544 ClearScriptedRequests(PENDING_REQUEST, aReason);
1545 mPendingRequest->CancelAndForgetObserver(aReason);
1546 mPendingRequest = nullptr;
1547 mPendingRequestFlags = 0;
1550 bool nsImageLoadingContent::HaveSize(imgIRequest* aImage) {
1551 // Handle the null case
1552 if (!aImage) return false;
1554 // Query the image
1555 uint32_t status;
1556 nsresult rv = aImage->GetImageStatus(&status);
1557 return (NS_SUCCEEDED(rv) && (status & imgIRequest::STATUS_SIZE_AVAILABLE));
1560 void nsImageLoadingContent::NotifyOwnerDocumentActivityChanged() {
1561 if (!GetOurOwnerDoc()->IsCurrentActiveDocument()) {
1562 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT);
1566 void nsImageLoadingContent::BindToTree(BindContext& aContext,
1567 nsINode& aParent) {
1568 // We may be getting connected, if so our image should be tracked,
1569 if (aContext.InComposedDoc()) {
1570 TrackImage(mCurrentRequest);
1571 TrackImage(mPendingRequest);
1575 void nsImageLoadingContent::UnbindFromTree() {
1576 // We may be leaving the document, so if our image is tracked, untrack it.
1577 nsCOMPtr<Document> doc = GetOurCurrentDoc();
1578 if (!doc) {
1579 return;
1582 UntrackImage(mCurrentRequest);
1583 UntrackImage(mPendingRequest);
1586 void nsImageLoadingContent::OnVisibilityChange(
1587 Visibility aNewVisibility, const Maybe<OnNonvisible>& aNonvisibleAction) {
1588 switch (aNewVisibility) {
1589 case Visibility::ApproximatelyVisible:
1590 TrackImage(mCurrentRequest);
1591 TrackImage(mPendingRequest);
1592 break;
1594 case Visibility::ApproximatelyNonVisible:
1595 UntrackImage(mCurrentRequest, aNonvisibleAction);
1596 UntrackImage(mPendingRequest, aNonvisibleAction);
1597 break;
1599 case Visibility::Untracked:
1600 MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
1601 break;
1605 void nsImageLoadingContent::TrackImage(imgIRequest* aImage,
1606 nsIFrame* aFrame /*= nullptr */) {
1607 if (!aImage) return;
1609 MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest,
1610 "Why haven't we heard of this request?");
1612 Document* doc = GetOurCurrentDoc();
1613 if (!doc) {
1614 return;
1617 if (!aFrame) {
1618 aFrame = GetOurPrimaryImageFrame();
1621 /* This line is deceptively simple. It hides a lot of subtlety. Before we
1622 * create an nsImageFrame we call nsImageFrame::ShouldCreateImageFrameFor
1623 * to determine if we should create an nsImageFrame or create a frame based
1624 * on the display of the element (ie inline, block, etc). Inline, block, etc
1625 * frames don't register for visibility tracking so they will return UNTRACKED
1626 * from GetVisibility(). So this line is choosing to mark such images as
1627 * visible. Once the image loads we will get an nsImageFrame and the proper
1628 * visibility. This is a pitfall of tracking the visibility on the frames
1629 * instead of the content node.
1631 if (!aFrame ||
1632 aFrame->GetVisibility() == Visibility::ApproximatelyNonVisible) {
1633 return;
1636 if (aImage == mCurrentRequest &&
1637 !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
1638 mCurrentRequestFlags |= REQUEST_IS_TRACKED;
1639 doc->ImageTracker()->Add(mCurrentRequest);
1641 if (aImage == mPendingRequest &&
1642 !(mPendingRequestFlags & REQUEST_IS_TRACKED)) {
1643 mPendingRequestFlags |= REQUEST_IS_TRACKED;
1644 doc->ImageTracker()->Add(mPendingRequest);
1648 void nsImageLoadingContent::UntrackImage(
1649 imgIRequest* aImage, const Maybe<OnNonvisible>& aNonvisibleAction
1650 /* = Nothing() */) {
1651 if (!aImage) return;
1653 MOZ_ASSERT(aImage == mCurrentRequest || aImage == mPendingRequest,
1654 "Why haven't we heard of this request?");
1656 // We may not be in the document. If we outlived our document that's fine,
1657 // because the document empties out the tracker and unlocks all locked images
1658 // on destruction. But if we were never in the document we may need to force
1659 // discarding the image here, since this is the only chance we have.
1660 Document* doc = GetOurCurrentDoc();
1661 if (aImage == mCurrentRequest) {
1662 if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
1663 mCurrentRequestFlags &= ~REQUEST_IS_TRACKED;
1664 doc->ImageTracker()->Remove(
1665 mCurrentRequest,
1666 aNonvisibleAction == Some(OnNonvisible::DiscardImages)
1667 ? ImageTracker::REQUEST_DISCARD
1668 : 0);
1669 } else if (aNonvisibleAction == Some(OnNonvisible::DiscardImages)) {
1670 // If we're not in the document we may still need to be discarded.
1671 aImage->RequestDiscard();
1674 if (aImage == mPendingRequest) {
1675 if (doc && (mPendingRequestFlags & REQUEST_IS_TRACKED)) {
1676 mPendingRequestFlags &= ~REQUEST_IS_TRACKED;
1677 doc->ImageTracker()->Remove(
1678 mPendingRequest,
1679 aNonvisibleAction == Some(OnNonvisible::DiscardImages)
1680 ? ImageTracker::REQUEST_DISCARD
1681 : 0);
1682 } else if (aNonvisibleAction == Some(OnNonvisible::DiscardImages)) {
1683 // If we're not in the document we may still need to be discarded.
1684 aImage->RequestDiscard();
1689 CORSMode nsImageLoadingContent::GetCORSMode() { return CORS_NONE; }
1691 nsImageLoadingContent::ImageObserver::ImageObserver(
1692 imgINotificationObserver* aObserver)
1693 : mObserver(aObserver), mNext(nullptr) {
1694 MOZ_COUNT_CTOR(ImageObserver);
1697 nsImageLoadingContent::ImageObserver::~ImageObserver() {
1698 MOZ_COUNT_DTOR(ImageObserver);
1699 NS_CONTENT_DELETE_LIST_MEMBER(ImageObserver, this, mNext);
1702 nsImageLoadingContent::ScriptedImageObserver::ScriptedImageObserver(
1703 imgINotificationObserver* aObserver,
1704 RefPtr<imgRequestProxy>&& aCurrentRequest,
1705 RefPtr<imgRequestProxy>&& aPendingRequest)
1706 : mObserver(aObserver),
1707 mCurrentRequest(aCurrentRequest),
1708 mPendingRequest(aPendingRequest) {}
1710 nsImageLoadingContent::ScriptedImageObserver::~ScriptedImageObserver() {
1711 // We should have cancelled any requests before getting released.
1712 DebugOnly<bool> cancel = CancelRequests();
1713 MOZ_ASSERT(!cancel, "Still have requests in ~ScriptedImageObserver!");
1716 bool nsImageLoadingContent::ScriptedImageObserver::CancelRequests() {
1717 bool cancelled = false;
1718 if (mCurrentRequest) {
1719 mCurrentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
1720 mCurrentRequest = nullptr;
1721 cancelled = true;
1723 if (mPendingRequest) {
1724 mPendingRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
1725 mPendingRequest = nullptr;
1726 cancelled = true;
1728 return cancelled;
1731 Element* nsImageLoadingContent::FindImageMap() {
1732 return FindImageMap(AsContent()->AsElement());
1735 /* static */ Element* nsImageLoadingContent::FindImageMap(Element* aElement) {
1736 nsAutoString useMap;
1737 aElement->GetAttr(nsGkAtoms::usemap, useMap);
1738 if (useMap.IsEmpty()) {
1739 return nullptr;
1742 nsAString::const_iterator start, end;
1743 useMap.BeginReading(start);
1744 useMap.EndReading(end);
1746 int32_t hash = useMap.FindChar('#');
1747 if (hash < 0) {
1748 return nullptr;
1750 // useMap contains a '#', set start to point right after the '#'
1751 start.advance(hash + 1);
1753 if (start == end) {
1754 return nullptr; // useMap == "#"
1757 RefPtr<nsContentList> imageMapList;
1758 if (aElement->IsInUncomposedDoc()) {
1759 // Optimize the common case and use document level image map.
1760 imageMapList = aElement->OwnerDoc()->ImageMapList();
1761 } else {
1762 // Per HTML spec image map should be searched in the element's scope,
1763 // so using SubtreeRoot() here.
1764 // Because this is a temporary list, we don't need to make it live.
1765 imageMapList =
1766 new nsContentList(aElement->SubtreeRoot(), kNameSpaceID_XHTML,
1767 nsGkAtoms::map, nsGkAtoms::map, true, /* deep */
1768 false /* live */);
1771 nsAutoString mapName(Substring(start, end));
1773 uint32_t i, n = imageMapList->Length(true);
1774 for (i = 0; i < n; ++i) {
1775 nsIContent* map = imageMapList->Item(i);
1776 if (map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, mapName,
1777 eCaseMatters) ||
1778 map->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
1779 mapName, eCaseMatters)) {
1780 return map->AsElement();
1784 return nullptr;
1787 nsLoadFlags nsImageLoadingContent::LoadFlags() {
1788 auto* image = HTMLImageElement::FromNode(AsContent());
1789 if (image && image->OwnerDoc()->IsScriptEnabled() &&
1790 !image->OwnerDoc()->IsStaticDocument() &&
1791 image->LoadingState() == Element::Loading::Lazy) {
1792 // Note that LOAD_BACKGROUND is not about priority of the load, but about
1793 // whether it blocks the load event (by bypassing the loadgroup).
1794 return nsIRequest::LOAD_BACKGROUND;
1796 return nsIRequest::LOAD_NORMAL;
1799 FetchPriority nsImageLoadingContent::GetFetchPriorityForImage() const {
1800 return FetchPriority::Auto;