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