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/ImageTracker.h"
16 #include "nsContentUtils.h"
17 #include "nsIReflowCallback.h"
18 #include "nsLayoutUtils.h"
20 #include "nsCanvasFrame.h"
21 #include "nsDisplayList.h"
22 #include "nsIFrameInlines.h"
23 #include "FrameLayerBuilder.h"
24 #include "imgIContainer.h"
25 #include "imgINotificationObserver.h"
27 #include "mozilla/PresShell.h"
28 #include "mozilla/ProfilerLabels.h"
29 #include "mozilla/SVGObserverUtils.h"
30 #include "mozilla/layers/WebRenderUserData.h"
32 using namespace mozilla::dom
;
34 namespace mozilla::css
{
36 // This is a singleton observer which looks in the `GlobalRequestTable` to look
37 // at which loaders to notify.
38 struct GlobalImageObserver final
: public imgINotificationObserver
{
40 NS_DECL_IMGINOTIFICATIONOBSERVER
42 GlobalImageObserver() = default;
45 virtual ~GlobalImageObserver() = default;
48 NS_IMPL_ADDREF(GlobalImageObserver
)
49 NS_IMPL_RELEASE(GlobalImageObserver
)
51 NS_INTERFACE_MAP_BEGIN(GlobalImageObserver
)
52 NS_INTERFACE_MAP_ENTRY(imgINotificationObserver
)
55 // Data associated with every started load.
56 struct ImageTableEntry
{
57 // Set of all ImageLoaders that have registered this URL and care for updates
59 nsTHashtable
<nsPtrHashKey
<ImageLoader
>> mImageLoaders
;
61 // The amount of style values that are sharing this image.
62 uint32_t mSharedCount
= 1;
65 using GlobalRequestTable
=
66 nsClassHashtable
<nsRefPtrHashKey
<imgIRequest
>, ImageTableEntry
>;
68 // A table of all loads, keyed by their id mapping them to the set of
69 // ImageLoaders they have been registered in, and recording their "canonical"
72 // We use the load id as the key since we can only access sImages on the
73 // main thread, but LoadData objects might be destroyed from other threads,
74 // and we don't want to leave dangling pointers around.
75 static GlobalRequestTable
* sImages
= nullptr;
76 static StaticRefPtr
<GlobalImageObserver
> sImageObserver
;
79 void ImageLoader::Init() {
80 sImages
= new GlobalRequestTable();
81 sImageObserver
= new GlobalImageObserver();
85 void ImageLoader::Shutdown() {
88 sImageObserver
= nullptr;
91 void ImageLoader::DropDocumentReference() {
92 MOZ_ASSERT(NS_IsMainThread());
94 // It's okay if GetPresContext returns null here (due to the presshell pointer
95 // on the document being null) as that means the presshell has already
96 // been destroyed, and it also calls ClearFrames when it is destroyed.
97 ClearFrames(GetPresContext());
102 // Arrays of requests and frames are sorted by their pointer address,
103 // for faster lookup.
104 template <typename Elem
, typename Item
,
105 typename Comparator
= nsDefaultComparator
<Elem
, Item
>>
106 static size_t GetMaybeSortedIndex(const nsTArray
<Elem
>& aArray
,
107 const Item
& aItem
, bool* aFound
,
108 Comparator aComparator
= Comparator()) {
109 size_t index
= aArray
.IndexOfFirstElementGt(aItem
, aComparator
);
110 *aFound
= index
> 0 && aComparator
.Equals(aItem
, aArray
.ElementAt(index
- 1));
114 // Returns true if an async decode is triggered for aRequest, and thus we will
115 // get an OnFrameComplete callback for this request eventually.
116 static bool TriggerAsyncDecodeAtIntrinsicSize(imgIRequest
* aRequest
) {
118 // Don't block onload if we've already got a frame complete status
119 // (since in that case the image is already loaded), or if we get an
120 // error status (since then we know the image won't ever load).
121 if (NS_SUCCEEDED(aRequest
->GetImageStatus(&status
))) {
122 if (status
& imgIRequest::STATUS_FRAME_COMPLETE
) {
123 // Already decoded, no need to do it again.
126 if (status
& imgIRequest::STATUS_ERROR
) {
127 // Already errored, this would be useless.
132 // We want to request decode in such a way that avoids triggering sync decode.
133 // First, we attempt to convert the aRequest into a imgIContainer. If that
134 // succeeds, then aRequest has an image and we can request decoding for size
135 // at zero size, the size will be ignored because we don't pass the
136 // FLAG_HIGH_QUALITY_SCALING flag and an async decode (because we didn't pass
137 // any sync decoding flags) at the intrinsic size will be requested. If the
138 // conversion to imgIContainer is unsuccessful, then that means aRequest
139 // doesn't have an image yet, which means we can safely call StartDecoding()
140 // on it without triggering any synchronous work.
141 nsCOMPtr
<imgIContainer
> imgContainer
;
142 aRequest
->GetImage(getter_AddRefs(imgContainer
));
144 imgContainer
->RequestDecodeForSize(gfx::IntSize(0, 0),
145 imgIContainer::DECODE_FLAGS_DEFAULT
);
147 // It's safe to call StartDecoding directly, since it can't
148 // trigger synchronous decode without an image. Flags are ignored.
149 aRequest
->StartDecoding(imgIContainer::FLAG_NONE
);
154 void ImageLoader::AssociateRequestToFrame(imgIRequest
* aRequest
,
155 nsIFrame
* aFrame
, Flags aFlags
) {
156 MOZ_ASSERT(NS_IsMainThread());
157 MOZ_ASSERT(!(aFlags
& Flags::IsBlockingLoadEvent
),
158 "Shouldn't be used in the public API");
161 nsCOMPtr
<imgINotificationObserver
> observer
;
162 aRequest
->GetNotificationObserver(getter_AddRefs(observer
));
164 // The request has already been canceled, so ignore it. This is ok because
165 // we're not going to get any more notifications from a canceled request.
168 MOZ_ASSERT(observer
== sImageObserver
);
171 auto* const frameSet
=
176 mDocument
->ImageTracker()->Add(aRequest
);
178 if (auto entry
= sImages
->Lookup(aRequest
)) {
179 DebugOnly
<bool> inserted
=
180 entry
.Data()->mImageLoaders
.EnsureInserted(this);
181 MOZ_ASSERT(inserted
);
183 MOZ_ASSERT_UNREACHABLE(
184 "Shouldn't be associating images not in sImages");
187 if (nsPresContext
* presContext
= GetPresContext()) {
188 nsLayoutUtils::RegisterImageRequestIfAnimated(
189 presContext
, aRequest
, nullptr);
191 return MakeUnique
<FrameSet
>();
195 auto* const requestSet
=
197 .LookupOrInsertWith(aFrame
,
199 aFrame
->SetHasImageRequest(true);
200 return MakeUnique
<RequestSet
>();
204 // Add frame to the frameSet, and handle any special processing the
205 // frame might require.
206 FrameWithFlags
fwf(aFrame
);
207 FrameWithFlags
* fwfToModify
= &fwf
;
209 // See if the frameSet already has this frame.
212 GetMaybeSortedIndex(*frameSet
, fwf
, &found
, FrameOnlyComparator());
214 // We're already tracking this frame, so prepare to modify the
215 // existing FrameWithFlags object.
216 fwfToModify
= &frameSet
->ElementAt(i
- 1);
219 // Check if the frame requires special processing.
220 if (aFlags
& Flags::RequiresReflowOnSizeAvailable
) {
221 MOZ_ASSERT(!(aFlags
&
222 Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking
),
223 "These two are exclusive");
224 fwfToModify
->mFlags
|= Flags::RequiresReflowOnSizeAvailable
;
227 if (aFlags
& Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking
) {
228 fwfToModify
->mFlags
|=
229 Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking
;
231 // If we weren't already blocking onload, do that now.
232 if (!(fwfToModify
->mFlags
& Flags::IsBlockingLoadEvent
)) {
233 if (TriggerAsyncDecodeAtIntrinsicSize(aRequest
)) {
234 // If there's no error, and the image has not loaded yet, so we can
236 fwfToModify
->mFlags
|= Flags::IsBlockingLoadEvent
;
238 // Block document onload until we either remove the frame in
239 // RemoveRequestToFrameMapping or onLoadComplete, or complete a reflow.
240 mDocument
->BlockOnload();
245 // Do some sanity checking to ensure that we only add to one mapping
246 // iff we also add to the other mapping.
247 DebugOnly
<bool> didAddToFrameSet(false);
248 DebugOnly
<bool> didAddToRequestSet(false);
250 // If we weren't already tracking this frame, add it to the frameSet.
252 frameSet
->InsertElementAt(i
, fwf
);
253 didAddToFrameSet
= true;
256 // Add request to the request set if it wasn't already there.
257 i
= GetMaybeSortedIndex(*requestSet
, aRequest
, &found
);
259 requestSet
->InsertElementAt(i
, aRequest
);
260 didAddToRequestSet
= true;
263 MOZ_ASSERT(didAddToFrameSet
== didAddToRequestSet
,
264 "We should only add to one map iff we also add to the other map.");
267 void ImageLoader::RemoveRequestToFrameMapping(imgIRequest
* aRequest
,
271 nsCOMPtr
<imgINotificationObserver
> observer
;
272 aRequest
->GetNotificationObserver(getter_AddRefs(observer
));
273 MOZ_ASSERT(!observer
|| observer
== sImageObserver
);
277 if (auto entry
= mRequestToFrameMap
.Lookup(aRequest
)) {
278 const auto& frameSet
= entry
.Data();
279 MOZ_ASSERT(frameSet
, "This should never be null");
281 // Before we remove aFrame from the frameSet, unblock onload if needed.
283 uint32_t i
= GetMaybeSortedIndex(*frameSet
, FrameWithFlags(aFrame
), &found
,
284 FrameOnlyComparator());
286 UnblockOnloadIfNeeded(frameSet
->ElementAt(i
- 1));
287 frameSet
->RemoveElementAt(i
- 1);
290 if (frameSet
->IsEmpty()) {
291 DeregisterImageRequest(aRequest
, GetPresContext());
297 void ImageLoader::DeregisterImageRequest(imgIRequest
* aRequest
,
298 nsPresContext
* aPresContext
) {
299 mDocument
->ImageTracker()->Remove(aRequest
);
301 if (auto entry
= sImages
->Lookup(aRequest
)) {
302 entry
.Data()->mImageLoaders
.EnsureRemoved(this);
306 nsLayoutUtils::DeregisterImageRequest(aPresContext
, aRequest
, nullptr);
310 void ImageLoader::RemoveFrameToRequestMapping(imgIRequest
* aRequest
,
312 if (auto entry
= mFrameToRequestMap
.Lookup(aFrame
)) {
313 const auto& requestSet
= entry
.Data();
314 MOZ_ASSERT(requestSet
, "This should never be null");
315 requestSet
->RemoveElementSorted(aRequest
);
316 if (requestSet
->IsEmpty()) {
317 aFrame
->SetHasImageRequest(false);
323 void ImageLoader::DisassociateRequestFromFrame(imgIRequest
* aRequest
,
325 MOZ_ASSERT(NS_IsMainThread());
326 MOZ_ASSERT(aFrame
->HasImageRequest(), "why call me?");
328 RemoveRequestToFrameMapping(aRequest
, aFrame
);
329 RemoveFrameToRequestMapping(aRequest
, aFrame
);
332 void ImageLoader::DropRequestsForFrame(nsIFrame
* aFrame
) {
333 MOZ_ASSERT(NS_IsMainThread());
334 MOZ_ASSERT(aFrame
->HasImageRequest(), "why call me?");
336 UniquePtr
<RequestSet
> requestSet
;
337 mFrameToRequestMap
.Remove(aFrame
, &requestSet
);
338 aFrame
->SetHasImageRequest(false);
339 if (MOZ_UNLIKELY(!requestSet
)) {
340 MOZ_ASSERT_UNREACHABLE("HasImageRequest was lying");
343 for (imgIRequest
* request
: *requestSet
) {
344 RemoveRequestToFrameMapping(request
, aFrame
);
348 void ImageLoader::SetAnimationMode(uint16_t aMode
) {
349 MOZ_ASSERT(NS_IsMainThread());
350 NS_ASSERTION(aMode
== imgIContainer::kNormalAnimMode
||
351 aMode
== imgIContainer::kDontAnimMode
||
352 aMode
== imgIContainer::kLoopOnceAnimMode
,
353 "Wrong Animation Mode is being set!");
355 for (auto iter
= mRequestToFrameMap
.ConstIter(); !iter
.Done(); iter
.Next()) {
356 auto request
= static_cast<imgIRequest
*>(iter
.Key());
360 nsCOMPtr
<imgIRequest
> debugRequest
= request
;
361 NS_ASSERTION(debugRequest
== request
, "This is bad");
365 nsCOMPtr
<imgIContainer
> container
;
366 request
->GetImage(getter_AddRefs(container
));
371 // This can fail if the image is in error, and we don't care.
372 container
->SetAnimationMode(aMode
);
376 void ImageLoader::ClearFrames(nsPresContext
* aPresContext
) {
377 MOZ_ASSERT(NS_IsMainThread());
379 for (auto iter
= mRequestToFrameMap
.ConstIter(); !iter
.Done(); iter
.Next()) {
380 auto request
= static_cast<imgIRequest
*>(iter
.Key());
384 nsCOMPtr
<imgIRequest
> debugRequest
= request
;
385 NS_ASSERTION(debugRequest
== request
, "This is bad");
389 DeregisterImageRequest(request
, aPresContext
);
392 mRequestToFrameMap
.Clear();
393 mFrameToRequestMap
.Clear();
396 static CORSMode
EffectiveCorsMode(nsIURI
* aURI
,
397 const StyleComputedImageUrl
& aImage
) {
399 StyleCorsMode mode
= aImage
.CorsMode();
400 if (mode
== StyleCorsMode::None
) {
401 return CORSMode::CORS_NONE
;
403 MOZ_ASSERT(mode
== StyleCorsMode::Anonymous
);
404 if (aURI
->SchemeIs("resource")) {
405 return CORSMode::CORS_NONE
;
407 return CORSMode::CORS_ANONYMOUS
;
411 already_AddRefed
<imgRequestProxy
> ImageLoader::LoadImage(
412 const StyleComputedImageUrl
& aImage
, Document
& aDocument
) {
413 MOZ_ASSERT(NS_IsMainThread());
414 nsIURI
* uri
= aImage
.GetURI();
419 if (aImage
.HasRef()) {
420 bool isEqualExceptRef
= false;
421 nsIURI
* docURI
= aDocument
.GetDocumentURI();
422 if (NS_SUCCEEDED(uri
->EqualsExceptRef(docURI
, &isEqualExceptRef
)) &&
424 // Prevent loading an internal resource.
430 nsIRequest::LOAD_NORMAL
|
431 nsContentUtils::CORSModeToLoadImageFlags(EffectiveCorsMode(uri
, aImage
));
433 const URLExtraData
& data
= aImage
.ExtraData();
435 RefPtr
<imgRequestProxy
> request
;
436 nsresult rv
= nsContentUtils::LoadImage(
437 uri
, &aDocument
, &aDocument
, data
.Principal(), 0, data
.ReferrerInfo(),
438 sImageObserver
, loadFlags
, u
"css"_ns
, getter_AddRefs(request
));
440 if (NS_FAILED(rv
) || !request
) {
443 sImages
->GetOrInsertNew(request
);
444 return request
.forget();
447 void ImageLoader::UnloadImage(imgRequestProxy
* aImage
) {
448 MOZ_ASSERT(NS_IsMainThread());
451 auto lookup
= sImages
->Lookup(aImage
);
452 MOZ_DIAGNOSTIC_ASSERT(lookup
, "Unregistered image?");
453 if (MOZ_UNLIKELY(!lookup
)) {
457 if (MOZ_UNLIKELY(--lookup
.Data()->mSharedCount
)) {
458 // Someone else still cares about this image.
462 aImage
->CancelAndForgetObserver(NS_BINDING_ABORTED
);
463 MOZ_DIAGNOSTIC_ASSERT(lookup
.Data()->mImageLoaders
.IsEmpty(),
464 "Shouldn't be keeping references to any loader "
469 void ImageLoader::NoteSharedLoad(imgRequestProxy
* aImage
) {
470 MOZ_ASSERT(NS_IsMainThread());
473 auto lookup
= sImages
->Lookup(aImage
);
474 MOZ_DIAGNOSTIC_ASSERT(lookup
, "Unregistered image?");
475 if (MOZ_UNLIKELY(!lookup
)) {
479 lookup
.Data()->mSharedCount
++;
482 nsPresContext
* ImageLoader::GetPresContext() {
487 return mDocument
->GetPresContext();
490 static bool IsRenderNoImages(uint32_t aDisplayItemKey
) {
491 DisplayItemType type
= GetDisplayItemTypeFromKey(aDisplayItemKey
);
492 uint8_t flags
= GetDisplayItemFlagsForType(type
);
493 return flags
& TYPE_RENDERS_NO_IMAGES
;
496 static void InvalidateImages(nsIFrame
* aFrame
, imgIRequest
* aRequest
,
498 if (!aFrame
->StyleVisibility()->IsVisible()) {
502 if (aFrame
->IsFrameOfType(nsIFrame::eTablePart
)) {
503 // Tables don't necessarily build border/background display items
504 // for the individual table part frames, so IterateRetainedDataFor
505 // might not find the right display item.
506 return aFrame
->InvalidateFrame();
509 if (aFrame
->IsPrimaryFrameOfRootOrBodyElement()) {
510 if (auto* canvas
= aFrame
->PresShell()->GetCanvasFrame()) {
511 // Try to invalidate the canvas too, in the probable case the background
512 // was propagated to it.
513 InvalidateImages(canvas
, aRequest
, aForcePaint
);
517 bool invalidateFrame
= aForcePaint
;
518 if (auto* array
= aFrame
->DisplayItemData()) {
519 for (auto* did
: *array
) {
520 DisplayItemData
* data
= DisplayItemData::AssertDisplayItemData(did
);
521 uint32_t displayItemKey
= data
->GetDisplayItemKey();
523 if (displayItemKey
!= 0 && !IsRenderNoImages(displayItemKey
)) {
524 if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
525 DisplayItemType type
= GetDisplayItemTypeFromKey(displayItemKey
);
527 "Invalidating display item(type=%d) based on frame %p \
528 because it might contain an invalidated image\n",
529 static_cast<uint32_t>(type
), aFrame
);
533 invalidateFrame
= true;
538 if (auto userDataTable
=
539 aFrame
->GetProperty(layers::WebRenderUserDataProperty::Key())) {
540 for (auto iter
= userDataTable
->Iter(); !iter
.Done(); iter
.Next()) {
541 RefPtr
<layers::WebRenderUserData
> data
= iter
.UserData();
542 switch (data
->GetType()) {
543 case layers::WebRenderUserData::UserDataType::eFallback
:
544 if (!IsRenderNoImages(data
->GetDisplayItemKey())) {
545 static_cast<layers::WebRenderFallbackData
*>(data
.get())
548 // XXX: handle Blob data
549 invalidateFrame
= true;
551 case layers::WebRenderUserData::UserDataType::eImage
:
552 if (static_cast<layers::WebRenderImageData
*>(data
.get())
553 ->UsingSharedSurface(aRequest
->GetProducerId())) {
558 invalidateFrame
= true;
564 // Update ancestor rendering observers (-moz-element etc)
566 // NOTE: We need to do this even if invalidateFrame is false, see bug 1114526.
568 nsIFrame
* f
= aFrame
;
569 while (f
&& !f
->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT
)) {
570 SVGObserverUtils::InvalidateDirectRenderingObservers(f
);
571 f
= nsLayoutUtils::GetCrossDocParentFrameInProcess(f
);
575 if (invalidateFrame
) {
576 aFrame
->SchedulePaint();
580 void ImageLoader::UnblockOnloadIfNeeded(FrameWithFlags
& aFwf
) {
581 if (aFwf
.mFlags
& Flags::IsBlockingLoadEvent
) {
582 mDocument
->UnblockOnload(false);
583 aFwf
.mFlags
&= ~Flags::IsBlockingLoadEvent
;
587 void ImageLoader::UnblockOnloadIfNeeded(nsIFrame
* aFrame
,
588 imgIRequest
* aRequest
) {
590 MOZ_ASSERT(aRequest
);
592 FrameSet
* frameSet
= mRequestToFrameMap
.Get(aRequest
);
598 frameSet
->BinaryIndexOf(FrameWithFlags(aFrame
), FrameOnlyComparator());
599 if (i
!= FrameSet::NoIndex
) {
600 UnblockOnloadIfNeeded(frameSet
->ElementAt(i
));
604 // This callback is used to unblock document onload after a reflow
605 // triggered from an image load.
606 struct ImageLoader::ImageReflowCallback final
: public nsIReflowCallback
{
607 RefPtr
<ImageLoader
> mLoader
;
609 nsCOMPtr
<imgIRequest
> const mRequest
;
611 ImageReflowCallback(ImageLoader
* aLoader
, nsIFrame
* aFrame
,
612 imgIRequest
* aRequest
)
613 : mLoader(aLoader
), mFrame(aFrame
), mRequest(aRequest
) {}
615 bool ReflowFinished() override
;
616 void ReflowCallbackCanceled() override
;
619 bool ImageLoader::ImageReflowCallback::ReflowFinished() {
620 // Check that the frame is still valid. If it isn't, then onload was
621 // unblocked when the frame was removed from the FrameSet in
622 // RemoveRequestToFrameMapping.
623 if (mFrame
.IsAlive()) {
624 mLoader
->UnblockOnloadIfNeeded(mFrame
, mRequest
);
627 // Get rid of this callback object.
630 // We don't need to trigger layout.
634 void ImageLoader::ImageReflowCallback::ReflowCallbackCanceled() {
635 // Check that the frame is still valid. If it isn't, then onload was
636 // unblocked when the frame was removed from the FrameSet in
637 // RemoveRequestToFrameMapping.
638 if (mFrame
.IsAlive()) {
639 mLoader
->UnblockOnloadIfNeeded(mFrame
, mRequest
);
642 // Get rid of this callback object.
646 void GlobalImageObserver::Notify(imgIRequest
* aRequest
, int32_t aType
,
647 const nsIntRect
* aData
) {
648 auto entry
= sImages
->Lookup(aRequest
);
649 MOZ_DIAGNOSTIC_ASSERT(entry
);
650 if (MOZ_UNLIKELY(!entry
)) {
654 auto& loaders
= entry
.Data()->mImageLoaders
;
655 nsTArray
<RefPtr
<ImageLoader
>> loadersToNotify(loaders
.Count());
656 for (auto iter
= loaders
.Iter(); !iter
.Done(); iter
.Next()) {
657 loadersToNotify
.AppendElement(iter
.Get()->GetKey());
659 for (auto& loader
: loadersToNotify
) {
660 loader
->Notify(aRequest
, aType
, aData
);
664 void ImageLoader::Notify(imgIRequest
* aRequest
, int32_t aType
,
665 const nsIntRect
* aData
) {
666 #ifdef MOZ_GECKO_PROFILER
668 if (profiler_is_active()) {
669 nsCOMPtr
<nsIURI
> uri
;
670 aRequest
->GetFinalURI(getter_AddRefs(uri
));
672 uri
->GetSpec(uriString
);
676 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("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::StyleChange
, NS_FRAME_IS_DIRTY
);
733 void ImageLoader::OnImageIsAnimated(imgIRequest
* aRequest
) {
738 FrameSet
* frameSet
= mRequestToFrameMap
.Get(aRequest
);
743 // Register with the refresh driver now that we are aware that
745 nsPresContext
* presContext
= GetPresContext();
747 nsLayoutUtils::RegisterImageRequest(presContext
, aRequest
, nullptr);
751 void ImageLoader::OnFrameComplete(imgIRequest
* aRequest
) {
752 ImageFrameChanged(aRequest
, /* aFirstFrame = */ true);
755 void ImageLoader::OnFrameUpdate(imgIRequest
* aRequest
) {
756 ImageFrameChanged(aRequest
, /* aFirstFrame = */ false);
759 void ImageLoader::ImageFrameChanged(imgIRequest
* aRequest
, bool aFirstFrame
) {
764 FrameSet
* frameSet
= mRequestToFrameMap
.Get(aRequest
);
769 for (FrameWithFlags
& fwf
: *frameSet
) {
770 // Since we just finished decoding a frame, we always want to paint, in
771 // case we're now able to paint an image that we couldn't paint before
772 // (and hence that we don't have retained data for).
773 const bool forceRepaint
= aFirstFrame
;
774 InvalidateImages(fwf
.mFrame
, aRequest
, forceRepaint
);
776 // We don't reflow / try to unblock onload for subsequent frame updates.
780 Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking
) {
781 // Tell the container of the frame to reflow because the image request
782 // has finished decoding its first frame.
783 // FIXME(emilio): Why requesting reflow on the _parent_?
784 nsIFrame
* parent
= fwf
.mFrame
->GetInFlowParent();
785 parent
->PresShell()->FrameNeedsReflow(parent
, IntrinsicDirty::StyleChange
,
787 // If we need to also potentially unblock onload, do it once reflow is
788 // done, with a reflow callback.
789 if (fwf
.mFlags
& Flags::IsBlockingLoadEvent
) {
790 auto* unblocker
= new ImageReflowCallback(this, fwf
.mFrame
, aRequest
);
791 parent
->PresShell()->PostReflowCallback(unblocker
);
797 void ImageLoader::OnLoadComplete(imgIRequest
* aRequest
) {
803 if (NS_FAILED(aRequest
->GetImageStatus(&status
))) {
807 FrameSet
* frameSet
= mRequestToFrameMap
.Get(aRequest
);
812 for (FrameWithFlags
& fwf
: *frameSet
) {
813 if (status
& imgIRequest::STATUS_ERROR
) {
814 // Check if aRequest has an error state. If it does, we need to unblock
815 // Document onload for all the frames associated with this request that
816 // have blocked onload. This is what happens in a CORS mode violation, and
817 // may happen during other network events.
818 UnblockOnloadIfNeeded(fwf
);
820 if (fwf
.mFrame
->StyleVisibility()->IsVisible()) {
821 fwf
.mFrame
->SchedulePaint();
826 } // namespace mozilla::css