Bug 1864419 - Part 1: Factor out ArgumentsData array into container class r=jandem
[gecko.git] / layout / style / ImageLoader.cpp
blobc56ecdfd20c109e8fff8db85d3eba252564fba96
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).
9 */
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"
20 #include "nsError.h"
21 #include "nsCanvasFrame.h"
22 #include "nsDisplayList.h"
23 #include "nsIFrameInlines.h"
24 #include "imgIContainer.h"
25 #include "imgINotificationObserver.h"
26 #include "Image.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 {
41 NS_DECL_ISUPPORTS
42 NS_DECL_IMGINOTIFICATIONOBSERVER
44 GlobalImageObserver() = default;
46 private:
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)
55 NS_INTERFACE_MAP_END
57 // Data associated with every started load.
58 struct ImageTableEntry {
59 // Set of all ImageLoaders that have registered this URL and care for updates
60 // for it.
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"
72 // image request.
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;
80 /* static */
81 void ImageLoader::Init() {
82 sImages = new GlobalRequestTable();
83 sImageObserver = new GlobalImageObserver();
86 /* static */
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
92 // lot of other code.
93 auto* req = static_cast<imgRequestProxy*>(imgRequest);
94 req->SetCancelable(true);
95 req->CancelAndForgetObserver(NS_BINDING_ABORTED);
98 sImages = nullptr;
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());
110 mDocument = nullptr;
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));
122 return index;
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) {
128 uint32_t status = 0;
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.
135 return false;
137 if (status & imgIRequest::STATUS_ERROR) {
138 // Already errored, this would be useless.
139 return false;
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));
154 if (imgContainer) {
155 imgContainer->RequestDecodeForSize(gfx::IntSize(0, 0),
156 imgIContainer::DECODE_FLAGS_DEFAULT);
157 } else {
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);
162 return true;
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));
174 if (!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.
177 return;
179 MOZ_ASSERT(observer == sImageObserver);
182 auto* const frameSet =
183 mRequestToFrameMap
184 .LookupOrInsertWith(
185 aRequest,
186 [&] {
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);
193 } else {
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>();
204 .get();
206 auto* const requestSet =
207 mFrameToRequestMap
208 .LookupOrInsertWith(aFrame,
209 [=]() {
210 aFrame->SetHasImageRequest(true);
211 return MakeUnique<RequestSet>();
213 .get();
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.
221 bool found;
222 uint32_t i =
223 GetMaybeSortedIndex(*frameSet, fwf, &found, FrameOnlyComparator());
224 if (found) {
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
246 // block onload.
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.
262 if (!found) {
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);
269 if (!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,
279 nsIFrame* aFrame) {
280 #ifdef DEBUG
282 nsCOMPtr<imgINotificationObserver> observer;
283 aRequest->GetNotificationObserver(getter_AddRefs(observer));
284 MOZ_ASSERT(!observer || observer == sImageObserver);
286 #endif
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.
293 bool found;
294 uint32_t i = GetMaybeSortedIndex(*frameSet, FrameWithFlags(aFrame), &found,
295 FrameOnlyComparator());
296 if (found) {
297 UnblockOnloadIfNeeded(frameSet->ElementAt(i - 1));
298 frameSet->RemoveElementAtUnsafe(i - 1);
301 if (frameSet->IsEmpty()) {
302 DeregisterImageRequest(aRequest, GetPresContext());
303 entry.Remove();
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);
316 if (aPresContext) {
317 nsLayoutUtils::DeregisterImageRequest(aPresContext, aRequest, nullptr);
321 void ImageLoader::RemoveFrameToRequestMapping(imgIRequest* aRequest,
322 nsIFrame* aFrame) {
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);
329 entry.Remove();
334 void ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest,
335 nsIFrame* aFrame) {
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");
352 return;
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);
369 #ifdef DEBUG
371 nsCOMPtr<imgIRequest> debugRequest = request;
372 NS_ASSERTION(debugRequest == request, "This is bad");
374 #endif
376 nsCOMPtr<imgIContainer> container;
377 request->GetImage(getter_AddRefs(container));
378 if (!container) {
379 continue;
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);
393 #ifdef DEBUG
395 nsCOMPtr<imgIRequest> debugRequest = request;
396 NS_ASSERTION(debugRequest == request, "This is bad");
398 #endif
400 DeregisterImageRequest(request, aPresContext);
403 mRequestToFrameMap.Clear();
404 mFrameToRequestMap.Clear();
407 static CORSMode EffectiveCorsMode(nsIURI* aURI,
408 const StyleComputedImageUrl& aImage) {
409 MOZ_ASSERT(aURI);
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;
421 /* static */
422 already_AddRefed<imgRequestProxy> ImageLoader::LoadImage(
423 const StyleComputedImageUrl& aImage, Document& aDocument) {
424 MOZ_ASSERT(NS_IsMainThread());
425 nsIURI* uri = aImage.GetURI();
426 if (!uri) {
427 return nullptr;
430 if (aImage.HasRef()) {
431 bool isEqualExceptRef = false;
432 nsIURI* docURI = aDocument.GetDocumentURI();
433 if (NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &isEqualExceptRef)) &&
434 isEqualExceptRef) {
435 // Prevent loading an internal resource.
436 return nullptr;
440 int32_t loadFlags =
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) {
452 return nullptr;
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());
464 MOZ_ASSERT(aImage);
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)) {
473 return;
476 if (MOZ_UNLIKELY(--lookup.Data()->mSharedCount)) {
477 // Someone else still cares about this image.
478 return;
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 "
486 "by now");
487 lookup.Remove();
490 void ImageLoader::NoteSharedLoad(imgRequestProxy* aImage) {
491 MOZ_ASSERT(NS_IsMainThread());
492 MOZ_ASSERT(aImage);
494 auto lookup = sImages->Lookup(aImage);
495 MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?");
496 if (MOZ_UNLIKELY(!lookup)) {
497 return;
500 lookup.Data()->mSharedCount++;
503 nsPresContext* ImageLoader::GetPresContext() {
504 if (!mDocument) {
505 return nullptr;
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,
518 bool aForcePaint) {
519 if (!aFrame->StyleVisibility()->IsVisible()) {
520 return;
523 if (aFrame->IsFrameOfType(nsIFrame::eTablePart)) {
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())
547 ->SetInvalid(true);
549 // XXX: handle Blob data
550 invalidateFrame = true;
551 break;
552 case layers::WebRenderUserData::UserDataType::eMask:
553 static_cast<layers::WebRenderMaskData*>(data.get())->Invalidate();
554 invalidateFrame = true;
555 break;
556 case layers::WebRenderUserData::UserDataType::eImageProvider:
557 if (static_cast<layers::WebRenderImageProviderData*>(data.get())
558 ->Invalidate(aRequest->GetProviderId())) {
559 break;
561 [[fallthrough]];
562 default:
563 invalidateFrame = true;
564 break;
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) {
594 MOZ_ASSERT(aFrame);
595 MOZ_ASSERT(aRequest);
597 FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
598 if (!frameSet) {
599 return;
602 size_t i =
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;
613 WeakFrame mFrame;
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.
633 delete this;
635 // We don't need to trigger layout.
636 return false;
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.
648 delete this;
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)) {
656 return;
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) {
668 nsCString uriString;
669 if (profiler_is_active()) {
670 nsCOMPtr<nsIURI> uri;
671 aRequest->GetFinalURI(getter_AddRefs(uri));
672 if (uri) {
673 uri->GetSpec(uriString);
677 AUTO_PROFILER_LABEL_DYNAMIC_CSTR("ImageLoader::Notify", OTHER,
678 uriString.get());
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();
714 if (!presContext) {
715 return;
718 aImage->SetAnimationMode(presContext->ImageAnimationMode());
720 FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
721 if (!frameSet) {
722 return;
725 for (const FrameWithFlags& fwf : *frameSet) {
726 if (fwf.mFlags & Flags::RequiresReflowOnSizeAvailable) {
727 fwf.mFrame->PresShell()->FrameNeedsReflow(
728 fwf.mFrame, IntrinsicDirty::FrameAncestorsAndDescendants,
729 NS_FRAME_IS_DIRTY);
734 void ImageLoader::OnImageIsAnimated(imgIRequest* aRequest) {
735 if (!mDocument) {
736 return;
739 FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
740 if (!frameSet) {
741 return;
744 // Register with the refresh driver now that we are aware that
745 // we are animated.
746 nsPresContext* presContext = GetPresContext();
747 if (presContext) {
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) {
761 if (!mDocument) {
762 return;
765 FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
766 if (!frameSet) {
767 return;
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);
776 if (!aFirstFrame) {
777 // We don't reflow / try to unblock onload for subsequent frame updates.
778 continue;
780 if (fwf.mFlags &
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,
788 NS_FRAME_IS_DIRTY);
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) {
800 if (!mDocument) {
801 return;
804 uint32_t status = 0;
805 if (NS_FAILED(aRequest->GetImageStatus(&status))) {
806 return;
809 FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
810 if (!frameSet) {
811 return;
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