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/. */
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"
15 #include "nsIContent.h"
16 #include "nsIScriptGlobalObject.h"
17 #include "nsServiceManagerUtils.h"
18 #include "nsContentList.h"
19 #include "nsContentPolicyUtils.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"
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"
67 // Undefine LoadImage to prevent naming conflict with Windows.
71 using namespace mozilla
;
72 using namespace mozilla::dom
;
75 static void PrintReqURL(imgIRequest
* req
) {
77 printf("(null req)\n");
82 req
->GetURI(getter_AddRefs(uri
));
84 printf("(null uri)\n");
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
},
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),
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
161 AutoTArray
<nsCOMPtr
<imgINotificationObserver
>, 2> observers
;
162 for (ImageObserver
*observer
= &mObserverList
, *next
; observer
;
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
) {
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(
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
));
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.
225 (imgIRequest::STATUS_ERROR
| imgIRequest::STATUS_LOAD_COMPLETE
))) {
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
);
240 FireEvent(u
"error"_ns
);
243 Element
* element
= AsContent()->AsElement();
244 SVGObserverUtils::InvalidateDirectRenderingObservers(element
);
245 MaybeResolveDecodePromises();
246 LargestContentfulPaint::MaybeProcessImageForElementTiming(mCurrentRequest
,
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();
265 if (frame
->GetVisibility() == Visibility::ApproximatelyVisible
) {
266 // This frame is already marked visible; there's nothing to do.
270 nsPresContext
* presContext
= frame
->PresContext();
275 PresShell
* presShell
= presContext
->GetPresShell();
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
;
290 MOZ_ASSERT_UNREACHABLE("Which image is this?");
293 nsLayoutUtils::RegisterImageRequest(GetFramePresContext(), aRequest
,
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
)) {
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
;
327 already_AddRefed
<Promise
> nsImageLoadingContent::QueueDecodeAsync(
329 Document
* doc
= GetOurOwnerDoc();
330 RefPtr
<Promise
> promise
= Promise::Create(doc
->GetScopeObject(), aRv
);
335 class QueueDecodeTask final
: public MicroTaskRunnable
{
337 QueueDecodeTask(nsImageLoadingContent
* aOwner
, Promise
* aPromise
,
338 uint32_t aRequestGeneration
)
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();
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();
383 bool wasEmpty
= mDecodePromises
.IsEmpty();
384 mDecodePromises
.AppendElement(std::move(aPromise
));
386 MaybeResolveDecodePromises();
390 void nsImageLoadingContent::MaybeResolveDecodePromises() {
391 if (mDecodePromises
.IsEmpty()) {
395 if (!mCurrentRequest
) {
396 RejectDecodePromises(NS_ERROR_DOM_IMAGE_INVALID_REQUEST
);
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
);
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
);
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
)) {
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
) {
430 if (decodeResult
== imgIContainer::DECODE_REQUEST_FAILED
) {
431 RejectDecodePromises(NS_ERROR_DOM_IMAGE_BROKEN
);
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
)) {
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()) {
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
476 nsCOMPtr
<nsIURI
> currentURI
;
477 mCurrentRequest
->GetURI(getter_AddRefs(currentURI
));
480 if (NS_SUCCEEDED(aNewURI
->Equals(currentURI
, &equal
)) && equal
) {
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
) {
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();
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
) {
541 nsresult rv
= aRequest
->GetImageStatus(&status
);
546 if (status
& imgIRequest::STATUS_SIZE_AVAILABLE
) {
547 aObserver
->Notify(aRequest
, imgINotificationObserver::SIZE_AVAILABLE
,
550 if (status
& imgIRequest::STATUS_FRAME_COMPLETE
) {
551 aObserver
->Notify(aRequest
, imgINotificationObserver::FRAME_COMPLETE
,
554 if (status
& imgIRequest::STATUS_HAS_TRANSPARENCY
) {
555 aObserver
->Notify(aRequest
, imgINotificationObserver::HAS_TRANSPARENCY
,
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
,
565 if (status
& imgIRequest::STATUS_LOAD_COMPLETE
) {
566 aObserver
->Notify(aRequest
, imgINotificationObserver::LOAD_COMPLETE
,
571 void nsImageLoadingContent::AddNativeObserver(
572 imgINotificationObserver
* aObserver
) {
573 if (NS_WARN_IF(!aObserver
)) {
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
);
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
)) {
605 if (mObserverList
.mObserver
== aObserver
) {
606 mObserverList
.mObserver
= nullptr;
607 // Don't touch the linking of the list!
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
) {
621 ImageObserver
* oldObserver
= observer
->mNext
;
622 observer
->mNext
= oldObserver
->mNext
;
623 oldObserver
->mNext
= nullptr; // so we don't destroy them all
628 NS_WARNING("Asked to remove nonexistent observer");
633 void nsImageLoadingContent::AddObserver(imgINotificationObserver
* aObserver
) {
634 if (NS_WARN_IF(!aObserver
)) {
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.
644 mCurrentRequest
->Clone(aObserver
, nullptr, getter_AddRefs(currentReq
));
650 RefPtr
<imgRequestProxy
> pendingReq
;
651 if (mPendingRequest
) {
652 // See above for why we don't use the loading document.
654 mPendingRequest
->Clone(aObserver
, nullptr, getter_AddRefs(pendingReq
));
656 mCurrentRequest
->CancelAndForgetObserver(NS_BINDING_ABORTED
);
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
)) {
671 if (NS_WARN_IF(mScriptedObservers
.IsEmpty())) {
675 RefPtr
<ScriptedImageObserver
> observer
;
676 auto i
= mScriptedObservers
.Length();
679 if (mScriptedObservers
[i
]->mObserver
== aObserver
) {
680 observer
= std::move(mScriptedObservers
[i
]);
681 mScriptedObservers
.RemoveElementAt(i
);
686 if (NS_WARN_IF(!observer
)) {
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
,
697 if (MOZ_LIKELY(mScriptedObservers
.IsEmpty())) {
701 nsTArray
<RefPtr
<ScriptedImageObserver
>> observers(mScriptedObservers
.Clone());
702 auto i
= observers
.Length();
706 RefPtr
<imgRequestProxy
> req
;
707 switch (aRequestType
) {
708 case CURRENT_REQUEST
:
709 req
= std::move(observers
[i
]->mCurrentRequest
);
711 case PENDING_REQUEST
:
712 req
= std::move(observers
[i
]->mPendingRequest
);
715 NS_ERROR("Unknown request type");
720 req
->CancelAndForgetObserver(aReason
);
725 void nsImageLoadingContent::CloneScriptedRequests(imgRequestProxy
* aRequest
) {
726 MOZ_ASSERT(aRequest
);
728 if (MOZ_LIKELY(mScriptedObservers
.IsEmpty())) {
733 if (aRequest
== mCurrentRequest
) {
735 } else if (aRequest
== mPendingRequest
) {
738 MOZ_ASSERT_UNREACHABLE("Unknown request type");
742 nsTArray
<RefPtr
<ScriptedImageObserver
>> observers(mScriptedObservers
.Clone());
743 auto i
= observers
.Length();
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
);
757 aRequest
->Clone(observer
->mObserver
, nullptr, getter_AddRefs(req
));
758 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
762 void nsImageLoadingContent::MakePendingScriptedRequestsCurrent() {
763 if (MOZ_LIKELY(mScriptedObservers
.IsEmpty())) {
767 nsTArray
<RefPtr
<ScriptedImageObserver
>> observers(mScriptedObservers
.Clone());
768 auto i
= observers
.Length();
772 ScriptedImageObserver
* observer
= observers
[i
];
773 if (observer
->mCurrentRequest
) {
774 observer
->mCurrentRequest
->CancelAndForgetObserver(NS_BINDING_ABORTED
);
776 observer
->mCurrentRequest
= std::move(observer
->mPendingRequest
);
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
;
787 case PENDING_REQUEST
:
788 request
= mPendingRequest
;
791 NS_ERROR("Unknown request type");
792 aError
.Throw(NS_ERROR_UNEXPECTED
);
795 return request
.forget();
799 nsImageLoadingContent::GetRequest(int32_t aRequestType
,
800 imgIRequest
** aRequest
) {
801 NS_ENSURE_ARG_POINTER(aRequest
);
804 *aRequest
= GetRequest(aRequestType
, result
).take();
806 return result
.StealNSResult();
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
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
);
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;
853 presShell
->RemoveFrameFromApproximatelyVisibleList(aFrame
);
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
;
885 nsImageLoadingContent::GetRequestType(imgIRequest
* aRequest
,
886 int32_t* aRequestType
) {
887 MOZ_ASSERT(aRequestType
, "Null out param");
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
));
906 nsImageLoadingContent::GetCurrentURI(nsIURI
** aURI
) {
907 NS_ENSURE_ARG_POINTER(aURI
);
908 *aURI
= GetCurrentURI().take();
912 already_AddRefed
<nsIURI
> nsImageLoadingContent::GetCurrentRequestFinalURI() {
913 nsCOMPtr
<nsIURI
> uri
;
914 if (mCurrentRequest
) {
915 mCurrentRequest
->GetFinalURI(getter_AddRefs(uri
));
921 nsImageLoadingContent::LoadImageWithChannel(nsIChannel
* aChannel
,
922 nsIStreamListener
** aListener
) {
924 nsContentUtils::GetImgLoaderForChannel(aChannel
, GetOurOwnerDoc());
926 return NS_ERROR_NULL_POINTER
;
929 nsCOMPtr
<Document
> doc
= GetOurOwnerDoc();
932 *aListener
= nullptr;
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); });
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
);
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
);
963 void nsImageLoadingContent::ForceReload(bool aNotify
, ErrorResult
& aError
) {
964 nsCOMPtr
<nsIURI
> currentURI
;
965 GetCurrentURI(getter_AddRefs(currentURI
));
967 aError
.Throw(NS_ERROR_NOT_AVAILABLE
);
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());
984 * Non-interface methods
987 nsresult
nsImageLoadingContent::LoadImage(const nsAString
& aNewURI
, bool aForce
,
989 ImageLoadType aImageLoadType
,
990 nsIPrincipal
* aTriggeringPrincipal
) {
991 // First, get a document (needed for security checks and the like)
992 Document
* doc
= GetOurOwnerDoc();
994 // No reason to bother, I think...
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
,
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();
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);
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
);
1037 NS_ASSERTION(!aDocument
|| aDocument
== GetOurOwnerDoc(),
1038 "Bogus document passed in");
1039 // First, get a document (needed for security checks and the like)
1041 aDocument
= GetOurOwnerDoc();
1043 // No reason to bother, I think...
1048 // Data documents, or documents from DOMParser shouldn't perform image
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
);
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
));
1071 if (currentURI
&& NS_SUCCEEDED(currentURI
->Equals(aNewURI
, &equal
)) &&
1073 // Nothing to do here.
1078 // From this point on, our image state could change. Watch it.
1079 auto updateStateOnExit
= MakeScopeExit([&] { UpdateImageState(aNotify
); });
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
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
);
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
);
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
);
1171 already_AddRefed
<Promise
> nsImageLoadingContent::RecognizeCurrentImageText(
1173 using widget::TextRecognition
;
1175 if (!mCurrentRequest
) {
1176 aRv
.ThrowInvalidStateError("No current request");
1179 nsCOMPtr
<imgIContainer
> image
;
1180 mCurrentRequest
->GetImage(getter_AddRefs(image
));
1182 aRv
.ThrowInvalidStateError("No image");
1186 RefPtr
<Promise
> domPromise
=
1187 Promise::Create(GetOurOwnerDoc()->GetScopeObject(), aRv
);
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
;
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.");
1222 languages
.AppendElement(locale
.Language().Span());
1226 TextRecognition::FindText(*image
, languages
)
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());
1237 RefPtr
<nsIImageLoadingContent
> iilc
= do_QueryReferent(weak
.get());
1239 domPromise
->MaybeRejectWithInvalidStateError(
1240 "Element was dead when we got the results");
1243 auto* ilc
= static_cast<nsImageLoadingContent
*>(iilc
.get());
1244 if (ilc
->mCurrentRequest
!= request
) {
1245 domPromise
->MaybeRejectWithInvalidStateError(
1246 "Request not current");
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",
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.
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
));
1299 if ((value
= element
->GetParsedAttr(nsGkAtoms::width
)) &&
1300 value
->Type() == nsAttrValue::eInteger
) {
1301 size
.width
= value
->GetIntegerValue();
1303 image
->GetWidth(&size
.width
);
1306 if ((value
= element
->GetParsedAttr(nsGkAtoms::height
)) &&
1307 value
->Type() == nsAttrValue::eInteger
) {
1308 size
.height
= value
->GetIntegerValue();
1310 image
->GetHeight(&size
.height
);
1313 NS_ASSERTION(size
.width
>= 0, "negative width");
1314 NS_ASSERTION(size
.height
>= 0, "negative height");
1318 void nsImageLoadingContent::UpdateImageState(bool aNotify
) {
1319 Element
* thisElement
= AsContent()->AsElement();
1320 const bool isBroken
= [&] {
1321 if (mLazyLoading
|| mHasPendingLoadTask
) {
1324 if (!mCurrentRequest
) {
1327 uint32_t currentLoadStatus
;
1328 nsresult rv
= mCurrentRequest
->GetImageStatus(¤tLoadStatus
);
1329 return NS_FAILED(rv
) || currentLoadStatus
& imgIRequest::STATUS_ERROR
;
1331 thisElement
->SetStates(ElementState::BROKEN
, isBroken
, aNotify
);
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();
1357 return frame
->PresContext();
1360 nsresult
nsImageLoadingContent::StringToURI(const nsAString
& aSpec
,
1361 Document
* aDocument
,
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
);
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
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
;
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
;
1460 class ImageRequestAutoLock
{
1462 explicit ImageRequestAutoLock(imgIRequest
* aRequest
) : mRequest(aRequest
) {
1464 mRequest
->LockImage();
1468 ~ImageRequestAutoLock() {
1470 mRequest
->UnlockImage();
1475 nsCOMPtr
<imgIRequest
> mRequest
;
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;
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
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
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;
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
,
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();
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
);
1594 case Visibility::ApproximatelyNonVisible
:
1595 UntrackImage(mCurrentRequest
, aNonvisibleAction
);
1596 UntrackImage(mPendingRequest
, aNonvisibleAction
);
1599 case Visibility::Untracked
:
1600 MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
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();
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.
1632 aFrame
->GetVisibility() == Visibility::ApproximatelyNonVisible
) {
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(
1666 aNonvisibleAction
== Some(OnNonvisible::DiscardImages
)
1667 ? ImageTracker::REQUEST_DISCARD
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(
1679 aNonvisibleAction
== Some(OnNonvisible::DiscardImages
)
1680 ? ImageTracker::REQUEST_DISCARD
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;
1723 if (mPendingRequest
) {
1724 mPendingRequest
->CancelAndForgetObserver(NS_BINDING_ABORTED
);
1725 mPendingRequest
= nullptr;
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()) {
1742 nsAString::const_iterator start
, end
;
1743 useMap
.BeginReading(start
);
1744 useMap
.EndReading(end
);
1746 int32_t hash
= useMap
.FindChar('#');
1750 // useMap contains a '#', set start to point right after the '#'
1751 start
.advance(hash
+ 1);
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();
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.
1766 new nsContentList(aElement
->SubtreeRoot(), kNameSpaceID_XHTML
,
1767 nsGkAtoms::map
, nsGkAtoms::map
, true, /* deep */
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
,
1778 map
->AsElement()->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::name
,
1779 mapName
, eCaseMatters
)) {
1780 return map
->AsElement();
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
;