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