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(¤tLoadStatus);
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