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