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/. */
6 
7 /* A class that handles style system image loads (other image loads are handled
8  * by the nodes in the content tree).
9  */
10 
11 #include "mozilla/css/ImageLoader.h"
12 
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"
31 #include "nsTHashSet.h"
32 
33 using namespace mozilla::dom;
34 
35 namespace mozilla::css {
36 
37 // This is a singleton observer which looks in the `GlobalRequestTable` to look
38 // at which loaders to notify.
39 struct GlobalImageObserver final : public imgINotificationObserver {
40   NS_DECL_ISUPPORTS
41   NS_DECL_IMGINOTIFICATIONOBSERVER
42 
43   GlobalImageObserver() = default;
44 
45  private:
46   virtual ~GlobalImageObserver() = default;
47 };
48 
49 NS_IMPL_ADDREF(GlobalImageObserver)
50 NS_IMPL_RELEASE(GlobalImageObserver)
51 
52 NS_INTERFACE_MAP_BEGIN(GlobalImageObserver)
53   NS_INTERFACE_MAP_ENTRY(imgINotificationObserver)
54 NS_INTERFACE_MAP_END
55 
56 // Data associated with every started load.
57 struct ImageTableEntry {
58   // Set of all ImageLoaders that have registered this URL and care for updates
59   // for it.
60   nsTHashSet<ImageLoader*> mImageLoaders;
61 
62   // The amount of style values that are sharing this image.
63   uint32_t mSharedCount = 1;
64 };
65 
66 using GlobalRequestTable =
67     nsClassHashtable<nsRefPtrHashKey<imgIRequest>, ImageTableEntry>;
68 
69 // A table of all loads, keyed by their id mapping them to the set of
70 // ImageLoaders they have been registered in, and recording their "canonical"
71 // image request.
72 //
73 // We use the load id as the key since we can only access sImages on the
74 // main thread, but LoadData objects might be destroyed from other threads,
75 // and we don't want to leave dangling pointers around.
76 static GlobalRequestTable* sImages = nullptr;
77 static StaticRefPtr<GlobalImageObserver> sImageObserver;
78 
79 /* static */
Init()80 void ImageLoader::Init() {
81   sImages = new GlobalRequestTable();
82   sImageObserver = new GlobalImageObserver();
83 }
84 
85 /* static */
Shutdown()86 void ImageLoader::Shutdown() {
87   for (const auto& entry : *sImages) {
88     entry.GetKey()->CancelAndForgetObserver(NS_BINDING_ABORTED);
89   }
90 
91   delete sImages;
92   sImages = nullptr;
93   sImageObserver = nullptr;
94 }
95 
DropDocumentReference()96 void ImageLoader::DropDocumentReference() {
97   MOZ_ASSERT(NS_IsMainThread());
98 
99   // It's okay if GetPresContext returns null here (due to the presshell pointer
100   // on the document being null) as that means the presshell has already
101   // been destroyed, and it also calls ClearFrames when it is destroyed.
102   ClearFrames(GetPresContext());
103 
104   mDocument = nullptr;
105 }
106 
107 // Arrays of requests and frames are sorted by their pointer address,
108 // for faster lookup.
109 template <typename Elem, typename Item,
110           typename Comparator = nsDefaultComparator<Elem, Item>>
GetMaybeSortedIndex(const nsTArray<Elem> & aArray,const Item & aItem,bool * aFound,Comparator aComparator=Comparator ())111 static size_t GetMaybeSortedIndex(const nsTArray<Elem>& aArray,
112                                   const Item& aItem, bool* aFound,
113                                   Comparator aComparator = Comparator()) {
114   size_t index = aArray.IndexOfFirstElementGt(aItem, aComparator);
115   *aFound = index > 0 && aComparator.Equals(aItem, aArray.ElementAt(index - 1));
116   return index;
117 }
118 
119 // Returns true if an async decode is triggered for aRequest, and thus we will
120 // get an OnFrameComplete callback for this request eventually.
TriggerAsyncDecodeAtIntrinsicSize(imgIRequest * aRequest)121 static bool TriggerAsyncDecodeAtIntrinsicSize(imgIRequest* aRequest) {
122   uint32_t status = 0;
123   // Don't block onload if we've already got a frame complete status
124   // (since in that case the image is already loaded), or if we get an
125   // error status (since then we know the image won't ever load).
126   if (NS_SUCCEEDED(aRequest->GetImageStatus(&status))) {
127     if (status & imgIRequest::STATUS_FRAME_COMPLETE) {
128       // Already decoded, no need to do it again.
129       return false;
130     }
131     if (status & imgIRequest::STATUS_ERROR) {
132       // Already errored, this would be useless.
133       return false;
134     }
135   }
136 
137   // We want to request decode in such a way that avoids triggering sync decode.
138   // First, we attempt to convert the aRequest into a imgIContainer. If that
139   // succeeds, then aRequest has an image and we can request decoding for size
140   // at zero size, the size will be ignored because we don't pass the
141   // FLAG_HIGH_QUALITY_SCALING flag and an async decode (because we didn't pass
142   // any sync decoding flags) at the intrinsic size will be requested. If the
143   // conversion to imgIContainer is unsuccessful, then that means aRequest
144   // doesn't have an image yet, which means we can safely call StartDecoding()
145   // on it without triggering any synchronous work.
146   nsCOMPtr<imgIContainer> imgContainer;
147   aRequest->GetImage(getter_AddRefs(imgContainer));
148   if (imgContainer) {
149     imgContainer->RequestDecodeForSize(gfx::IntSize(0, 0),
150                                        imgIContainer::DECODE_FLAGS_DEFAULT);
151   } else {
152     // It's safe to call StartDecoding directly, since it can't
153     // trigger synchronous decode without an image. Flags are ignored.
154     aRequest->StartDecoding(imgIContainer::FLAG_NONE);
155   }
156   return true;
157 }
158 
AssociateRequestToFrame(imgIRequest * aRequest,nsIFrame * aFrame,Flags aFlags)159 void ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest,
160                                           nsIFrame* aFrame, Flags aFlags) {
161   MOZ_ASSERT(NS_IsMainThread());
162   MOZ_ASSERT(!(aFlags & Flags::IsBlockingLoadEvent),
163              "Shouldn't be used in the public API");
164 
165   {
166     nsCOMPtr<imgINotificationObserver> observer;
167     aRequest->GetNotificationObserver(getter_AddRefs(observer));
168     if (!observer) {
169       // The request has already been canceled, so ignore it. This is ok because
170       // we're not going to get any more notifications from a canceled request.
171       return;
172     }
173     MOZ_ASSERT(observer == sImageObserver);
174   }
175 
176   auto* const frameSet =
177       mRequestToFrameMap
178           .LookupOrInsertWith(
179               aRequest,
180               [&] {
181                 mDocument->ImageTracker()->Add(aRequest);
182 
183                 if (auto entry = sImages->Lookup(aRequest)) {
184                   DebugOnly<bool> inserted =
185                       entry.Data()->mImageLoaders.EnsureInserted(this);
186                   MOZ_ASSERT(inserted);
187                 } else {
188                   MOZ_ASSERT_UNREACHABLE(
189                       "Shouldn't be associating images not in sImages");
190                 }
191 
192                 if (nsPresContext* presContext = GetPresContext()) {
193                   nsLayoutUtils::RegisterImageRequestIfAnimated(
194                       presContext, aRequest, nullptr);
195                 }
196                 return MakeUnique<FrameSet>();
197               })
198           .get();
199 
200   auto* const requestSet =
201       mFrameToRequestMap
202           .LookupOrInsertWith(aFrame,
203                               [=]() {
204                                 aFrame->SetHasImageRequest(true);
205                                 return MakeUnique<RequestSet>();
206                               })
207           .get();
208 
209   // Add frame to the frameSet, and handle any special processing the
210   // frame might require.
211   FrameWithFlags fwf(aFrame);
212   FrameWithFlags* fwfToModify = &fwf;
213 
214   // See if the frameSet already has this frame.
215   bool found;
216   uint32_t i =
217       GetMaybeSortedIndex(*frameSet, fwf, &found, FrameOnlyComparator());
218   if (found) {
219     // We're already tracking this frame, so prepare to modify the
220     // existing FrameWithFlags object.
221     fwfToModify = &frameSet->ElementAt(i - 1);
222   }
223 
224   // Check if the frame requires special processing.
225   if (aFlags & Flags::RequiresReflowOnSizeAvailable) {
226     MOZ_ASSERT(!(aFlags &
227                  Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking),
228                "These two are exclusive");
229     fwfToModify->mFlags |= Flags::RequiresReflowOnSizeAvailable;
230   }
231 
232   if (aFlags & Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking) {
233     fwfToModify->mFlags |=
234         Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking;
235 
236     // If we weren't already blocking onload, do that now.
237     if (!(fwfToModify->mFlags & Flags::IsBlockingLoadEvent)) {
238       if (TriggerAsyncDecodeAtIntrinsicSize(aRequest)) {
239         // If there's no error, and the image has not loaded yet, so we can
240         // block onload.
241         fwfToModify->mFlags |= Flags::IsBlockingLoadEvent;
242 
243         // Block document onload until we either remove the frame in
244         // RemoveRequestToFrameMapping or onLoadComplete, or complete a reflow.
245         mDocument->BlockOnload();
246       }
247     }
248   }
249 
250   // Do some sanity checking to ensure that we only add to one mapping
251   // iff we also add to the other mapping.
252   DebugOnly<bool> didAddToFrameSet(false);
253   DebugOnly<bool> didAddToRequestSet(false);
254 
255   // If we weren't already tracking this frame, add it to the frameSet.
256   if (!found) {
257     frameSet->InsertElementAt(i, fwf);
258     didAddToFrameSet = true;
259   }
260 
261   // Add request to the request set if it wasn't already there.
262   i = GetMaybeSortedIndex(*requestSet, aRequest, &found);
263   if (!found) {
264     requestSet->InsertElementAt(i, aRequest);
265     didAddToRequestSet = true;
266   }
267 
268   MOZ_ASSERT(didAddToFrameSet == didAddToRequestSet,
269              "We should only add to one map iff we also add to the other map.");
270 }
271 
RemoveRequestToFrameMapping(imgIRequest * aRequest,nsIFrame * aFrame)272 void ImageLoader::RemoveRequestToFrameMapping(imgIRequest* aRequest,
273                                               nsIFrame* aFrame) {
274 #ifdef DEBUG
275   {
276     nsCOMPtr<imgINotificationObserver> observer;
277     aRequest->GetNotificationObserver(getter_AddRefs(observer));
278     MOZ_ASSERT(!observer || observer == sImageObserver);
279   }
280 #endif
281 
282   if (auto entry = mRequestToFrameMap.Lookup(aRequest)) {
283     const auto& frameSet = entry.Data();
284     MOZ_ASSERT(frameSet, "This should never be null");
285 
286     // Before we remove aFrame from the frameSet, unblock onload if needed.
287     bool found;
288     uint32_t i = GetMaybeSortedIndex(*frameSet, FrameWithFlags(aFrame), &found,
289                                      FrameOnlyComparator());
290     if (found) {
291       UnblockOnloadIfNeeded(frameSet->ElementAt(i - 1));
292       frameSet->RemoveElementAt(i - 1);
293     }
294 
295     if (frameSet->IsEmpty()) {
296       DeregisterImageRequest(aRequest, GetPresContext());
297       entry.Remove();
298     }
299   }
300 }
301 
DeregisterImageRequest(imgIRequest * aRequest,nsPresContext * aPresContext)302 void ImageLoader::DeregisterImageRequest(imgIRequest* aRequest,
303                                          nsPresContext* aPresContext) {
304   mDocument->ImageTracker()->Remove(aRequest);
305 
306   if (auto entry = sImages->Lookup(aRequest)) {
307     entry.Data()->mImageLoaders.EnsureRemoved(this);
308   }
309 
310   if (aPresContext) {
311     nsLayoutUtils::DeregisterImageRequest(aPresContext, aRequest, nullptr);
312   }
313 }
314 
RemoveFrameToRequestMapping(imgIRequest * aRequest,nsIFrame * aFrame)315 void ImageLoader::RemoveFrameToRequestMapping(imgIRequest* aRequest,
316                                               nsIFrame* aFrame) {
317   if (auto entry = mFrameToRequestMap.Lookup(aFrame)) {
318     const auto& requestSet = entry.Data();
319     MOZ_ASSERT(requestSet, "This should never be null");
320     requestSet->RemoveElementSorted(aRequest);
321     if (requestSet->IsEmpty()) {
322       aFrame->SetHasImageRequest(false);
323       entry.Remove();
324     }
325   }
326 }
327 
DisassociateRequestFromFrame(imgIRequest * aRequest,nsIFrame * aFrame)328 void ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest,
329                                                nsIFrame* aFrame) {
330   MOZ_ASSERT(NS_IsMainThread());
331   MOZ_ASSERT(aFrame->HasImageRequest(), "why call me?");
332 
333   RemoveRequestToFrameMapping(aRequest, aFrame);
334   RemoveFrameToRequestMapping(aRequest, aFrame);
335 }
336 
DropRequestsForFrame(nsIFrame * aFrame)337 void ImageLoader::DropRequestsForFrame(nsIFrame* aFrame) {
338   MOZ_ASSERT(NS_IsMainThread());
339   MOZ_ASSERT(aFrame->HasImageRequest(), "why call me?");
340 
341   UniquePtr<RequestSet> requestSet;
342   mFrameToRequestMap.Remove(aFrame, &requestSet);
343   aFrame->SetHasImageRequest(false);
344   if (MOZ_UNLIKELY(!requestSet)) {
345     MOZ_ASSERT_UNREACHABLE("HasImageRequest was lying");
346     return;
347   }
348   for (imgIRequest* request : *requestSet) {
349     RemoveRequestToFrameMapping(request, aFrame);
350   }
351 }
352 
SetAnimationMode(uint16_t aMode)353 void ImageLoader::SetAnimationMode(uint16_t aMode) {
354   MOZ_ASSERT(NS_IsMainThread());
355   NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode ||
356                    aMode == imgIContainer::kDontAnimMode ||
357                    aMode == imgIContainer::kLoopOnceAnimMode,
358                "Wrong Animation Mode is being set!");
359 
360   for (nsISupports* key : mRequestToFrameMap.Keys()) {
361     auto* request = static_cast<imgIRequest*>(key);
362 
363 #ifdef DEBUG
364     {
365       nsCOMPtr<imgIRequest> debugRequest = request;
366       NS_ASSERTION(debugRequest == request, "This is bad");
367     }
368 #endif
369 
370     nsCOMPtr<imgIContainer> container;
371     request->GetImage(getter_AddRefs(container));
372     if (!container) {
373       continue;
374     }
375 
376     // This can fail if the image is in error, and we don't care.
377     container->SetAnimationMode(aMode);
378   }
379 }
380 
ClearFrames(nsPresContext * aPresContext)381 void ImageLoader::ClearFrames(nsPresContext* aPresContext) {
382   MOZ_ASSERT(NS_IsMainThread());
383 
384   for (const auto& key : mRequestToFrameMap.Keys()) {
385     auto* request = static_cast<imgIRequest*>(key);
386 
387 #ifdef DEBUG
388     {
389       nsCOMPtr<imgIRequest> debugRequest = request;
390       NS_ASSERTION(debugRequest == request, "This is bad");
391     }
392 #endif
393 
394     DeregisterImageRequest(request, aPresContext);
395   }
396 
397   mRequestToFrameMap.Clear();
398   mFrameToRequestMap.Clear();
399 }
400 
EffectiveCorsMode(nsIURI * aURI,const StyleComputedImageUrl & aImage)401 static CORSMode EffectiveCorsMode(nsIURI* aURI,
402                                   const StyleComputedImageUrl& aImage) {
403   MOZ_ASSERT(aURI);
404   StyleCorsMode mode = aImage.CorsMode();
405   if (mode == StyleCorsMode::None) {
406     return CORSMode::CORS_NONE;
407   }
408   MOZ_ASSERT(mode == StyleCorsMode::Anonymous);
409   if (aURI->SchemeIs("resource")) {
410     return CORSMode::CORS_NONE;
411   }
412   return CORSMode::CORS_ANONYMOUS;
413 }
414 
415 /* static */
LoadImage(const StyleComputedImageUrl & aImage,Document & aDocument)416 already_AddRefed<imgRequestProxy> ImageLoader::LoadImage(
417     const StyleComputedImageUrl& aImage, Document& aDocument) {
418   MOZ_ASSERT(NS_IsMainThread());
419   nsIURI* uri = aImage.GetURI();
420   if (!uri) {
421     return nullptr;
422   }
423 
424   if (aImage.HasRef()) {
425     bool isEqualExceptRef = false;
426     nsIURI* docURI = aDocument.GetDocumentURI();
427     if (NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &isEqualExceptRef)) &&
428         isEqualExceptRef) {
429       // Prevent loading an internal resource.
430       return nullptr;
431     }
432   }
433 
434   int32_t loadFlags =
435       nsIRequest::LOAD_NORMAL |
436       nsContentUtils::CORSModeToLoadImageFlags(EffectiveCorsMode(uri, aImage));
437 
438   const URLExtraData& data = aImage.ExtraData();
439 
440   RefPtr<imgRequestProxy> request;
441   nsresult rv = nsContentUtils::LoadImage(
442       uri, &aDocument, &aDocument, data.Principal(), 0, data.ReferrerInfo(),
443       sImageObserver, loadFlags, u"css"_ns, getter_AddRefs(request));
444 
445   if (NS_FAILED(rv) || !request) {
446     return nullptr;
447   }
448   sImages->GetOrInsertNew(request);
449   return request.forget();
450 }
451 
UnloadImage(imgRequestProxy * aImage)452 void ImageLoader::UnloadImage(imgRequestProxy* aImage) {
453   MOZ_ASSERT(NS_IsMainThread());
454   MOZ_ASSERT(aImage);
455 
456   if (MOZ_UNLIKELY(!sImages)) {
457     return;  // Shutdown() takes care of it.
458   }
459 
460   auto lookup = sImages->Lookup(aImage);
461   MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?");
462   if (MOZ_UNLIKELY(!lookup)) {
463     return;
464   }
465 
466   if (MOZ_UNLIKELY(--lookup.Data()->mSharedCount)) {
467     // Someone else still cares about this image.
468     return;
469   }
470 
471   aImage->CancelAndForgetObserver(NS_BINDING_ABORTED);
472   MOZ_DIAGNOSTIC_ASSERT(lookup.Data()->mImageLoaders.IsEmpty(),
473                         "Shouldn't be keeping references to any loader "
474                         "by now");
475   lookup.Remove();
476 }
477 
NoteSharedLoad(imgRequestProxy * aImage)478 void ImageLoader::NoteSharedLoad(imgRequestProxy* aImage) {
479   MOZ_ASSERT(NS_IsMainThread());
480   MOZ_ASSERT(aImage);
481 
482   auto lookup = sImages->Lookup(aImage);
483   MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?");
484   if (MOZ_UNLIKELY(!lookup)) {
485     return;
486   }
487 
488   lookup.Data()->mSharedCount++;
489 }
490 
GetPresContext()491 nsPresContext* ImageLoader::GetPresContext() {
492   if (!mDocument) {
493     return nullptr;
494   }
495 
496   return mDocument->GetPresContext();
497 }
498 
IsRenderNoImages(uint32_t aDisplayItemKey)499 static bool IsRenderNoImages(uint32_t aDisplayItemKey) {
500   DisplayItemType type = GetDisplayItemTypeFromKey(aDisplayItemKey);
501   uint8_t flags = GetDisplayItemFlagsForType(type);
502   return flags & TYPE_RENDERS_NO_IMAGES;
503 }
504 
InvalidateImages(nsIFrame * aFrame,imgIRequest * aRequest,bool aForcePaint)505 static void InvalidateImages(nsIFrame* aFrame, imgIRequest* aRequest,
506                              bool aForcePaint) {
507   if (!aFrame->StyleVisibility()->IsVisible()) {
508     return;
509   }
510 
511   if (aFrame->IsFrameOfType(nsIFrame::eTablePart)) {
512     // Tables don't necessarily build border/background display items
513     // for the individual table part frames, so IterateRetainedDataFor
514     // might not find the right display item.
515     return aFrame->InvalidateFrame();
516   }
517 
518   if (aFrame->IsPrimaryFrameOfRootOrBodyElement()) {
519     if (auto* canvas = aFrame->PresShell()->GetCanvasFrame()) {
520       // Try to invalidate the canvas too, in the probable case the background
521       // was propagated to it.
522       InvalidateImages(canvas, aRequest, aForcePaint);
523     }
524   }
525 
526   bool invalidateFrame = aForcePaint;
527   if (auto* array = aFrame->DisplayItemData()) {
528     for (auto* did : *array) {
529       DisplayItemData* data = DisplayItemData::AssertDisplayItemData(did);
530       uint32_t displayItemKey = data->GetDisplayItemKey();
531 
532       if (displayItemKey != 0 && !IsRenderNoImages(displayItemKey)) {
533         if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
534           DisplayItemType type = GetDisplayItemTypeFromKey(displayItemKey);
535           printf_stderr(
536               "Invalidating display item(type=%d) based on frame %p \
537                          because it might contain an invalidated image\n",
538               static_cast<uint32_t>(type), aFrame);
539         }
540 
541         data->Invalidate();
542         invalidateFrame = true;
543       }
544     }
545   }
546 
547   if (auto userDataTable =
548           aFrame->GetProperty(layers::WebRenderUserDataProperty::Key())) {
549     for (RefPtr<layers::WebRenderUserData> data : userDataTable->Values()) {
550       switch (data->GetType()) {
551         case layers::WebRenderUserData::UserDataType::eFallback:
552           if (!IsRenderNoImages(data->GetDisplayItemKey())) {
553             static_cast<layers::WebRenderFallbackData*>(data.get())
554                 ->SetInvalid(true);
555           }
556           // XXX: handle Blob data
557           invalidateFrame = true;
558           break;
559         case layers::WebRenderUserData::UserDataType::eImage:
560           if (static_cast<layers::WebRenderImageData*>(data.get())
561                   ->UsingSharedSurface(aRequest->GetProducerId())) {
562             break;
563           }
564           [[fallthrough]];
565         default:
566           invalidateFrame = true;
567           break;
568       }
569     }
570   }
571 
572   // Update ancestor rendering observers (-moz-element etc)
573   //
574   // NOTE: We need to do this even if invalidateFrame is false, see bug 1114526.
575   {
576     nsIFrame* f = aFrame;
577     while (f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) {
578       SVGObserverUtils::InvalidateDirectRenderingObservers(f);
579       f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f);
580     }
581   }
582 
583   if (invalidateFrame) {
584     aFrame->SchedulePaint();
585   }
586 }
587 
UnblockOnloadIfNeeded(FrameWithFlags & aFwf)588 void ImageLoader::UnblockOnloadIfNeeded(FrameWithFlags& aFwf) {
589   if (aFwf.mFlags & Flags::IsBlockingLoadEvent) {
590     mDocument->UnblockOnload(false);
591     aFwf.mFlags &= ~Flags::IsBlockingLoadEvent;
592   }
593 }
594 
UnblockOnloadIfNeeded(nsIFrame * aFrame,imgIRequest * aRequest)595 void ImageLoader::UnblockOnloadIfNeeded(nsIFrame* aFrame,
596                                         imgIRequest* aRequest) {
597   MOZ_ASSERT(aFrame);
598   MOZ_ASSERT(aRequest);
599 
600   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
601   if (!frameSet) {
602     return;
603   }
604 
605   size_t i =
606       frameSet->BinaryIndexOf(FrameWithFlags(aFrame), FrameOnlyComparator());
607   if (i != FrameSet::NoIndex) {
608     UnblockOnloadIfNeeded(frameSet->ElementAt(i));
609   }
610 }
611 
612 // This callback is used to unblock document onload after a reflow
613 // triggered from an image load.
614 struct ImageLoader::ImageReflowCallback final : public nsIReflowCallback {
615   RefPtr<ImageLoader> mLoader;
616   WeakFrame mFrame;
617   nsCOMPtr<imgIRequest> const mRequest;
618 
ImageReflowCallbackmozilla::css::ImageLoader::ImageReflowCallback619   ImageReflowCallback(ImageLoader* aLoader, nsIFrame* aFrame,
620                       imgIRequest* aRequest)
621       : mLoader(aLoader), mFrame(aFrame), mRequest(aRequest) {}
622 
623   bool ReflowFinished() override;
624   void ReflowCallbackCanceled() override;
625 };
626 
ReflowFinished()627 bool ImageLoader::ImageReflowCallback::ReflowFinished() {
628   // Check that the frame is still valid. If it isn't, then onload was
629   // unblocked when the frame was removed from the FrameSet in
630   // RemoveRequestToFrameMapping.
631   if (mFrame.IsAlive()) {
632     mLoader->UnblockOnloadIfNeeded(mFrame, mRequest);
633   }
634 
635   // Get rid of this callback object.
636   delete this;
637 
638   // We don't need to trigger layout.
639   return false;
640 }
641 
ReflowCallbackCanceled()642 void ImageLoader::ImageReflowCallback::ReflowCallbackCanceled() {
643   // Check that the frame is still valid. If it isn't, then onload was
644   // unblocked when the frame was removed from the FrameSet in
645   // RemoveRequestToFrameMapping.
646   if (mFrame.IsAlive()) {
647     mLoader->UnblockOnloadIfNeeded(mFrame, mRequest);
648   }
649 
650   // Get rid of this callback object.
651   delete this;
652 }
653 
Notify(imgIRequest * aRequest,int32_t aType,const nsIntRect * aData)654 void GlobalImageObserver::Notify(imgIRequest* aRequest, int32_t aType,
655                                  const nsIntRect* aData) {
656   auto entry = sImages->Lookup(aRequest);
657   MOZ_DIAGNOSTIC_ASSERT(entry);
658   if (MOZ_UNLIKELY(!entry)) {
659     return;
660   }
661 
662   const auto loadersToNotify =
663       ToTArray<nsTArray<RefPtr<ImageLoader>>>(entry.Data()->mImageLoaders);
664   for (const auto& loader : loadersToNotify) {
665     loader->Notify(aRequest, aType, aData);
666   }
667 }
668 
Notify(imgIRequest * aRequest,int32_t aType,const nsIntRect * aData)669 void ImageLoader::Notify(imgIRequest* aRequest, int32_t aType,
670                          const nsIntRect* aData) {
671   nsCString uriString;
672   if (profiler_is_active()) {
673     nsCOMPtr<nsIURI> uri;
674     aRequest->GetFinalURI(getter_AddRefs(uri));
675     if (uri) {
676       uri->GetSpec(uriString);
677     }
678   }
679 
680   AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("ImageLoader::Notify", OTHER,
681                                         uriString);
682 
683   if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
684     nsCOMPtr<imgIContainer> image;
685     aRequest->GetImage(getter_AddRefs(image));
686     return OnSizeAvailable(aRequest, image);
687   }
688 
689   if (aType == imgINotificationObserver::IS_ANIMATED) {
690     return OnImageIsAnimated(aRequest);
691   }
692 
693   if (aType == imgINotificationObserver::FRAME_COMPLETE) {
694     return OnFrameComplete(aRequest);
695   }
696 
697   if (aType == imgINotificationObserver::FRAME_UPDATE) {
698     return OnFrameUpdate(aRequest);
699   }
700 
701   if (aType == imgINotificationObserver::DECODE_COMPLETE) {
702     nsCOMPtr<imgIContainer> image;
703     aRequest->GetImage(getter_AddRefs(image));
704     if (image && mDocument) {
705       image->PropagateUseCounters(mDocument);
706     }
707   }
708 
709   if (aType == imgINotificationObserver::LOAD_COMPLETE) {
710     return OnLoadComplete(aRequest);
711   }
712 }
713 
OnSizeAvailable(imgIRequest * aRequest,imgIContainer * aImage)714 void ImageLoader::OnSizeAvailable(imgIRequest* aRequest,
715                                   imgIContainer* aImage) {
716   nsPresContext* presContext = GetPresContext();
717   if (!presContext) {
718     return;
719   }
720 
721   aImage->SetAnimationMode(presContext->ImageAnimationMode());
722 
723   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
724   if (!frameSet) {
725     return;
726   }
727 
728   for (const FrameWithFlags& fwf : *frameSet) {
729     if (fwf.mFlags & Flags::RequiresReflowOnSizeAvailable) {
730       fwf.mFrame->PresShell()->FrameNeedsReflow(
731           fwf.mFrame, IntrinsicDirty::StyleChange, NS_FRAME_IS_DIRTY);
732     }
733   }
734 }
735 
OnImageIsAnimated(imgIRequest * aRequest)736 void ImageLoader::OnImageIsAnimated(imgIRequest* aRequest) {
737   if (!mDocument) {
738     return;
739   }
740 
741   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
742   if (!frameSet) {
743     return;
744   }
745 
746   // Register with the refresh driver now that we are aware that
747   // we are animated.
748   nsPresContext* presContext = GetPresContext();
749   if (presContext) {
750     nsLayoutUtils::RegisterImageRequest(presContext, aRequest, nullptr);
751   }
752 }
753 
OnFrameComplete(imgIRequest * aRequest)754 void ImageLoader::OnFrameComplete(imgIRequest* aRequest) {
755   ImageFrameChanged(aRequest, /* aFirstFrame = */ true);
756 }
757 
OnFrameUpdate(imgIRequest * aRequest)758 void ImageLoader::OnFrameUpdate(imgIRequest* aRequest) {
759   ImageFrameChanged(aRequest, /* aFirstFrame = */ false);
760 }
761 
ImageFrameChanged(imgIRequest * aRequest,bool aFirstFrame)762 void ImageLoader::ImageFrameChanged(imgIRequest* aRequest, bool aFirstFrame) {
763   if (!mDocument) {
764     return;
765   }
766 
767   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
768   if (!frameSet) {
769     return;
770   }
771 
772   for (FrameWithFlags& fwf : *frameSet) {
773     // Since we just finished decoding a frame, we always want to paint, in
774     // case we're now able to paint an image that we couldn't paint before
775     // (and hence that we don't have retained data for).
776     const bool forceRepaint = aFirstFrame;
777     InvalidateImages(fwf.mFrame, aRequest, forceRepaint);
778     if (!aFirstFrame) {
779       // We don't reflow / try to unblock onload for subsequent frame updates.
780       continue;
781     }
782     if (fwf.mFlags &
783         Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking) {
784       // Tell the container of the frame to reflow because the image request
785       // has finished decoding its first frame.
786       // FIXME(emilio): Why requesting reflow on the _parent_?
787       nsIFrame* parent = fwf.mFrame->GetInFlowParent();
788       parent->PresShell()->FrameNeedsReflow(parent, IntrinsicDirty::StyleChange,
789                                             NS_FRAME_IS_DIRTY);
790       // If we need to also potentially unblock onload, do it once reflow is
791       // done, with a reflow callback.
792       if (fwf.mFlags & Flags::IsBlockingLoadEvent) {
793         auto* unblocker = new ImageReflowCallback(this, fwf.mFrame, aRequest);
794         parent->PresShell()->PostReflowCallback(unblocker);
795       }
796     }
797   }
798 }
799 
OnLoadComplete(imgIRequest * aRequest)800 void ImageLoader::OnLoadComplete(imgIRequest* aRequest) {
801   if (!mDocument) {
802     return;
803   }
804 
805   uint32_t status = 0;
806   if (NS_FAILED(aRequest->GetImageStatus(&status))) {
807     return;
808   }
809 
810   FrameSet* frameSet = mRequestToFrameMap.Get(aRequest);
811   if (!frameSet) {
812     return;
813   }
814 
815   for (FrameWithFlags& fwf : *frameSet) {
816     if (status & imgIRequest::STATUS_ERROR) {
817       // Check if aRequest has an error state. If it does, we need to unblock
818       // Document onload for all the frames associated with this request that
819       // have blocked onload. This is what happens in a CORS mode violation, and
820       // may happen during other network events.
821       UnblockOnloadIfNeeded(fwf);
822     }
823     if (fwf.mFrame->StyleVisibility()->IsVisible()) {
824       fwf.mFrame->SchedulePaint();
825     }
826   }
827 }
828 
829 }  // namespace mozilla::css
830