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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* A class that handles style system image loads (other image loads are handled
8 * by the nodes in the content tree).
11 #include "mozilla/css/ImageLoader.h"
13 #include "mozilla/dom/Document.h"
14 #include "mozilla/dom/DocumentInlines.h"
15 #include "mozilla/dom/LargestContentfulPaint.h"
16 #include "mozilla/dom/ImageTracker.h"
17 #include "nsContentUtils.h"
18 #include "nsIReflowCallback.h"
19 #include "nsLayoutUtils.h"
21 #include "nsCanvasFrame.h"
22 #include "nsDisplayList.h"
23 #include "nsIFrameInlines.h"
24 #include "imgIContainer.h"
25 #include "imgINotificationObserver.h"
27 #include "mozilla/PresShell.h"
28 #include "mozilla/ProfilerLabels.h"
29 #include "mozilla/StaticPtr.h"
30 #include "mozilla/SVGObserverUtils.h"
31 #include "mozilla/layers/WebRenderUserData.h"
32 #include "nsTHashSet.h"
34 using namespace mozilla::dom
;
36 namespace mozilla::css
{
38 // This is a singleton observer which looks in the `GlobalRequestTable` to look
39 // at which loaders to notify.
40 struct GlobalImageObserver final
: public imgINotificationObserver
{
42 NS_DECL_IMGINOTIFICATIONOBSERVER
44 GlobalImageObserver() = default;
47 virtual ~GlobalImageObserver() = default;
50 NS_IMPL_ADDREF(GlobalImageObserver
)
51 NS_IMPL_RELEASE(GlobalImageObserver
)
53 NS_INTERFACE_MAP_BEGIN(GlobalImageObserver
)
54 NS_INTERFACE_MAP_ENTRY(imgINotificationObserver
)
57 // Data associated with every started load.
58 struct ImageTableEntry
{
59 // Set of all ImageLoaders that have registered this URL and care for updates
61 nsTHashSet
<ImageLoader
*> mImageLoaders
;
63 // The amount of style values that are sharing this image.
64 uint32_t mSharedCount
= 1;
67 using GlobalRequestTable
=
68 nsClassHashtable
<nsRefPtrHashKey
<imgIRequest
>, ImageTableEntry
>;
70 // A table of all loads, keyed by their id mapping them to the set of
71 // ImageLoaders they have been registered in, and recording their "canonical"
74 // We use the load id as the key since we can only access sImages on the
75 // main thread, but LoadData objects might be destroyed from other threads,
76 // and we don't want to leave dangling pointers around.
77 static StaticAutoPtr
<GlobalRequestTable
> sImages
;
78 static StaticRefPtr
<GlobalImageObserver
> sImageObserver
;
81 void ImageLoader::Init() {
82 sImages
= new GlobalRequestTable();
83 sImageObserver
= new GlobalImageObserver();
87 void ImageLoader::Shutdown() {
88 for (const auto& entry
: *sImages
) {
89 imgIRequest
* imgRequest
= entry
.GetKey();
90 // All the images we put in sImages are imgRequestProxy, see LoadImage, but
91 // it's non-trivial to make the hash table to use that without changing a
93 auto* req
= static_cast<imgRequestProxy
*>(imgRequest
);
94 req
->SetCancelable(true);
95 req
->CancelAndForgetObserver(NS_BINDING_ABORTED
);
99 sImageObserver
= nullptr;
102 void ImageLoader::DropDocumentReference() {
103 MOZ_ASSERT(NS_IsMainThread());
105 // It's okay if GetPresContext returns null here (due to the presshell pointer
106 // on the document being null) as that means the presshell has already
107 // been destroyed, and it also calls ClearFrames when it is destroyed.
108 ClearFrames(GetPresContext());
113 // Arrays of requests and frames are sorted by their pointer address,
114 // for faster lookup.
115 template <typename Elem
, typename Item
,
116 typename Comparator
= nsDefaultComparator
<Elem
, Item
>>
117 static size_t GetMaybeSortedIndex(const nsTArray
<Elem
>& aArray
,
118 const Item
& aItem
, bool* aFound
,
119 Comparator aComparator
= Comparator()) {
120 size_t index
= aArray
.IndexOfFirstElementGt(aItem
, aComparator
);
121 *aFound
= index
> 0 && aComparator
.Equals(aItem
, aArray
.ElementAt(index
- 1));
125 // Returns true if an async decode is triggered for aRequest, and thus we will
126 // get an OnFrameComplete callback for this request eventually.
127 static bool TriggerAsyncDecodeAtIntrinsicSize(imgIRequest
* aRequest
) {
129 // Don't block onload if we've already got a frame complete status
130 // (since in that case the image is already loaded), or if we get an
131 // error status (since then we know the image won't ever load).
132 if (NS_SUCCEEDED(aRequest
->GetImageStatus(&status
))) {
133 if (status
& imgIRequest::STATUS_FRAME_COMPLETE
) {
134 // Already decoded, no need to do it again.
137 if (status
& imgIRequest::STATUS_ERROR
) {
138 // Already errored, this would be useless.
143 // We want to request decode in such a way that avoids triggering sync decode.
144 // First, we attempt to convert the aRequest into a imgIContainer. If that
145 // succeeds, then aRequest has an image and we can request decoding for size
146 // at zero size, the size will be ignored because we don't pass the
147 // FLAG_HIGH_QUALITY_SCALING flag and an async decode (because we didn't pass
148 // any sync decoding flags) at the intrinsic size will be requested. If the
149 // conversion to imgIContainer is unsuccessful, then that means aRequest
150 // doesn't have an image yet, which means we can safely call StartDecoding()
151 // on it without triggering any synchronous work.
152 nsCOMPtr
<imgIContainer
> imgContainer
;
153 aRequest
->GetImage(getter_AddRefs(imgContainer
));
155 imgContainer
->RequestDecodeForSize(gfx::IntSize(0, 0),
156 imgIContainer::DECODE_FLAGS_DEFAULT
);
158 // It's safe to call StartDecoding directly, since it can't
159 // trigger synchronous decode without an image. Flags are ignored.
160 aRequest
->StartDecoding(imgIContainer::FLAG_NONE
);
165 void ImageLoader::AssociateRequestToFrame(imgIRequest
* aRequest
,
166 nsIFrame
* aFrame
, Flags aFlags
) {
167 MOZ_ASSERT(NS_IsMainThread());
168 MOZ_ASSERT(!(aFlags
& Flags::IsBlockingLoadEvent
),
169 "Shouldn't be used in the public API");
172 nsCOMPtr
<imgINotificationObserver
> observer
;
173 aRequest
->GetNotificationObserver(getter_AddRefs(observer
));
175 // The request has already been canceled, so ignore it. This is ok because
176 // we're not going to get any more notifications from a canceled request.
179 MOZ_ASSERT(observer
== sImageObserver
);
182 auto* const frameSet
=
187 mDocument
->ImageTracker()->Add(aRequest
);
189 if (auto entry
= sImages
->Lookup(aRequest
)) {
190 DebugOnly
<bool> inserted
=
191 entry
.Data()->mImageLoaders
.EnsureInserted(this);
192 MOZ_ASSERT(inserted
);
194 MOZ_ASSERT_UNREACHABLE(
195 "Shouldn't be associating images not in sImages");
198 if (nsPresContext
* presContext
= GetPresContext()) {
199 nsLayoutUtils::RegisterImageRequestIfAnimated(
200 presContext
, aRequest
, nullptr);
202 return MakeUnique
<FrameSet
>();
206 auto* const requestSet
=
208 .LookupOrInsertWith(aFrame
,
210 aFrame
->SetHasImageRequest(true);
211 return MakeUnique
<RequestSet
>();
215 // Add frame to the frameSet, and handle any special processing the
216 // frame might require.
217 FrameWithFlags
fwf(aFrame
);
218 FrameWithFlags
* fwfToModify
= &fwf
;
220 // See if the frameSet already has this frame.
223 GetMaybeSortedIndex(*frameSet
, fwf
, &found
, FrameOnlyComparator());
225 // We're already tracking this frame, so prepare to modify the
226 // existing FrameWithFlags object.
227 fwfToModify
= &frameSet
->ElementAt(i
- 1);
230 // Check if the frame requires special processing.
231 if (aFlags
& Flags::RequiresReflowOnSizeAvailable
) {
232 MOZ_ASSERT(!(aFlags
&
233 Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking
),
234 "These two are exclusive");
235 fwfToModify
->mFlags
|= Flags::RequiresReflowOnSizeAvailable
;
238 if (aFlags
& Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking
) {
239 fwfToModify
->mFlags
|=
240 Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking
;
242 // If we weren't already blocking onload, do that now.
243 if (!(fwfToModify
->mFlags
& Flags::IsBlockingLoadEvent
)) {
244 if (TriggerAsyncDecodeAtIntrinsicSize(aRequest
)) {
245 // If there's no error, and the image has not loaded yet, so we can
247 fwfToModify
->mFlags
|= Flags::IsBlockingLoadEvent
;
249 // Block document onload until we either remove the frame in
250 // RemoveRequestToFrameMapping or onLoadComplete, or complete a reflow.
251 mDocument
->BlockOnload();
256 // Do some sanity checking to ensure that we only add to one mapping
257 // iff we also add to the other mapping.
258 DebugOnly
<bool> didAddToFrameSet(false);
259 DebugOnly
<bool> didAddToRequestSet(false);
261 // If we weren't already tracking this frame, add it to the frameSet.
263 frameSet
->InsertElementAt(i
, fwf
);
264 didAddToFrameSet
= true;
267 // Add request to the request set if it wasn't already there.
268 i
= GetMaybeSortedIndex(*requestSet
, aRequest
, &found
);
270 requestSet
->InsertElementAt(i
, aRequest
);
271 didAddToRequestSet
= true;
274 MOZ_ASSERT(didAddToFrameSet
== didAddToRequestSet
,
275 "We should only add to one map iff we also add to the other map.");
278 void ImageLoader::RemoveRequestToFrameMapping(imgIRequest
* aRequest
,
282 nsCOMPtr
<imgINotificationObserver
> observer
;
283 aRequest
->GetNotificationObserver(getter_AddRefs(observer
));
284 MOZ_ASSERT(!observer
|| observer
== sImageObserver
);
288 if (auto entry
= mRequestToFrameMap
.Lookup(aRequest
)) {
289 const auto& frameSet
= entry
.Data();
290 MOZ_ASSERT(frameSet
, "This should never be null");
292 // Before we remove aFrame from the frameSet, unblock onload if needed.
294 uint32_t i
= GetMaybeSortedIndex(*frameSet
, FrameWithFlags(aFrame
), &found
,
295 FrameOnlyComparator());
297 UnblockOnloadIfNeeded(frameSet
->ElementAt(i
- 1));
298 frameSet
->RemoveElementAtUnsafe(i
- 1);
301 if (frameSet
->IsEmpty()) {
302 DeregisterImageRequest(aRequest
, GetPresContext());
308 void ImageLoader::DeregisterImageRequest(imgIRequest
* aRequest
,
309 nsPresContext
* aPresContext
) {
310 mDocument
->ImageTracker()->Remove(aRequest
);
312 if (auto entry
= sImages
->Lookup(aRequest
)) {
313 entry
.Data()->mImageLoaders
.EnsureRemoved(this);
317 nsLayoutUtils::DeregisterImageRequest(aPresContext
, aRequest
, nullptr);
321 void ImageLoader::RemoveFrameToRequestMapping(imgIRequest
* aRequest
,
323 if (auto entry
= mFrameToRequestMap
.Lookup(aFrame
)) {
324 const auto& requestSet
= entry
.Data();
325 MOZ_ASSERT(requestSet
, "This should never be null");
326 requestSet
->RemoveElementSorted(aRequest
);
327 if (requestSet
->IsEmpty()) {
328 aFrame
->SetHasImageRequest(false);
334 void ImageLoader::DisassociateRequestFromFrame(imgIRequest
* aRequest
,
336 MOZ_ASSERT(NS_IsMainThread());
337 MOZ_ASSERT(aFrame
->HasImageRequest(), "why call me?");
339 RemoveRequestToFrameMapping(aRequest
, aFrame
);
340 RemoveFrameToRequestMapping(aRequest
, aFrame
);
343 void ImageLoader::DropRequestsForFrame(nsIFrame
* aFrame
) {
344 MOZ_ASSERT(NS_IsMainThread());
345 MOZ_ASSERT(aFrame
->HasImageRequest(), "why call me?");
347 UniquePtr
<RequestSet
> requestSet
;
348 mFrameToRequestMap
.Remove(aFrame
, &requestSet
);
349 aFrame
->SetHasImageRequest(false);
350 if (MOZ_UNLIKELY(!requestSet
)) {
351 MOZ_ASSERT_UNREACHABLE("HasImageRequest was lying");
354 for (imgIRequest
* request
: *requestSet
) {
355 RemoveRequestToFrameMapping(request
, aFrame
);
359 void ImageLoader::SetAnimationMode(uint16_t aMode
) {
360 MOZ_ASSERT(NS_IsMainThread());
361 NS_ASSERTION(aMode
== imgIContainer::kNormalAnimMode
||
362 aMode
== imgIContainer::kDontAnimMode
||
363 aMode
== imgIContainer::kLoopOnceAnimMode
,
364 "Wrong Animation Mode is being set!");
366 for (nsISupports
* key
: mRequestToFrameMap
.Keys()) {
367 auto* request
= static_cast<imgIRequest
*>(key
);
371 nsCOMPtr
<imgIRequest
> debugRequest
= request
;
372 NS_ASSERTION(debugRequest
== request
, "This is bad");
376 nsCOMPtr
<imgIContainer
> container
;
377 request
->GetImage(getter_AddRefs(container
));
382 // This can fail if the image is in error, and we don't care.
383 container
->SetAnimationMode(aMode
);
387 void ImageLoader::ClearFrames(nsPresContext
* aPresContext
) {
388 MOZ_ASSERT(NS_IsMainThread());
390 for (const auto& key
: mRequestToFrameMap
.Keys()) {
391 auto* request
= static_cast<imgIRequest
*>(key
);
395 nsCOMPtr
<imgIRequest
> debugRequest
= request
;
396 NS_ASSERTION(debugRequest
== request
, "This is bad");
400 DeregisterImageRequest(request
, aPresContext
);
403 mRequestToFrameMap
.Clear();
404 mFrameToRequestMap
.Clear();
407 static CORSMode
EffectiveCorsMode(nsIURI
* aURI
,
408 const StyleComputedImageUrl
& aImage
) {
410 StyleCorsMode mode
= aImage
.CorsMode();
411 if (mode
== StyleCorsMode::None
) {
412 return CORSMode::CORS_NONE
;
414 MOZ_ASSERT(mode
== StyleCorsMode::Anonymous
);
415 if (aURI
->SchemeIs("resource")) {
416 return CORSMode::CORS_NONE
;
418 return CORSMode::CORS_ANONYMOUS
;
422 already_AddRefed
<imgRequestProxy
> ImageLoader::LoadImage(
423 const StyleComputedImageUrl
& aImage
, Document
& aDocument
) {
424 MOZ_ASSERT(NS_IsMainThread());
425 nsIURI
* uri
= aImage
.GetURI();
430 if (aImage
.HasRef()) {
431 bool isEqualExceptRef
= false;
432 nsIURI
* docURI
= aDocument
.GetDocumentURI();
433 if (NS_SUCCEEDED(uri
->EqualsExceptRef(docURI
, &isEqualExceptRef
)) &&
435 // Prevent loading an internal resource.
441 nsIRequest::LOAD_NORMAL
|
442 nsContentUtils::CORSModeToLoadImageFlags(EffectiveCorsMode(uri
, aImage
));
444 const URLExtraData
& data
= aImage
.ExtraData();
446 RefPtr
<imgRequestProxy
> request
;
447 nsresult rv
= nsContentUtils::LoadImage(
448 uri
, &aDocument
, &aDocument
, data
.Principal(), 0, data
.ReferrerInfo(),
449 sImageObserver
, loadFlags
, u
"css"_ns
, getter_AddRefs(request
));
451 if (NS_FAILED(rv
) || !request
) {
455 // This image could be shared across documents, so its load cannot be
456 // canceled, see bug 1800979.
457 request
->SetCancelable(false);
458 sImages
->GetOrInsertNew(request
);
459 return request
.forget();
462 void ImageLoader::UnloadImage(imgRequestProxy
* aImage
) {
463 MOZ_ASSERT(NS_IsMainThread());
466 if (MOZ_UNLIKELY(!sImages
)) {
467 return; // Shutdown() takes care of it.
470 auto lookup
= sImages
->Lookup(aImage
);
471 MOZ_DIAGNOSTIC_ASSERT(lookup
, "Unregistered image?");
472 if (MOZ_UNLIKELY(!lookup
)) {
476 if (MOZ_UNLIKELY(--lookup
.Data()->mSharedCount
)) {
477 // Someone else still cares about this image.
481 // Now we want to really cancel the request.
482 aImage
->SetCancelable(true);
483 aImage
->CancelAndForgetObserver(NS_BINDING_ABORTED
);
484 MOZ_DIAGNOSTIC_ASSERT(lookup
.Data()->mImageLoaders
.IsEmpty(),
485 "Shouldn't be keeping references to any loader "
490 void ImageLoader::NoteSharedLoad(imgRequestProxy
* aImage
) {
491 MOZ_ASSERT(NS_IsMainThread());
494 auto lookup
= sImages
->Lookup(aImage
);
495 MOZ_DIAGNOSTIC_ASSERT(lookup
, "Unregistered image?");
496 if (MOZ_UNLIKELY(!lookup
)) {
500 lookup
.Data()->mSharedCount
++;
503 nsPresContext
* ImageLoader::GetPresContext() {
508 return mDocument
->GetPresContext();
511 static bool IsRenderNoImages(uint32_t aDisplayItemKey
) {
512 DisplayItemType type
= GetDisplayItemTypeFromKey(aDisplayItemKey
);
513 uint8_t flags
= GetDisplayItemFlagsForType(type
);
514 return flags
& TYPE_RENDERS_NO_IMAGES
;
517 static void InvalidateImages(nsIFrame
* aFrame
, imgIRequest
* aRequest
,
519 if (!aFrame
->StyleVisibility()->IsVisible()) {
523 if (aFrame
->IsTablePart()) {
524 // Tables don't necessarily build border/background display items
525 // for the individual table part frames, so IterateRetainedDataFor
526 // might not find the right display item.
527 return aFrame
->InvalidateFrame();
530 if (aFrame
->IsPrimaryFrameOfRootOrBodyElement()) {
531 if (auto* canvas
= aFrame
->PresShell()->GetCanvasFrame()) {
532 // Try to invalidate the canvas too, in the probable case the background
533 // was propagated to it.
534 InvalidateImages(canvas
, aRequest
, aForcePaint
);
538 bool invalidateFrame
= aForcePaint
;
540 if (auto userDataTable
=
541 aFrame
->GetProperty(layers::WebRenderUserDataProperty::Key())) {
542 for (RefPtr
<layers::WebRenderUserData
> data
: userDataTable
->Values()) {
543 switch (data
->GetType()) {
544 case layers::WebRenderUserData::UserDataType::eFallback
:
545 if (!IsRenderNoImages(data
->GetDisplayItemKey())) {
546 static_cast<layers::WebRenderFallbackData
*>(data
.get())
549 // XXX: handle Blob data
550 invalidateFrame
= true;
552 case layers::WebRenderUserData::UserDataType::eMask
:
553 static_cast<layers::WebRenderMaskData
*>(data
.get())->Invalidate();
554 invalidateFrame
= true;
556 case layers::WebRenderUserData::UserDataType::eImageProvider
:
557 if (static_cast<layers::WebRenderImageProviderData
*>(data
.get())
558 ->Invalidate(aRequest
->GetProviderId())) {
563 invalidateFrame
= true;
569 // Update ancestor rendering observers (-moz-element etc)
571 // NOTE: We need to do this even if invalidateFrame is false, see bug 1114526.
573 nsIFrame
* f
= aFrame
;
574 while (f
&& !f
->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT
)) {
575 SVGObserverUtils::InvalidateDirectRenderingObservers(f
);
576 f
= nsLayoutUtils::GetCrossDocParentFrameInProcess(f
);
580 if (invalidateFrame
) {
581 aFrame
->SchedulePaint();
585 void ImageLoader::UnblockOnloadIfNeeded(FrameWithFlags
& aFwf
) {
586 if (aFwf
.mFlags
& Flags::IsBlockingLoadEvent
) {
587 mDocument
->UnblockOnload(false);
588 aFwf
.mFlags
&= ~Flags::IsBlockingLoadEvent
;
592 void ImageLoader::UnblockOnloadIfNeeded(nsIFrame
* aFrame
,
593 imgIRequest
* aRequest
) {
595 MOZ_ASSERT(aRequest
);
597 FrameSet
* frameSet
= mRequestToFrameMap
.Get(aRequest
);
603 frameSet
->BinaryIndexOf(FrameWithFlags(aFrame
), FrameOnlyComparator());
604 if (i
!= FrameSet::NoIndex
) {
605 UnblockOnloadIfNeeded(frameSet
->ElementAt(i
));
609 // This callback is used to unblock document onload after a reflow
610 // triggered from an image load.
611 struct ImageLoader::ImageReflowCallback final
: public nsIReflowCallback
{
612 RefPtr
<ImageLoader
> mLoader
;
614 nsCOMPtr
<imgIRequest
> const mRequest
;
616 ImageReflowCallback(ImageLoader
* aLoader
, nsIFrame
* aFrame
,
617 imgIRequest
* aRequest
)
618 : mLoader(aLoader
), mFrame(aFrame
), mRequest(aRequest
) {}
620 bool ReflowFinished() override
;
621 void ReflowCallbackCanceled() override
;
624 bool ImageLoader::ImageReflowCallback::ReflowFinished() {
625 // Check that the frame is still valid. If it isn't, then onload was
626 // unblocked when the frame was removed from the FrameSet in
627 // RemoveRequestToFrameMapping.
628 if (mFrame
.IsAlive()) {
629 mLoader
->UnblockOnloadIfNeeded(mFrame
, mRequest
);
632 // Get rid of this callback object.
635 // We don't need to trigger layout.
639 void ImageLoader::ImageReflowCallback::ReflowCallbackCanceled() {
640 // Check that the frame is still valid. If it isn't, then onload was
641 // unblocked when the frame was removed from the FrameSet in
642 // RemoveRequestToFrameMapping.
643 if (mFrame
.IsAlive()) {
644 mLoader
->UnblockOnloadIfNeeded(mFrame
, mRequest
);
647 // Get rid of this callback object.
651 void GlobalImageObserver::Notify(imgIRequest
* aRequest
, int32_t aType
,
652 const nsIntRect
* aData
) {
653 auto entry
= sImages
->Lookup(aRequest
);
654 MOZ_DIAGNOSTIC_ASSERT(entry
);
655 if (MOZ_UNLIKELY(!entry
)) {
659 const auto loadersToNotify
=
660 ToTArray
<nsTArray
<RefPtr
<ImageLoader
>>>(entry
.Data()->mImageLoaders
);
661 for (const auto& loader
: loadersToNotify
) {
662 loader
->Notify(aRequest
, aType
, aData
);
666 void ImageLoader::Notify(imgIRequest
* aRequest
, int32_t aType
,
667 const nsIntRect
* aData
) {
669 if (profiler_is_active()) {
670 nsCOMPtr
<nsIURI
> uri
;
671 aRequest
->GetFinalURI(getter_AddRefs(uri
));
673 uri
->GetSpec(uriString
);
677 AUTO_PROFILER_LABEL_DYNAMIC_CSTR("ImageLoader::Notify", OTHER
,
680 if (aType
== imgINotificationObserver::SIZE_AVAILABLE
) {
681 nsCOMPtr
<imgIContainer
> image
;
682 aRequest
->GetImage(getter_AddRefs(image
));
683 return OnSizeAvailable(aRequest
, image
);
686 if (aType
== imgINotificationObserver::IS_ANIMATED
) {
687 return OnImageIsAnimated(aRequest
);
690 if (aType
== imgINotificationObserver::FRAME_COMPLETE
) {
691 return OnFrameComplete(aRequest
);
694 if (aType
== imgINotificationObserver::FRAME_UPDATE
) {
695 return OnFrameUpdate(aRequest
);
698 if (aType
== imgINotificationObserver::DECODE_COMPLETE
) {
699 nsCOMPtr
<imgIContainer
> image
;
700 aRequest
->GetImage(getter_AddRefs(image
));
701 if (image
&& mDocument
) {
702 image
->PropagateUseCounters(mDocument
);
706 if (aType
== imgINotificationObserver::LOAD_COMPLETE
) {
707 return OnLoadComplete(aRequest
);
711 void ImageLoader::OnSizeAvailable(imgIRequest
* aRequest
,
712 imgIContainer
* aImage
) {
713 nsPresContext
* presContext
= GetPresContext();
718 aImage
->SetAnimationMode(presContext
->ImageAnimationMode());
720 FrameSet
* frameSet
= mRequestToFrameMap
.Get(aRequest
);
725 for (const FrameWithFlags
& fwf
: *frameSet
) {
726 if (fwf
.mFlags
& Flags::RequiresReflowOnSizeAvailable
) {
727 fwf
.mFrame
->PresShell()->FrameNeedsReflow(
728 fwf
.mFrame
, IntrinsicDirty::FrameAncestorsAndDescendants
,
734 void ImageLoader::OnImageIsAnimated(imgIRequest
* aRequest
) {
739 FrameSet
* frameSet
= mRequestToFrameMap
.Get(aRequest
);
744 // Register with the refresh driver now that we are aware that
746 nsPresContext
* presContext
= GetPresContext();
748 nsLayoutUtils::RegisterImageRequest(presContext
, aRequest
, nullptr);
752 void ImageLoader::OnFrameComplete(imgIRequest
* aRequest
) {
753 ImageFrameChanged(aRequest
, /* aFirstFrame = */ true);
756 void ImageLoader::OnFrameUpdate(imgIRequest
* aRequest
) {
757 ImageFrameChanged(aRequest
, /* aFirstFrame = */ false);
760 void ImageLoader::ImageFrameChanged(imgIRequest
* aRequest
, bool aFirstFrame
) {
765 FrameSet
* frameSet
= mRequestToFrameMap
.Get(aRequest
);
770 for (FrameWithFlags
& fwf
: *frameSet
) {
771 // Since we just finished decoding a frame, we always want to paint, in
772 // case we're now able to paint an image that we couldn't paint before
773 // (and hence that we don't have retained data for).
774 const bool forceRepaint
= aFirstFrame
;
775 InvalidateImages(fwf
.mFrame
, aRequest
, forceRepaint
);
777 // We don't reflow / try to unblock onload for subsequent frame updates.
781 Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking
) {
782 // Tell the container of the frame to reflow because the image request
783 // has finished decoding its first frame.
784 // FIXME(emilio): Why requesting reflow on the _parent_?
785 nsIFrame
* parent
= fwf
.mFrame
->GetInFlowParent();
786 parent
->PresShell()->FrameNeedsReflow(
787 parent
, IntrinsicDirty::FrameAncestorsAndDescendants
,
789 // If we need to also potentially unblock onload, do it once reflow is
790 // done, with a reflow callback.
791 if (fwf
.mFlags
& Flags::IsBlockingLoadEvent
) {
792 auto* unblocker
= new ImageReflowCallback(this, fwf
.mFrame
, aRequest
);
793 parent
->PresShell()->PostReflowCallback(unblocker
);
799 void ImageLoader::OnLoadComplete(imgIRequest
* aRequest
) {
805 if (NS_FAILED(aRequest
->GetImageStatus(&status
))) {
809 FrameSet
* frameSet
= mRequestToFrameMap
.Get(aRequest
);
814 for (FrameWithFlags
& fwf
: *frameSet
) {
815 if (status
& imgIRequest::STATUS_ERROR
) {
816 // Check if aRequest has an error state. If it does, we need to unblock
817 // Document onload for all the frames associated with this request that
818 // have blocked onload. This is what happens in a CORS mode violation, and
819 // may happen during other network events.
820 UnblockOnloadIfNeeded(fwf
);
822 nsIFrame
* frame
= fwf
.mFrame
;
823 if (frame
->StyleVisibility()->IsVisible()) {
824 frame
->SchedulePaint();
827 if (StaticPrefs::dom_enable_largest_contentful_paint()) {
828 LargestContentfulPaint::MaybeProcessImageForElementTiming(
829 static_cast<imgRequestProxy
*>(aRequest
),
830 frame
->GetContent()->AsElement());
834 } // namespace mozilla::css