gecko-dev/layout/style/ImageLoader.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

830 lines
26 KiB
C++
Raw Normal View History

/* -*- 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"
Bug 1590639 part 4: Fix non-unified build issues in layout/style. r=emilio This patch: - Gives layout/generic/AnonymousContentKey.h an include for `<stdint.h>` to provide the uint8_t type, and TypedEnumBits.h to provide the MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS macro. (This is a change in another directory, but it's needed in order for layout/style/ServoStyleSet.cpp to build successfully.) - Adds a missing "nsINode" forward-decl to dom/base/IdentifierMapEntry.h, because it uses that type in function declarations. (This change is needed in order for layout/style/CachedInheritingStyles.cpp to build successfully.) - Gives CSSStyleRule.cpp an include for PseudoStyleType.h, nsCSSPseudoElements.h, and CSSEnabledState.h because it uses those types. - Gives GeckoBindings.cpp an include for gfxTextRun.h, to provide the definition of type gfxFontGroup (so GeckoBindings can call GetFirstValidFont() on an object of that type). - Gives Loader.h an include for nsIContentInlines.h, to provide the inline function IsInUAWidget(). - Gives Rule.cpp an include for HoldDropJSObjects.h, to provide DropJSObjects(). - Gives nsImageLoader.cpp an include for DocumentInlines.h (and Document.h for good measure), to provide the inline function GetPresContext(). - Gives nsStyleStruct.cpp an include for DocumentInlines.h, to provide inline function Document::GetPresContext(). - Gives nsStyleTransformMatrix.h an include for Units.h (instead of gfxPoint.h, which isn't useful) to provide the CSSPoint type. - Gives nsStyleTransformMatrix.h an include for ServoStyleConsts.h, to provide LengthPercentage and the various StyleRotate/StyleScale/StyleTransform/etc types. (These can't be easily forward-declared, because some of them are legitimate types whereas others are type aliases. We could theoretically forward-declare all of the underlying types and then repeat the type aliases, but that'd be verbose and unmaintainable.) Depends on D50165 Differential Revision: https://phabricator.services.mozilla.com/D50166 --HG-- extra : moz-landing-system : lando
2019-10-23 08:14:54 +00:00
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.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 "FrameLayerBuilder.h"
#include "imgIContainer.h"
#include "imgINotificationObserver.h"
#include "Image.h"
#include "mozilla/PresShell.h"
#include "mozilla/ProfilerLabels.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 GlobalRequestTable* sImages = nullptr;
static StaticRefPtr<GlobalImageObserver> sImageObserver;
/* static */
void ImageLoader::Init() {
sImages = new GlobalRequestTable();
sImageObserver = new GlobalImageObserver();
}
/* static */
void ImageLoader::Shutdown() {
for (const auto& entry : *sImages) {
entry.GetKey()->CancelAndForgetObserver(NS_BINDING_ABORTED);
}
delete sImages;
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());
2012-08-14 20:34:20 +00:00
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->RemoveElementAt(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());
Bug 1552708 - Use cbindgen for URIs. r=heycam This doesn't clean up as much as a whole, but it's a step in the right direction. In particular, it allows us to start using simple bindings for: * Filters * Shapes and images, almost. Need to: * Get rid of the complex -moz- gradient parsing (let layout.css.simple-moz-gradient.enabled get to release). * Counters, almost. Need to: * Share the Attr representation with Gecko, by not using Option<>. * Just another variant should be enough (ContentItem::{Attr,Prefixedattr}, maybe). Which in turn allows us to remove a whole lot of bindings in followups to this. The setup changes a bit. This also removes the double pointer I complained about while reviewing the shared UA sheet patches. The old setup is: ``` SpecifiedUrl * CssUrl * Arc<CssUrlData> * String * UrlExtraData * UrlValueSource * Arc<CssUrlData> * load id * resolved uri * CORS mode. * ... ``` The new one removes the double reference to the url data via URLValue, and looks like: ``` SpecifiedUrl * CssUrl * Arc<CssUrlData> * String * UrlExtraData * CorsMode * LoadData * load id * resolved URI ``` The LoadData is the only mutable bit that C++ can change, and is not used from Rust. Ideally, in the future, we could just use rust-url to resolve the URL after parsing or something, and make it all immutable. Maybe. I've verified that this approach still works with the UA sheet patches (via the LoadDataSource::Lazy). The reordering of mWillChange is to avoid nsStyleDisplay from going over the size limit. We want to split it up anyway in bug 1552587, but mBinding gains a tag member, which means that we were having a bit of extra padding. One thing I want to explore is to see if we can abuse rustc's non-zero optimizations to predict the layout from C++, but that's something to explore at some other point in time and with a lot of care and help from Michael (who sits next to me and works on rustc ;)). Differential Revision: https://phabricator.services.mozilla.com/D31742
2019-05-27 11:45:12 +00:00
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));
Bug 1552708 - Use cbindgen for URIs. r=heycam This doesn't clean up as much as a whole, but it's a step in the right direction. In particular, it allows us to start using simple bindings for: * Filters * Shapes and images, almost. Need to: * Get rid of the complex -moz- gradient parsing (let layout.css.simple-moz-gradient.enabled get to release). * Counters, almost. Need to: * Share the Attr representation with Gecko, by not using Option<>. * Just another variant should be enough (ContentItem::{Attr,Prefixedattr}, maybe). Which in turn allows us to remove a whole lot of bindings in followups to this. The setup changes a bit. This also removes the double pointer I complained about while reviewing the shared UA sheet patches. The old setup is: ``` SpecifiedUrl * CssUrl * Arc<CssUrlData> * String * UrlExtraData * UrlValueSource * Arc<CssUrlData> * load id * resolved uri * CORS mode. * ... ``` The new one removes the double reference to the url data via URLValue, and looks like: ``` SpecifiedUrl * CssUrl * Arc<CssUrlData> * String * UrlExtraData * CorsMode * LoadData * load id * resolved URI ``` The LoadData is the only mutable bit that C++ can change, and is not used from Rust. Ideally, in the future, we could just use rust-url to resolve the URL after parsing or something, and make it all immutable. Maybe. I've verified that this approach still works with the UA sheet patches (via the LoadDataSource::Lazy). The reordering of mWillChange is to avoid nsStyleDisplay from going over the size limit. We want to split it up anyway in bug 1552587, but mBinding gains a tag member, which means that we were having a bit of extra padding. One thing I want to explore is to see if we can abuse rustc's non-zero optimizations to predict the layout from C++, but that's something to explore at some other point in time and with a lot of care and help from Michael (who sits next to me and works on rustc ;)). Differential Revision: https://phabricator.services.mozilla.com/D31742
2019-05-27 11:45:12 +00:00
const URLExtraData& data = aImage.ExtraData();
Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat The bulk of this commit was generated with a script, executed at the top level of a typical source code checkout. The only non-machine-generated part was modifying MFBT's moz.build to reflect the new naming. CLOSED TREE makes big refactorings like this a piece of cake. # The main substitution. find . -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.mm' -o -name '*.idl'| \ xargs perl -p -i -e ' s/nsRefPtr\.h/RefPtr\.h/g; # handle includes s/nsRefPtr ?</RefPtr</g; # handle declarations and variables ' # Handle a special friend declaration in gfx/layers/AtomicRefCountedWithFinalize.h. perl -p -i -e 's/::nsRefPtr;/::RefPtr;/' gfx/layers/AtomicRefCountedWithFinalize.h # Handle nsRefPtr.h itself, a couple places that define constructors # from nsRefPtr, and code generators specially. We do this here, rather # than indiscriminantly s/nsRefPtr/RefPtr/, because that would rename # things like nsRefPtrHashtable. perl -p -i -e 's/nsRefPtr/RefPtr/g' \ mfbt/nsRefPtr.h \ xpcom/glue/nsCOMPtr.h \ xpcom/base/OwningNonNull.h \ ipc/ipdl/ipdl/lower.py \ ipc/ipdl/ipdl/builtin.py \ dom/bindings/Codegen.py \ python/lldbutils/lldbutils/utils.py # In our indiscriminate substitution above, we renamed # nsRefPtrGetterAddRefs, the class behind getter_AddRefs. Fix that up. find . -name '*.cpp' -o -name '*.h' -o -name '*.idl' | \ xargs perl -p -i -e 's/nsRefPtrGetterAddRefs/RefPtrGetterAddRefs/g' if [ -d .git ]; then git mv mfbt/nsRefPtr.h mfbt/RefPtr.h else hg mv mfbt/nsRefPtr.h mfbt/RefPtr.h fi --HG-- rename : mfbt/nsRefPtr.h => mfbt/RefPtr.h
2015-10-18 05:24:48 +00:00
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;
}
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;
}
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) {
2012-08-14 20:34:20 +00:00
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->IsFrameOfType(nsIFrame::eTablePart)) {
// 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* array = aFrame->DisplayItemData()) {
for (auto* did : *array) {
DisplayItemData* data = DisplayItemData::AssertDisplayItemData(did);
uint32_t displayItemKey = data->GetDisplayItemKey();
if (displayItemKey != 0 && !IsRenderNoImages(displayItemKey)) {
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
DisplayItemType type = GetDisplayItemTypeFromKey(displayItemKey);
printf_stderr(
"Invalidating display item(type=%d) based on frame %p \
because it might contain an invalidated image\n",
static_cast<uint32_t>(type), aFrame);
}
data->Invalidate();
invalidateFrame = true;
}
}
}
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::eImage:
if (static_cast<layers::WebRenderImageData*>(data.get())
->UsingSharedSurface(aRequest->GetProducerId())) {
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_NSCSTRING("ImageLoader::Notify", OTHER,
uriString);
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;
}
Bug 1554777 - Call SchedulePaint() rather than MarkNeedsDisplayItemRebuild() when we get the size available notification for a style image. r=mattwoodrow,tnikkel So the issue here is that this test-case has a zero-sized border, but still with a border-image (which we should paint). So the first time we get here, the image is not loaded, and thus we don't get here: https://searchfox.org/mozilla-central/rev/e0b0c38ee83f99d3cf868bad525ace4a395039f1/layout/painting/nsDisplayList.h#4254 Which means that we end up with zero bounds and thus we don't even get to the border painting code. However, when the image loads, we get to MarkNeedsDisplayItemRebuild(), but that doesn't schedule any paint, only marks the frames as modified in order for display items to be rebuilt _eventually_. Thus eventually, where we force a repaint by other means, we paint correctly. This only works in more general cases because we get to the nsImageRenderer code which does vastly different stuff. InvalidateFrame() seems to do the right thing and schedule a paint, so use it. It's not clear to me which one of nsIFrame::InvalidateFrame() or nsIFrame::SchedulePaint() we should use... If I understand correctly, InvalidateFrame() will only do something iff there are display items for the frame, so that should make the IsVisible() check redundant. Note however that I think there's still a race condition, if we get to paint in between the SIZE_AVAILABLE notification (the one where we actually invalidate the display items), and the LOAD notification (the one the border-image code checks). I'll send a separate patch for that, I think SIZE_AVAILABLE should be a strong-enough hint and that allows us to remove nsStyleImage::IsLoaded()... The RequestReflow stuff also looks highly suspicious... shape-outside sync-decodes, and it seems we could end up invalidating reflow from the reflow code... Differential Revision: https://phabricator.services.mozilla.com/D41005 --HG-- extra : moz-landing-system : lando
2019-08-15 19:03:18 +00:00
for (const FrameWithFlags& fwf : *frameSet) {
if (fwf.mFlags & Flags::RequiresReflowOnSizeAvailable) {
fwf.mFrame->PresShell()->FrameNeedsReflow(
fwf.mFrame, IntrinsicDirty::StyleChange, 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::StyleChange,
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);
}
if (fwf.mFrame->StyleVisibility()->IsVisible()) {
fwf.mFrame->SchedulePaint();
}
}
}
} // namespace mozilla::css