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