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