Bug 1698786: part 2) Change some compile-time dependent `printf`s to `MOZ_LOG` in...
[gecko.git] / layout / style / ImageLoader.cpp
blob53cd1e21df1c096996028671e2b2f601b86fe289
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/ImageTracker.h"
16 #include "nsContentUtils.h"
17 #include "nsIReflowCallback.h"
18 #include "nsLayoutUtils.h"
19 #include "nsError.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"
26 #include "Image.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 {
39 NS_DECL_ISUPPORTS
40 NS_DECL_IMGINOTIFICATIONOBSERVER
42 GlobalImageObserver() = default;
44 private:
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)
53 NS_INTERFACE_MAP_END
55 // Data associated with every started load.
56 struct ImageTableEntry {
57 // Set of all ImageLoaders that have registered this URL and care for updates
58 // for it.
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"
70 // image request.
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;
78 /* static */
79 void ImageLoader::Init() {
80 sImages = new GlobalRequestTable();
81 sImageObserver = new GlobalImageObserver();
84 /* static */
85 void ImageLoader::Shutdown() {
86 delete sImages;
87 sImages = nullptr;
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());
99 mDocument = nullptr;
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));
111 return index;
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) {
117 uint32_t status = 0;
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.
124 return false;
126 if (status & imgIRequest::STATUS_ERROR) {
127 // Already errored, this would be useless.
128 return false;
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));
143 if (imgContainer) {
144 imgContainer->RequestDecodeForSize(gfx::IntSize(0, 0),
145 imgIContainer::DECODE_FLAGS_DEFAULT);
146 } else {
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);
151 return true;
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));
163 if (!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.
166 return;
168 MOZ_ASSERT(observer == sImageObserver);
171 auto* const frameSet =
172 mRequestToFrameMap
173 .LookupOrInsertWith(
174 aRequest,
175 [&] {
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);
182 } else {
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>();
193 .get();
195 auto* const requestSet =
196 mFrameToRequestMap
197 .LookupOrInsertWith(aFrame,
198 [=]() {
199 aFrame->SetHasImageRequest(true);
200 return MakeUnique<RequestSet>();
202 .get();
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.
210 bool found;
211 uint32_t i =
212 GetMaybeSortedIndex(*frameSet, fwf, &found, FrameOnlyComparator());
213 if (found) {
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
235 // block onload.
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.
251 if (!found) {
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);
258 if (!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,
268 nsIFrame* aFrame) {
269 #ifdef DEBUG
271 nsCOMPtr<imgINotificationObserver> observer;
272 aRequest->GetNotificationObserver(getter_AddRefs(observer));
273 MOZ_ASSERT(!observer || observer == sImageObserver);
275 #endif
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.
282 bool found;
283 uint32_t i = GetMaybeSortedIndex(*frameSet, FrameWithFlags(aFrame), &found,
284 FrameOnlyComparator());
285 if (found) {
286 UnblockOnloadIfNeeded(frameSet->ElementAt(i - 1));
287 frameSet->RemoveElementAt(i - 1);
290 if (frameSet->IsEmpty()) {
291 DeregisterImageRequest(aRequest, GetPresContext());
292 entry.Remove();
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);
305 if (aPresContext) {
306 nsLayoutUtils::DeregisterImageRequest(aPresContext, aRequest, nullptr);
310 void ImageLoader::RemoveFrameToRequestMapping(imgIRequest* aRequest,
311 nsIFrame* aFrame) {
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);
318 entry.Remove();
323 void ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest,
324 nsIFrame* aFrame) {
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");
341 return;
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());
358 #ifdef DEBUG
360 nsCOMPtr<imgIRequest> debugRequest = request;
361 NS_ASSERTION(debugRequest == request, "This is bad");
363 #endif
365 nsCOMPtr<imgIContainer> container;
366 request->GetImage(getter_AddRefs(container));
367 if (!container) {
368 continue;
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());
382 #ifdef DEBUG
384 nsCOMPtr<imgIRequest> debugRequest = request;
385 NS_ASSERTION(debugRequest == request, "This is bad");
387 #endif
389 DeregisterImageRequest(request, aPresContext);
392 mRequestToFrameMap.Clear();
393 mFrameToRequestMap.Clear();
396 static CORSMode EffectiveCorsMode(nsIURI* aURI,
397 const StyleComputedImageUrl& aImage) {
398 MOZ_ASSERT(aURI);
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;
410 /* static */
411 already_AddRefed<imgRequestProxy> ImageLoader::LoadImage(
412 const StyleComputedImageUrl& aImage, Document& aDocument) {
413 MOZ_ASSERT(NS_IsMainThread());
414 nsIURI* uri = aImage.GetURI();
415 if (!uri) {
416 return nullptr;
419 if (aImage.HasRef()) {
420 bool isEqualExceptRef = false;
421 nsIURI* docURI = aDocument.GetDocumentURI();
422 if (NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &isEqualExceptRef)) &&
423 isEqualExceptRef) {
424 // Prevent loading an internal resource.
425 return nullptr;
429 int32_t loadFlags =
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) {
441 return nullptr;
443 sImages->GetOrInsertNew(request);
444 return request.forget();
447 void ImageLoader::UnloadImage(imgRequestProxy* aImage) {
448 MOZ_ASSERT(NS_IsMainThread());
449 MOZ_ASSERT(aImage);
451 auto lookup = sImages->Lookup(aImage);
452 MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?");
453 if (MOZ_UNLIKELY(!lookup)) {
454 return;
457 if (MOZ_UNLIKELY(--lookup.Data()->mSharedCount)) {
458 // Someone else still cares about this image.
459 return;
462 aImage->CancelAndForgetObserver(NS_BINDING_ABORTED);
463 MOZ_DIAGNOSTIC_ASSERT(lookup.Data()->mImageLoaders.IsEmpty(),
464 "Shouldn't be keeping references to any loader "
465 "by now");
466 lookup.Remove();
469 void ImageLoader::NoteSharedLoad(imgRequestProxy* aImage) {
470 MOZ_ASSERT(NS_IsMainThread());
471 MOZ_ASSERT(aImage);
473 auto lookup = sImages->Lookup(aImage);
474 MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?");
475 if (MOZ_UNLIKELY(!lookup)) {
476 return;
479 lookup.Data()->mSharedCount++;
482 nsPresContext* ImageLoader::GetPresContext() {
483 if (!mDocument) {
484 return nullptr;
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,
497 bool aForcePaint) {
498 if (!aFrame->StyleVisibility()->IsVisible()) {
499 return;
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);
526 printf_stderr(
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);
532 data->Invalidate();
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())
546 ->SetInvalid(true);
548 // XXX: handle Blob data
549 invalidateFrame = true;
550 break;
551 case layers::WebRenderUserData::UserDataType::eImage:
552 if (static_cast<layers::WebRenderImageData*>(data.get())
553 ->UsingSharedSurface(aRequest->GetProducerId())) {
554 break;
556 [[fallthrough]];
557 default:
558 invalidateFrame = true;
559 break;
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) {
589 MOZ_ASSERT(aFrame);
590 MOZ_ASSERT(aRequest);
592 FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
593 if (!frameSet) {
594 return;
597 size_t i =
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;
608 WeakFrame mFrame;
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.
628 delete this;
630 // We don't need to trigger layout.
631 return false;
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.
643 delete this;
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)) {
651 return;
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
667 nsCString uriString;
668 if (profiler_is_active()) {
669 nsCOMPtr<nsIURI> uri;
670 aRequest->GetFinalURI(getter_AddRefs(uri));
671 if (uri) {
672 uri->GetSpec(uriString);
676 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("ImageLoader::Notify", OTHER,
677 uriString);
678 #endif
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::StyleChange, NS_FRAME_IS_DIRTY);
733 void ImageLoader::OnImageIsAnimated(imgIRequest* aRequest) {
734 if (!mDocument) {
735 return;
738 FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
739 if (!frameSet) {
740 return;
743 // Register with the refresh driver now that we are aware that
744 // we are animated.
745 nsPresContext* presContext = GetPresContext();
746 if (presContext) {
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) {
760 if (!mDocument) {
761 return;
764 FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
765 if (!frameSet) {
766 return;
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);
775 if (!aFirstFrame) {
776 // We don't reflow / try to unblock onload for subsequent frame updates.
777 continue;
779 if (fwf.mFlags &
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,
786 NS_FRAME_IS_DIRTY);
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) {
798 if (!mDocument) {
799 return;
802 uint32_t status = 0;
803 if (NS_FAILED(aRequest->GetImageStatus(&status))) {
804 return;
807 FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
808 if (!frameSet) {
809 return;
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