diff options
Diffstat (limited to 'layout/style/ImageLoader.cpp')
-rw-r--r-- | layout/style/ImageLoader.cpp | 834 |
1 files changed, 834 insertions, 0 deletions
diff --git a/layout/style/ImageLoader.cpp b/layout/style/ImageLoader.cpp new file mode 100644 index 0000000000..2ae1822b02 --- /dev/null +++ b/layout/style/ImageLoader.cpp @@ -0,0 +1,834 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* A class that handles style system image loads (other image loads are handled + * by the nodes in the content tree). + */ + +#include "mozilla/css/ImageLoader.h" + +#include "mozilla/dom/Document.h" +#include "mozilla/dom/DocumentInlines.h" +#include "mozilla/dom/LargestContentfulPaint.h" +#include "mozilla/dom/ImageTracker.h" +#include "nsContentUtils.h" +#include "nsIReflowCallback.h" +#include "nsLayoutUtils.h" +#include "nsError.h" +#include "nsCanvasFrame.h" +#include "nsDisplayList.h" +#include "nsIFrameInlines.h" +#include "imgIContainer.h" +#include "imgINotificationObserver.h" +#include "Image.h" +#include "mozilla/PresShell.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/layers/WebRenderUserData.h" +#include "nsTHashSet.h" + +using namespace mozilla::dom; + +namespace mozilla::css { + +// This is a singleton observer which looks in the `GlobalRequestTable` to look +// at which loaders to notify. +struct GlobalImageObserver final : public imgINotificationObserver { + NS_DECL_ISUPPORTS + NS_DECL_IMGINOTIFICATIONOBSERVER + + GlobalImageObserver() = default; + + private: + virtual ~GlobalImageObserver() = default; +}; + +NS_IMPL_ADDREF(GlobalImageObserver) +NS_IMPL_RELEASE(GlobalImageObserver) + +NS_INTERFACE_MAP_BEGIN(GlobalImageObserver) + NS_INTERFACE_MAP_ENTRY(imgINotificationObserver) +NS_INTERFACE_MAP_END + +// Data associated with every started load. +struct ImageTableEntry { + // Set of all ImageLoaders that have registered this URL and care for updates + // for it. + nsTHashSet<ImageLoader*> mImageLoaders; + + // The amount of style values that are sharing this image. + uint32_t mSharedCount = 1; +}; + +using GlobalRequestTable = + nsClassHashtable<nsRefPtrHashKey<imgIRequest>, ImageTableEntry>; + +// A table of all loads, keyed by their id mapping them to the set of +// ImageLoaders they have been registered in, and recording their "canonical" +// image request. +// +// We use the load id as the key since we can only access sImages on the +// main thread, but LoadData objects might be destroyed from other threads, +// and we don't want to leave dangling pointers around. +static StaticAutoPtr<GlobalRequestTable> sImages; +static StaticRefPtr<GlobalImageObserver> sImageObserver; + +/* static */ +void ImageLoader::Init() { + sImages = new GlobalRequestTable(); + sImageObserver = new GlobalImageObserver(); +} + +/* static */ +void ImageLoader::Shutdown() { + for (const auto& entry : *sImages) { + imgIRequest* imgRequest = entry.GetKey(); + // All the images we put in sImages are imgRequestProxy, see LoadImage, but + // it's non-trivial to make the hash table to use that without changing a + // lot of other code. + auto* req = static_cast<imgRequestProxy*>(imgRequest); + req->SetCancelable(true); + req->CancelAndForgetObserver(NS_BINDING_ABORTED); + } + + sImages = nullptr; + sImageObserver = nullptr; +} + +void ImageLoader::DropDocumentReference() { + MOZ_ASSERT(NS_IsMainThread()); + + // It's okay if GetPresContext returns null here (due to the presshell pointer + // on the document being null) as that means the presshell has already + // been destroyed, and it also calls ClearFrames when it is destroyed. + ClearFrames(GetPresContext()); + + mDocument = nullptr; +} + +// Arrays of requests and frames are sorted by their pointer address, +// for faster lookup. +template <typename Elem, typename Item, + typename Comparator = nsDefaultComparator<Elem, Item>> +static size_t GetMaybeSortedIndex(const nsTArray<Elem>& aArray, + const Item& aItem, bool* aFound, + Comparator aComparator = Comparator()) { + size_t index = aArray.IndexOfFirstElementGt(aItem, aComparator); + *aFound = index > 0 && aComparator.Equals(aItem, aArray.ElementAt(index - 1)); + return index; +} + +// Returns true if an async decode is triggered for aRequest, and thus we will +// get an OnFrameComplete callback for this request eventually. +static bool TriggerAsyncDecodeAtIntrinsicSize(imgIRequest* aRequest) { + uint32_t status = 0; + // Don't block onload if we've already got a frame complete status + // (since in that case the image is already loaded), or if we get an + // error status (since then we know the image won't ever load). + if (NS_SUCCEEDED(aRequest->GetImageStatus(&status))) { + if (status & imgIRequest::STATUS_FRAME_COMPLETE) { + // Already decoded, no need to do it again. + return false; + } + if (status & imgIRequest::STATUS_ERROR) { + // Already errored, this would be useless. + return false; + } + } + + // We want to request decode in such a way that avoids triggering sync decode. + // First, we attempt to convert the aRequest into a imgIContainer. If that + // succeeds, then aRequest has an image and we can request decoding for size + // at zero size, the size will be ignored because we don't pass the + // FLAG_HIGH_QUALITY_SCALING flag and an async decode (because we didn't pass + // any sync decoding flags) at the intrinsic size will be requested. If the + // conversion to imgIContainer is unsuccessful, then that means aRequest + // doesn't have an image yet, which means we can safely call StartDecoding() + // on it without triggering any synchronous work. + nsCOMPtr<imgIContainer> imgContainer; + aRequest->GetImage(getter_AddRefs(imgContainer)); + if (imgContainer) { + imgContainer->RequestDecodeForSize(gfx::IntSize(0, 0), + imgIContainer::DECODE_FLAGS_DEFAULT); + } else { + // It's safe to call StartDecoding directly, since it can't + // trigger synchronous decode without an image. Flags are ignored. + aRequest->StartDecoding(imgIContainer::FLAG_NONE); + } + return true; +} + +void ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest, + nsIFrame* aFrame, Flags aFlags) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!(aFlags & Flags::IsBlockingLoadEvent), + "Shouldn't be used in the public API"); + + { + nsCOMPtr<imgINotificationObserver> observer; + aRequest->GetNotificationObserver(getter_AddRefs(observer)); + if (!observer) { + // The request has already been canceled, so ignore it. This is ok because + // we're not going to get any more notifications from a canceled request. + return; + } + MOZ_ASSERT(observer == sImageObserver); + } + + auto* const frameSet = + mRequestToFrameMap + .LookupOrInsertWith( + aRequest, + [&] { + mDocument->ImageTracker()->Add(aRequest); + + if (auto entry = sImages->Lookup(aRequest)) { + DebugOnly<bool> inserted = + entry.Data()->mImageLoaders.EnsureInserted(this); + MOZ_ASSERT(inserted); + } else { + MOZ_ASSERT_UNREACHABLE( + "Shouldn't be associating images not in sImages"); + } + + if (nsPresContext* presContext = GetPresContext()) { + nsLayoutUtils::RegisterImageRequestIfAnimated( + presContext, aRequest, nullptr); + } + return MakeUnique<FrameSet>(); + }) + .get(); + + auto* const requestSet = + mFrameToRequestMap + .LookupOrInsertWith(aFrame, + [=]() { + aFrame->SetHasImageRequest(true); + return MakeUnique<RequestSet>(); + }) + .get(); + + // Add frame to the frameSet, and handle any special processing the + // frame might require. + FrameWithFlags fwf(aFrame); + FrameWithFlags* fwfToModify = &fwf; + + // See if the frameSet already has this frame. + bool found; + uint32_t i = + GetMaybeSortedIndex(*frameSet, fwf, &found, FrameOnlyComparator()); + if (found) { + // We're already tracking this frame, so prepare to modify the + // existing FrameWithFlags object. + fwfToModify = &frameSet->ElementAt(i - 1); + } + + // Check if the frame requires special processing. + if (aFlags & Flags::RequiresReflowOnSizeAvailable) { + MOZ_ASSERT(!(aFlags & + Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking), + "These two are exclusive"); + fwfToModify->mFlags |= Flags::RequiresReflowOnSizeAvailable; + } + + if (aFlags & Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking) { + fwfToModify->mFlags |= + Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking; + + // If we weren't already blocking onload, do that now. + if (!(fwfToModify->mFlags & Flags::IsBlockingLoadEvent)) { + if (TriggerAsyncDecodeAtIntrinsicSize(aRequest)) { + // If there's no error, and the image has not loaded yet, so we can + // block onload. + fwfToModify->mFlags |= Flags::IsBlockingLoadEvent; + + // Block document onload until we either remove the frame in + // RemoveRequestToFrameMapping or onLoadComplete, or complete a reflow. + mDocument->BlockOnload(); + } + } + } + + // Do some sanity checking to ensure that we only add to one mapping + // iff we also add to the other mapping. + DebugOnly<bool> didAddToFrameSet(false); + DebugOnly<bool> didAddToRequestSet(false); + + // If we weren't already tracking this frame, add it to the frameSet. + if (!found) { + frameSet->InsertElementAt(i, fwf); + didAddToFrameSet = true; + } + + // Add request to the request set if it wasn't already there. + i = GetMaybeSortedIndex(*requestSet, aRequest, &found); + if (!found) { + requestSet->InsertElementAt(i, aRequest); + didAddToRequestSet = true; + } + + MOZ_ASSERT(didAddToFrameSet == didAddToRequestSet, + "We should only add to one map iff we also add to the other map."); +} + +void ImageLoader::RemoveRequestToFrameMapping(imgIRequest* aRequest, + nsIFrame* aFrame) { +#ifdef DEBUG + { + nsCOMPtr<imgINotificationObserver> observer; + aRequest->GetNotificationObserver(getter_AddRefs(observer)); + MOZ_ASSERT(!observer || observer == sImageObserver); + } +#endif + + if (auto entry = mRequestToFrameMap.Lookup(aRequest)) { + const auto& frameSet = entry.Data(); + MOZ_ASSERT(frameSet, "This should never be null"); + + // Before we remove aFrame from the frameSet, unblock onload if needed. + bool found; + uint32_t i = GetMaybeSortedIndex(*frameSet, FrameWithFlags(aFrame), &found, + FrameOnlyComparator()); + if (found) { + UnblockOnloadIfNeeded(frameSet->ElementAt(i - 1)); + frameSet->RemoveElementAtUnsafe(i - 1); + } + + if (frameSet->IsEmpty()) { + DeregisterImageRequest(aRequest, GetPresContext()); + entry.Remove(); + } + } +} + +void ImageLoader::DeregisterImageRequest(imgIRequest* aRequest, + nsPresContext* aPresContext) { + mDocument->ImageTracker()->Remove(aRequest); + + if (auto entry = sImages->Lookup(aRequest)) { + entry.Data()->mImageLoaders.EnsureRemoved(this); + } + + if (aPresContext) { + nsLayoutUtils::DeregisterImageRequest(aPresContext, aRequest, nullptr); + } +} + +void ImageLoader::RemoveFrameToRequestMapping(imgIRequest* aRequest, + nsIFrame* aFrame) { + if (auto entry = mFrameToRequestMap.Lookup(aFrame)) { + const auto& requestSet = entry.Data(); + MOZ_ASSERT(requestSet, "This should never be null"); + requestSet->RemoveElementSorted(aRequest); + if (requestSet->IsEmpty()) { + aFrame->SetHasImageRequest(false); + entry.Remove(); + } + } +} + +void ImageLoader::DisassociateRequestFromFrame(imgIRequest* aRequest, + nsIFrame* aFrame) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aFrame->HasImageRequest(), "why call me?"); + + RemoveRequestToFrameMapping(aRequest, aFrame); + RemoveFrameToRequestMapping(aRequest, aFrame); +} + +void ImageLoader::DropRequestsForFrame(nsIFrame* aFrame) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aFrame->HasImageRequest(), "why call me?"); + + UniquePtr<RequestSet> requestSet; + mFrameToRequestMap.Remove(aFrame, &requestSet); + aFrame->SetHasImageRequest(false); + if (MOZ_UNLIKELY(!requestSet)) { + MOZ_ASSERT_UNREACHABLE("HasImageRequest was lying"); + return; + } + for (imgIRequest* request : *requestSet) { + RemoveRequestToFrameMapping(request, aFrame); + } +} + +void ImageLoader::SetAnimationMode(uint16_t aMode) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode || + aMode == imgIContainer::kDontAnimMode || + aMode == imgIContainer::kLoopOnceAnimMode, + "Wrong Animation Mode is being set!"); + + for (nsISupports* key : mRequestToFrameMap.Keys()) { + auto* request = static_cast<imgIRequest*>(key); + +#ifdef DEBUG + { + nsCOMPtr<imgIRequest> debugRequest = request; + NS_ASSERTION(debugRequest == request, "This is bad"); + } +#endif + + nsCOMPtr<imgIContainer> container; + request->GetImage(getter_AddRefs(container)); + if (!container) { + continue; + } + + // This can fail if the image is in error, and we don't care. + container->SetAnimationMode(aMode); + } +} + +void ImageLoader::ClearFrames(nsPresContext* aPresContext) { + MOZ_ASSERT(NS_IsMainThread()); + + for (const auto& key : mRequestToFrameMap.Keys()) { + auto* request = static_cast<imgIRequest*>(key); + +#ifdef DEBUG + { + nsCOMPtr<imgIRequest> debugRequest = request; + NS_ASSERTION(debugRequest == request, "This is bad"); + } +#endif + + DeregisterImageRequest(request, aPresContext); + } + + mRequestToFrameMap.Clear(); + mFrameToRequestMap.Clear(); +} + +static CORSMode EffectiveCorsMode(nsIURI* aURI, + const StyleComputedImageUrl& aImage) { + MOZ_ASSERT(aURI); + StyleCorsMode mode = aImage.CorsMode(); + if (mode == StyleCorsMode::None) { + return CORSMode::CORS_NONE; + } + MOZ_ASSERT(mode == StyleCorsMode::Anonymous); + if (aURI->SchemeIs("resource")) { + return CORSMode::CORS_NONE; + } + return CORSMode::CORS_ANONYMOUS; +} + +/* static */ +already_AddRefed<imgRequestProxy> ImageLoader::LoadImage( + const StyleComputedImageUrl& aImage, Document& aDocument) { + MOZ_ASSERT(NS_IsMainThread()); + nsIURI* uri = aImage.GetURI(); + if (!uri) { + return nullptr; + } + + if (aImage.HasRef()) { + bool isEqualExceptRef = false; + nsIURI* docURI = aDocument.GetDocumentURI(); + if (NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &isEqualExceptRef)) && + isEqualExceptRef) { + // Prevent loading an internal resource. + return nullptr; + } + } + + int32_t loadFlags = + nsIRequest::LOAD_NORMAL | + nsContentUtils::CORSModeToLoadImageFlags(EffectiveCorsMode(uri, aImage)); + + const URLExtraData& data = aImage.ExtraData(); + + RefPtr<imgRequestProxy> request; + nsresult rv = nsContentUtils::LoadImage( + uri, &aDocument, &aDocument, data.Principal(), 0, data.ReferrerInfo(), + sImageObserver, loadFlags, u"css"_ns, getter_AddRefs(request)); + + if (NS_FAILED(rv) || !request) { + return nullptr; + } + + // This image could be shared across documents, so its load cannot be + // canceled, see bug 1800979. + request->SetCancelable(false); + sImages->GetOrInsertNew(request); + return request.forget(); +} + +void ImageLoader::UnloadImage(imgRequestProxy* aImage) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aImage); + + if (MOZ_UNLIKELY(!sImages)) { + return; // Shutdown() takes care of it. + } + + auto lookup = sImages->Lookup(aImage); + MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?"); + if (MOZ_UNLIKELY(!lookup)) { + return; + } + + if (MOZ_UNLIKELY(--lookup.Data()->mSharedCount)) { + // Someone else still cares about this image. + return; + } + + // Now we want to really cancel the request. + aImage->SetCancelable(true); + aImage->CancelAndForgetObserver(NS_BINDING_ABORTED); + MOZ_DIAGNOSTIC_ASSERT(lookup.Data()->mImageLoaders.IsEmpty(), + "Shouldn't be keeping references to any loader " + "by now"); + lookup.Remove(); +} + +void ImageLoader::NoteSharedLoad(imgRequestProxy* aImage) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aImage); + + auto lookup = sImages->Lookup(aImage); + MOZ_DIAGNOSTIC_ASSERT(lookup, "Unregistered image?"); + if (MOZ_UNLIKELY(!lookup)) { + return; + } + + lookup.Data()->mSharedCount++; +} + +nsPresContext* ImageLoader::GetPresContext() { + if (!mDocument) { + return nullptr; + } + + return mDocument->GetPresContext(); +} + +static bool IsRenderNoImages(uint32_t aDisplayItemKey) { + DisplayItemType type = GetDisplayItemTypeFromKey(aDisplayItemKey); + uint8_t flags = GetDisplayItemFlagsForType(type); + return flags & TYPE_RENDERS_NO_IMAGES; +} + +static void InvalidateImages(nsIFrame* aFrame, imgIRequest* aRequest, + bool aForcePaint) { + if (!aFrame->StyleVisibility()->IsVisible()) { + return; + } + + if (aFrame->IsTablePart()) { + // Tables don't necessarily build border/background display items + // for the individual table part frames, so IterateRetainedDataFor + // might not find the right display item. + return aFrame->InvalidateFrame(); + } + + if (aFrame->IsPrimaryFrameOfRootOrBodyElement()) { + if (auto* canvas = aFrame->PresShell()->GetCanvasFrame()) { + // Try to invalidate the canvas too, in the probable case the background + // was propagated to it. + InvalidateImages(canvas, aRequest, aForcePaint); + } + } + + bool invalidateFrame = aForcePaint; + + if (auto userDataTable = + aFrame->GetProperty(layers::WebRenderUserDataProperty::Key())) { + for (RefPtr<layers::WebRenderUserData> data : userDataTable->Values()) { + switch (data->GetType()) { + case layers::WebRenderUserData::UserDataType::eFallback: + if (!IsRenderNoImages(data->GetDisplayItemKey())) { + static_cast<layers::WebRenderFallbackData*>(data.get()) + ->SetInvalid(true); + } + // XXX: handle Blob data + invalidateFrame = true; + break; + case layers::WebRenderUserData::UserDataType::eMask: + static_cast<layers::WebRenderMaskData*>(data.get())->Invalidate(); + invalidateFrame = true; + break; + case layers::WebRenderUserData::UserDataType::eImageProvider: + if (static_cast<layers::WebRenderImageProviderData*>(data.get()) + ->Invalidate(aRequest->GetProviderId())) { + break; + } + [[fallthrough]]; + default: + invalidateFrame = true; + break; + } + } + } + + // Update ancestor rendering observers (-moz-element etc) + // + // NOTE: We need to do this even if invalidateFrame is false, see bug 1114526. + { + nsIFrame* f = aFrame; + while (f && !f->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT)) { + SVGObserverUtils::InvalidateDirectRenderingObservers(f); + f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f); + } + } + + if (invalidateFrame) { + aFrame->SchedulePaint(); + } +} + +void ImageLoader::UnblockOnloadIfNeeded(FrameWithFlags& aFwf) { + if (aFwf.mFlags & Flags::IsBlockingLoadEvent) { + mDocument->UnblockOnload(false); + aFwf.mFlags &= ~Flags::IsBlockingLoadEvent; + } +} + +void ImageLoader::UnblockOnloadIfNeeded(nsIFrame* aFrame, + imgIRequest* aRequest) { + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aRequest); + + FrameSet* frameSet = mRequestToFrameMap.Get(aRequest); + if (!frameSet) { + return; + } + + size_t i = + frameSet->BinaryIndexOf(FrameWithFlags(aFrame), FrameOnlyComparator()); + if (i != FrameSet::NoIndex) { + UnblockOnloadIfNeeded(frameSet->ElementAt(i)); + } +} + +// This callback is used to unblock document onload after a reflow +// triggered from an image load. +struct ImageLoader::ImageReflowCallback final : public nsIReflowCallback { + RefPtr<ImageLoader> mLoader; + WeakFrame mFrame; + nsCOMPtr<imgIRequest> const mRequest; + + ImageReflowCallback(ImageLoader* aLoader, nsIFrame* aFrame, + imgIRequest* aRequest) + : mLoader(aLoader), mFrame(aFrame), mRequest(aRequest) {} + + bool ReflowFinished() override; + void ReflowCallbackCanceled() override; +}; + +bool ImageLoader::ImageReflowCallback::ReflowFinished() { + // Check that the frame is still valid. If it isn't, then onload was + // unblocked when the frame was removed from the FrameSet in + // RemoveRequestToFrameMapping. + if (mFrame.IsAlive()) { + mLoader->UnblockOnloadIfNeeded(mFrame, mRequest); + } + + // Get rid of this callback object. + delete this; + + // We don't need to trigger layout. + return false; +} + +void ImageLoader::ImageReflowCallback::ReflowCallbackCanceled() { + // Check that the frame is still valid. If it isn't, then onload was + // unblocked when the frame was removed from the FrameSet in + // RemoveRequestToFrameMapping. + if (mFrame.IsAlive()) { + mLoader->UnblockOnloadIfNeeded(mFrame, mRequest); + } + + // Get rid of this callback object. + delete this; +} + +void GlobalImageObserver::Notify(imgIRequest* aRequest, int32_t aType, + const nsIntRect* aData) { + auto entry = sImages->Lookup(aRequest); + MOZ_DIAGNOSTIC_ASSERT(entry); + if (MOZ_UNLIKELY(!entry)) { + return; + } + + const auto loadersToNotify = + ToTArray<nsTArray<RefPtr<ImageLoader>>>(entry.Data()->mImageLoaders); + for (const auto& loader : loadersToNotify) { + loader->Notify(aRequest, aType, aData); + } +} + +void ImageLoader::Notify(imgIRequest* aRequest, int32_t aType, + const nsIntRect* aData) { + nsCString uriString; + if (profiler_is_active()) { + nsCOMPtr<nsIURI> uri; + aRequest->GetFinalURI(getter_AddRefs(uri)); + if (uri) { + uri->GetSpec(uriString); + } + } + + AUTO_PROFILER_LABEL_DYNAMIC_CSTR("ImageLoader::Notify", OTHER, + uriString.get()); + + if (aType == imgINotificationObserver::SIZE_AVAILABLE) { + nsCOMPtr<imgIContainer> image; + aRequest->GetImage(getter_AddRefs(image)); + return OnSizeAvailable(aRequest, image); + } + + if (aType == imgINotificationObserver::IS_ANIMATED) { + return OnImageIsAnimated(aRequest); + } + + if (aType == imgINotificationObserver::FRAME_COMPLETE) { + return OnFrameComplete(aRequest); + } + + if (aType == imgINotificationObserver::FRAME_UPDATE) { + return OnFrameUpdate(aRequest); + } + + if (aType == imgINotificationObserver::DECODE_COMPLETE) { + nsCOMPtr<imgIContainer> image; + aRequest->GetImage(getter_AddRefs(image)); + if (image && mDocument) { + image->PropagateUseCounters(mDocument); + } + } + + if (aType == imgINotificationObserver::LOAD_COMPLETE) { + return OnLoadComplete(aRequest); + } +} + +void ImageLoader::OnSizeAvailable(imgIRequest* aRequest, + imgIContainer* aImage) { + nsPresContext* presContext = GetPresContext(); + if (!presContext) { + return; + } + + aImage->SetAnimationMode(presContext->ImageAnimationMode()); + + FrameSet* frameSet = mRequestToFrameMap.Get(aRequest); + if (!frameSet) { + return; + } + + for (const FrameWithFlags& fwf : *frameSet) { + if (fwf.mFlags & Flags::RequiresReflowOnSizeAvailable) { + fwf.mFrame->PresShell()->FrameNeedsReflow( + fwf.mFrame, IntrinsicDirty::FrameAncestorsAndDescendants, + NS_FRAME_IS_DIRTY); + } + } +} + +void ImageLoader::OnImageIsAnimated(imgIRequest* aRequest) { + if (!mDocument) { + return; + } + + FrameSet* frameSet = mRequestToFrameMap.Get(aRequest); + if (!frameSet) { + return; + } + + // Register with the refresh driver now that we are aware that + // we are animated. + nsPresContext* presContext = GetPresContext(); + if (presContext) { + nsLayoutUtils::RegisterImageRequest(presContext, aRequest, nullptr); + } +} + +void ImageLoader::OnFrameComplete(imgIRequest* aRequest) { + ImageFrameChanged(aRequest, /* aFirstFrame = */ true); +} + +void ImageLoader::OnFrameUpdate(imgIRequest* aRequest) { + ImageFrameChanged(aRequest, /* aFirstFrame = */ false); +} + +void ImageLoader::ImageFrameChanged(imgIRequest* aRequest, bool aFirstFrame) { + if (!mDocument) { + return; + } + + FrameSet* frameSet = mRequestToFrameMap.Get(aRequest); + if (!frameSet) { + return; + } + + for (FrameWithFlags& fwf : *frameSet) { + // Since we just finished decoding a frame, we always want to paint, in + // case we're now able to paint an image that we couldn't paint before + // (and hence that we don't have retained data for). + const bool forceRepaint = aFirstFrame; + InvalidateImages(fwf.mFrame, aRequest, forceRepaint); + if (!aFirstFrame) { + // We don't reflow / try to unblock onload for subsequent frame updates. + continue; + } + if (fwf.mFlags & + Flags::RequiresReflowOnFirstFrameCompleteAndLoadEventBlocking) { + // Tell the container of the frame to reflow because the image request + // has finished decoding its first frame. + // FIXME(emilio): Why requesting reflow on the _parent_? + nsIFrame* parent = fwf.mFrame->GetInFlowParent(); + parent->PresShell()->FrameNeedsReflow( + parent, IntrinsicDirty::FrameAncestorsAndDescendants, + NS_FRAME_IS_DIRTY); + // If we need to also potentially unblock onload, do it once reflow is + // done, with a reflow callback. + if (fwf.mFlags & Flags::IsBlockingLoadEvent) { + auto* unblocker = new ImageReflowCallback(this, fwf.mFrame, aRequest); + parent->PresShell()->PostReflowCallback(unblocker); + } + } + } +} + +void ImageLoader::OnLoadComplete(imgIRequest* aRequest) { + if (!mDocument) { + return; + } + + uint32_t status = 0; + if (NS_FAILED(aRequest->GetImageStatus(&status))) { + return; + } + + FrameSet* frameSet = mRequestToFrameMap.Get(aRequest); + if (!frameSet) { + return; + } + + for (FrameWithFlags& fwf : *frameSet) { + if (status & imgIRequest::STATUS_ERROR) { + // Check if aRequest has an error state. If it does, we need to unblock + // Document onload for all the frames associated with this request that + // have blocked onload. This is what happens in a CORS mode violation, and + // may happen during other network events. + UnblockOnloadIfNeeded(fwf); + } + nsIFrame* frame = fwf.mFrame; + if (frame->StyleVisibility()->IsVisible()) { + frame->SchedulePaint(); + } + + if (StaticPrefs::dom_enable_largest_contentful_paint()) { + LargestContentfulPaint::MaybeProcessImageForElementTiming( + static_cast<imgRequestProxy*>(aRequest), + frame->GetContent()->AsElement()); + } + } +} +} // namespace mozilla::css |