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