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