fune/layout/style/ImageLoader.cpp
Timothy Nikkel 537395e32c Bug 1889166. Invalidate images in popups on macOS which aren't drawn with webrender. r=layout-reviewers,emilio
The problem is that in InvalidateImages in ImageLoader.cpp

https://searchfox.org/mozilla-central/rev/e1e4a33e82ee1d278df238cf0896b7358a4bc359/layout/style/ImageLoader.cpp#517

we do nothing to invalidate images that are in popups that aren't drawn by webrender. This is controlled via ShouldUseOffMainThreadCompositing here

https://searchfox.org/mozilla-central/rev/5df192b9edee971a1ab06ff9ef688d92c21a92c1/widget/cocoa/nsChildView.mm#1245

So all popups on macOS are painted using the fallback renderer. So InvalidateImages will do absolutely nothing in this case because there is no saved webrender user data, and the aForcePaint flag is false (because this is a frame update notification, not a frame complete/first frame notification).

Differential Revision: https://phabricator.services.mozilla.com/D206569
2024-04-04 19:25:17 +00:00

844 lines
27 KiB
C++

/* -*- 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;
}
}
}
#ifdef XP_MACOSX
else if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
// On macOS popups are painted with fallback rendering so they don't have
// webrender user data to tell us if the frame was painted last time, so we
// just have to invalidate always. Bug 1754796 tracks making popups on macOS
// use webrender. (On other platforms tooltips type popups are still
// rendered with fallback, but we don't expect them to have images.)
invalidateFrame = true;
}
#endif
// 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