Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/. */
// Main header first:
#include "SVGObserverUtils.h"
// Keep others in (case-insensitive) order:
#include "mozilla/css/ImageLoader.h"
#include "mozilla/dom/CanvasRenderingContext2D.h"
#include "mozilla/dom/ReferrerInfo.h"
#include "mozilla/dom/SVGGeometryElement.h"
#include "mozilla/dom/SVGMPathElement.h"
#include "mozilla/dom/SVGTextPathElement.h"
#include "mozilla/dom/SVGUseElement.h"
#include "mozilla/PresShell.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/SVGClipPathFrame.h"
#include "mozilla/SVGGeometryFrame.h"
#include "mozilla/SVGMaskFrame.h"
#include "mozilla/SVGTextFrame.h"
#include "mozilla/SVGUtils.h"
#include "nsCSSFrameConstructor.h"
#include "nsCycleCollectionParticipant.h"
#include "nsHashKeys.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsInterfaceHashtable.h"
#include "nsIReflowCallback.h"
#include "nsISupportsImpl.h"
#include "nsLayoutUtils.h"
#include "nsNetUtil.h"
#include "nsTHashtable.h"
#include "nsURIHashKey.h"
#include "SVGFilterFrame.h"
#include "SVGMarkerFrame.h"
#include "SVGPaintServerFrame.h"
using namespace mozilla::dom;
namespace mozilla {
bool URLAndReferrerInfo::operator==(const URLAndReferrerInfo& aRHS) const {
bool uriEqual = false, referrerEqual = false;
this->mURI->Equals(aRHS.mURI, &uriEqual);
this->mReferrerInfo->Equals(aRHS.mReferrerInfo, &referrerEqual);
return uriEqual && referrerEqual;
}
class URLAndReferrerInfoHashKey : public PLDHashEntryHdr {
public:
using KeyType = const URLAndReferrerInfo*;
using KeyTypePointer = const URLAndReferrerInfo*;
explicit URLAndReferrerInfoHashKey(const URLAndReferrerInfo* aKey) noexcept
: mKey(aKey) {
MOZ_COUNT_CTOR(URLAndReferrerInfoHashKey);
}
URLAndReferrerInfoHashKey(URLAndReferrerInfoHashKey&& aToMove) noexcept
: PLDHashEntryHdr(std::move(aToMove)), mKey(std::move(aToMove.mKey)) {
MOZ_COUNT_CTOR(URLAndReferrerInfoHashKey);
}
MOZ_COUNTED_DTOR(URLAndReferrerInfoHashKey)
const URLAndReferrerInfo* GetKey() const { return mKey; }
bool KeyEquals(const URLAndReferrerInfo* aKey) const {
if (!mKey) {
return !aKey;
}
return *mKey == *aKey;
}
static const URLAndReferrerInfo* KeyToPointer(
const URLAndReferrerInfo* aKey) {
return aKey;
}
static PLDHashNumber HashKey(const URLAndReferrerInfo* aKey) {
if (!aKey) {
// If the key is null, return hash for empty string.
return HashString(""_ns);
}
nsAutoCString urlSpec, referrerSpec;
// nsURIHashKey ignores GetSpec() failures, so we do too:
Unused << aKey->GetURI()->GetSpec(urlSpec);
return AddToHash(
HashString(urlSpec),
static_cast<ReferrerInfo*>(aKey->GetReferrerInfo())->Hash());
}
enum { ALLOW_MEMMOVE = true };
protected:
RefPtr<const URLAndReferrerInfo> mKey;
};
/**
* Return a baseURL for resolving a local-ref URL.
*
* @param aContent an element which uses a local-ref property. Here are some
* examples:
* <rect fill=url(#foo)>
* <circle clip-path=url(#foo)>
* <use xlink:href="#foo">
*/
static already_AddRefed<nsIURI> GetBaseURLForLocalRef(nsIContent* content,
nsIURI* aURI) {
MOZ_ASSERT(content);
// Content is in a shadow tree. If this URL was specified in the subtree
// referenced by the <use>, element, and that subtree came from a separate
// resource document, then we want the fragment-only URL to resolve to an
// element from the resource document. Otherwise, the URL was specified
// somewhere in the document with the <use> element, and we want the
// fragment-only URL to resolve to an element in that document.
if (SVGUseElement* use = content->GetContainingSVGUseShadowHost()) {
if (nsIURI* originalURI = use->GetSourceDocURI()) {
bool isEqualsExceptRef = false;
aURI->EqualsExceptRef(originalURI, &isEqualsExceptRef);
if (isEqualsExceptRef) {
return do_AddRef(originalURI);
}
}
}
// For a local-reference URL, resolve that fragment against the current
// document that relative URLs are resolved against.
return do_AddRef(content->OwnerDoc()->GetDocumentURI());
}
static already_AddRefed<URLAndReferrerInfo> ResolveURLUsingLocalRef(
nsIFrame* aFrame, const StyleComputedImageUrl& aURL) {
MOZ_ASSERT(aFrame);
nsCOMPtr<nsIURI> uri = aURL.GetURI();
if (aURL.IsLocalRef()) {
uri = GetBaseURLForLocalRef(aFrame->GetContent(), uri);
uri = aURL.ResolveLocalRef(uri);
}
if (!uri) {
return nullptr;
}
return do_AddRef(new URLAndReferrerInfo(uri, aURL.ExtraData()));
}
static already_AddRefed<URLAndReferrerInfo> ResolveURLUsingLocalRef(
nsIContent* aContent, const nsAString& aURL) {
// Like GetBaseURLForLocalRef, we want to resolve the
// URL against any <use> element shadow tree's source document.
//
// Unlike GetBaseURLForLocalRef, we are assuming that the URL was specified
// directly on mFrame's content (because this ResolveURLUsingLocalRef
// overload is used for href="" attributes and not CSS URL values), so there
// is no need to check whether the URL was specified / inherited from
// outside the shadow tree.
nsIURI* base = nullptr;
const Encoding* encoding = nullptr;
if (SVGUseElement* use = aContent->GetContainingSVGUseShadowHost()) {
base = use->GetSourceDocURI();
encoding = use->GetSourceDocCharacterSet();
}
if (!base) {
base = aContent->OwnerDoc()->GetDocumentURI();
encoding = aContent->OwnerDoc()->GetDocumentCharacterSet();
}
nsCOMPtr<nsIURI> uri;
Unused << NS_NewURI(getter_AddRefs(uri), aURL, WrapNotNull(encoding), base);
if (!uri) {
return nullptr;
}
// There's no clear refererer policy spec about non-CSS SVG resource
// references Bug 1415044 to investigate which referrer we should use
nsIReferrerInfo* referrerInfo =
aContent->OwnerDoc()->ReferrerInfoForInternalCSSAndSVGResources();
return do_AddRef(new URLAndReferrerInfo(uri, referrerInfo));
}
class SVGFilterObserverList;
/**
* A class used as a member of the "observer" classes below to help them
* avoid dereferencing their frame during presshell teardown when their frame
* may have been destroyed (leaving their pointer to their frame dangling).
*
* When a presshell is torn down, the properties for each frame may not be
* deleted until after the frames are destroyed. "Observer" objects (attached
* as frame properties) must therefore check whether the presshell is being
* torn down before using their pointer to their frame.
*
* mFramePresShell may be null, but when mFrame is non-null, mFramePresShell
* is guaranteed to be non-null, too.
*/
struct SVGFrameReferenceFromProperty {
explicit SVGFrameReferenceFromProperty(nsIFrame* aFrame)
: mFrame(aFrame), mFramePresShell(aFrame->PresShell()) {}
// Clear our reference to the frame.
void Detach() {
mFrame = nullptr;
mFramePresShell = nullptr;
}
// null if the frame has become invalid
nsIFrame* Get() {
if (mFramePresShell && mFramePresShell->IsDestroying()) {
Detach(); // mFrame is no longer valid.
}
return mFrame;
}
private:
// The frame that our property is attached to (may be null).
nsIFrame* mFrame;
PresShell* mFramePresShell;
};
void SVGRenderingObserver::StartObserving() {
Element* target = GetReferencedElementWithoutObserving();
if (target) {
target->AddMutationObserver(this);
}
}
void SVGRenderingObserver::StopObserving() {
Element* target = GetReferencedElementWithoutObserving();
if (target) {
target->RemoveMutationObserver(this);
if (mInObserverSet) {
SVGObserverUtils::RemoveRenderingObserver(target, this);
mInObserverSet = false;
}
}
NS_ASSERTION(!mInObserverSet, "still in an observer set?");
}
Element* SVGRenderingObserver::GetAndObserveReferencedElement() {
#ifdef DEBUG
DebugObserverSet();
#endif
Element* referencedElement = GetReferencedElementWithoutObserving();
if (referencedElement && !mInObserverSet) {
SVGObserverUtils::AddRenderingObserver(referencedElement, this);
mInObserverSet = true;
}
return referencedElement;
}
nsIFrame* SVGRenderingObserver::GetAndObserveReferencedFrame() {
Element* referencedElement = GetAndObserveReferencedElement();
return referencedElement ? referencedElement->GetPrimaryFrame() : nullptr;
}
nsIFrame* SVGRenderingObserver::GetAndObserveReferencedFrame(
LayoutFrameType aFrameType, bool* aOK) {
nsIFrame* frame = GetAndObserveReferencedFrame();
if (frame) {
if (frame->Type() == aFrameType) {
return frame;
}
if (aOK) {
*aOK = false;
}
}
return nullptr;
}
void SVGRenderingObserver::OnNonDOMMutationRenderingChange() {
OnRenderingChange();
}
void SVGRenderingObserver::NotifyEvictedFromRenderingObserverSet() {
mInObserverSet = false; // We've been removed from rendering-obs. set.
StopObserving(); // Stop observing mutations too.
}
void SVGRenderingObserver::AttributeChanged(dom::Element* aElement,
int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue) {
if (aElement->IsInNativeAnonymousSubtree()) {
// Don't observe attribute changes in native-anonymous subtrees like
// scrollbars.
return;
}
// An attribute belonging to the element that we are observing *or one of its
// descendants* has changed.
//
// In the case of observing a gradient element, say, we want to know if any
// of its 'stop' element children change, but we don't actually want to do
// anything for changes to SMIL element children, for example. Maybe it's not
// worth having logic to optimize for that, but in most cases it could be a
// small check?
//
// XXXjwatt: do we really want to blindly break the link between our
// observers and ourselves for all attribute changes? For non-ID changes
// surely that is unnecessary.
OnRenderingChange();
}
void SVGRenderingObserver::ContentAppended(nsIContent* aFirstNewContent) {
OnRenderingChange();
}
void SVGRenderingObserver::ContentInserted(nsIContent* aChild) {
OnRenderingChange();
}
void SVGRenderingObserver::ContentRemoved(nsIContent* aChild,
nsIContent* aPreviousSibling) {
OnRenderingChange();
}
/**
* SVG elements reference supporting resources by element ID. We need to
* track when those resources change and when the document changes in ways
* that affect which element is referenced by a given ID (e.g., when
* element IDs change). The code here is responsible for that.
*
* When a frame references a supporting resource, we create a property
* object derived from SVGIDRenderingObserver to manage the relationship. The
* property object is attached to the referencing frame.
*/
class SVGIDRenderingObserver : public SVGRenderingObserver {
public:
// Callback for checking if the element being observed is valid for this
// observer. Note that this may be called during construction, before the
// deriving class is fully constructed.
using TargetIsValidCallback = bool (*)(const Element&);
SVGIDRenderingObserver(
URLAndReferrerInfo* aURI, nsIContent* aObservingContent,
bool aReferenceImage,
uint32_t aCallbacks = kAttributeChanged | kContentAppended |
kContentInserted | kContentRemoved,
TargetIsValidCallback aTargetIsValidCallback = nullptr);
void Traverse(nsCycleCollectionTraversalCallback* aCB);
protected:
virtual ~SVGIDRenderingObserver() {
// This needs to call our GetReferencedElementWithoutObserving override,
// so must be called here rather than in our base class's dtor.
StopObserving();
}
void TargetChanged() {
mTargetIsValid = ([this] {
Element* observed = mObservedElementTracker.get();
if (!observed) {
return false;
}
// If the content is observing an ancestor, then return the target is not
// valid.
//
// TODO(emilio): Should we allow content observing its own descendants?
// That seems potentially-bad as well.
if (observed->OwnerDoc() == mObservingContent->OwnerDoc() &&
nsContentUtils::ContentIsHostIncludingDescendantOf(mObservingContent,
observed)) {
return false;
}
if (mTargetIsValidCallback) {
return mTargetIsValidCallback(*observed);
}
return true;
}());
}
Element* GetReferencedElementWithoutObserving() final {
return mTargetIsValid ? mObservedElementTracker.get() : nullptr;
}
void OnRenderingChange() override;
/**
* Helper that provides a reference to the element with the ID that our
* observer wants to observe, and that will invalidate our observer if the
* element that that ID identifies changes to a different element (or none).
*/
class ElementTracker final : public IDTracker {
public:
explicit ElementTracker(SVGIDRenderingObserver* aOwningObserver)
: mOwningObserver(aOwningObserver) {}
protected:
void ElementChanged(Element* aFrom, Element* aTo) override {
// Call OnRenderingChange() before the target changes, so that
// mIsTargetValid reflects the right state.
mOwningObserver->OnRenderingChange();
mOwningObserver->StopObserving();
IDTracker::ElementChanged(aFrom, aTo);
mOwningObserver->TargetChanged();
mOwningObserver->StartObserving();
// And same after the target changes, for the same reason.
mOwningObserver->OnRenderingChange();
}
/**
* Override IsPersistent because we want to keep tracking the element
* for the ID even when it changes.
*/
bool IsPersistent() override { return true; }
private:
SVGIDRenderingObserver* mOwningObserver;
};
ElementTracker mObservedElementTracker;
RefPtr<Element> mObservingContent;
bool mTargetIsValid = false;
TargetIsValidCallback mTargetIsValidCallback;
};
/**
* Note that in the current setup there are two separate observer lists.
*
* In SVGIDRenderingObserver's ctor, the new object adds itself to the
* mutation observer list maintained by the referenced element. In this way the
* SVGIDRenderingObserver is notified if there are any attribute or content
* tree changes to the element or any of its *descendants*.
*
* In SVGIDRenderingObserver::GetAndObserveReferencedElement() the
* SVGIDRenderingObserver object also adds itself to an
* SVGRenderingObserverSet object belonging to the referenced
* element.
*
* XXX: it would be nice to have a clear and concise executive summary of the
* benefits/necessity of maintaining a second observer list.
*/
SVGIDRenderingObserver::SVGIDRenderingObserver(
URLAndReferrerInfo* aURI, nsIContent* aObservingContent,
bool aReferenceImage, uint32_t aCallbacks,
TargetIsValidCallback aTargetIsValidCallback)
: SVGRenderingObserver(aCallbacks),
mObservedElementTracker(this),
mObservingContent(aObservingContent->AsElement()),
mTargetIsValidCallback(aTargetIsValidCallback) {
// Start watching the target element
nsIURI* uri = nullptr;
nsIReferrerInfo* referrerInfo = nullptr;
if (aURI) {
uri = aURI->GetURI();
referrerInfo = aURI->GetReferrerInfo();
}
mObservedElementTracker.ResetToURIFragmentID(
aObservingContent, uri, referrerInfo, true, aReferenceImage);
TargetChanged();
StartObserving();
}
void SVGIDRenderingObserver::Traverse(nsCycleCollectionTraversalCallback* aCB) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mObservingContent");
aCB->NoteXPCOMChild(mObservingContent);
mObservedElementTracker.Traverse(aCB);
}
void SVGIDRenderingObserver::OnRenderingChange() {
if (mObservedElementTracker.get() && mInObserverSet) {
SVGObserverUtils::RemoveRenderingObserver(mObservedElementTracker.get(),
this);
mInObserverSet = false;
}
}
class SVGRenderingObserverProperty : public SVGIDRenderingObserver {
public:
NS_DECL_ISUPPORTS
SVGRenderingObserverProperty(
URLAndReferrerInfo* aURI, nsIFrame* aFrame, bool aReferenceImage,
uint32_t aCallbacks = kAttributeChanged | kContentAppended |
kContentInserted | kContentRemoved,
TargetIsValidCallback aTargetIsValidCallback = nullptr)
: SVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage,
aCallbacks, aTargetIsValidCallback),
mFrameReference(aFrame) {}
protected:
virtual ~SVGRenderingObserverProperty() = default; // non-public
void OnRenderingChange() override;
SVGFrameReferenceFromProperty mFrameReference;
};
NS_IMPL_ISUPPORTS(SVGRenderingObserverProperty, nsIMutationObserver)
void SVGRenderingObserverProperty::OnRenderingChange() {
SVGIDRenderingObserver::OnRenderingChange();
if (!mTargetIsValid) {
return;
}
nsIFrame* frame = mFrameReference.Get();
if (frame && frame->HasAllStateBits(NS_FRAME_SVG_LAYOUT)) {
// We need to notify anything that is observing the referencing frame or
// any of its ancestors that the referencing frame has been invalidated.
// Since walking the parent chain checking for observers is expensive we
// do that using a change hint (multiple change hints of the same type are
// coalesced).
nsLayoutUtils::PostRestyleEvent(frame->GetContent()->AsElement(),
RestyleHint{0},
nsChangeHint_InvalidateRenderingObservers);
}
}
static bool IsSVGGeometryElement(const Element& aObserved) {
return aObserved.IsSVGGeometryElement();
}
class SVGTextPathObserver final : public SVGRenderingObserverProperty {
public:
SVGTextPathObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
bool aReferenceImage)
: SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage,
kAttributeChanged, IsSVGGeometryElement) {}
protected:
void OnRenderingChange() override;
};
void SVGTextPathObserver::OnRenderingChange() {
SVGRenderingObserverProperty::OnRenderingChange();
if (!mTargetIsValid) {
return;
}
nsIFrame* frame = mFrameReference.Get();
if (!frame) {
return;
}
MOZ_ASSERT(frame->IsSVGFrame() || frame->IsInSVGTextSubtree(),
"SVG frame expected");
MOZ_ASSERT(frame->GetContent()->IsSVGElement(nsGkAtoms::textPath),
"expected frame for a <textPath> element");
auto* text = static_cast<SVGTextFrame*>(
nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::SVGText));
MOZ_ASSERT(text, "expected to find an ancestor SVGTextFrame");
if (text) {
text->AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY);
if (SVGUtils::AnyOuterSVGIsCallingReflowSVG(text)) {
text->AddStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
if (text->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
text->ReflowSVGNonDisplayText();
} else {
text->ReflowSVG();
}
} else {
text->ScheduleReflowSVG();
}
}
}
class SVGMPathObserver final : public SVGIDRenderingObserver {
public:
NS_DECL_ISUPPORTS
SVGMPathObserver(URLAndReferrerInfo* aURI, SVGMPathElement* aElement)
: SVGIDRenderingObserver(aURI, aElement, /* aReferenceImage = */ false,
kAttributeChanged, IsSVGGeometryElement) {}
protected:
virtual ~SVGMPathObserver() = default; // non-public
void OnRenderingChange() override;
};
NS_IMPL_ISUPPORTS(SVGMPathObserver, nsIMutationObserver)
void SVGMPathObserver::OnRenderingChange() {
SVGIDRenderingObserver::OnRenderingChange();
if (!mTargetIsValid) {
return;
}
auto* element = static_cast<SVGMPathElement*>(mObservingContent.get());
element->NotifyParentOfMpathChange();
}
class SVGMarkerObserver final : public SVGRenderingObserverProperty {
public:
SVGMarkerObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
bool aReferenceImage)
: SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage,
kAttributeChanged | kContentAppended |
kContentInserted | kContentRemoved) {}
protected:
void OnRenderingChange() override;
};
void SVGMarkerObserver::OnRenderingChange() {
SVGRenderingObserverProperty::OnRenderingChange();
nsIFrame* frame = mFrameReference.Get();
if (!frame) {
return;
}
MOZ_ASSERT(frame->IsSVGFrame(), "SVG frame expected");
// Don't need to request ReflowFrame if we're being reflowed.
// Because mRect for SVG frames includes the bounds of any markers
// (see the comment for nsIFrame::GetRect), the referencing frame must be
// reflowed for any marker changes.
if (!frame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
// XXXjwatt: We need to unify SVG into standard reflow so we can just use
// nsChangeHint_NeedReflow | nsChangeHint_NeedDirtyReflow here.
// XXXSDL KILL THIS!!!
SVGUtils::ScheduleReflowSVG(frame);
}
frame->PresContext()->RestyleManager()->PostRestyleEvent(
frame->GetContent()->AsElement(), RestyleHint{0},
nsChangeHint_RepaintFrame);
}
class SVGPaintingProperty : public SVGRenderingObserverProperty {
public:
SVGPaintingProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
bool aReferenceImage)
: SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {}
protected:
void OnRenderingChange() override;
};
void SVGPaintingProperty::OnRenderingChange() {
SVGRenderingObserverProperty::OnRenderingChange();
nsIFrame* frame = mFrameReference.Get();
if (!frame) {
return;
}
if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
frame->InvalidateFrameSubtree();
} else {
for (nsIFrame* f = frame; f;
f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
f->InvalidateFrame();
}
}
}
// Observer for -moz-element(#element). Note that the observed element does not
// have to be an SVG element.
class SVGMozElementObserver final : public SVGPaintingProperty {
public:
SVGMozElementObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame)
: SVGPaintingProperty(aURI, aFrame, /* aReferenceImage = */ true) {}
// We only return true here because GetAndObserveBackgroundImage uses us
// to implement observing of arbitrary elements (including HTML elements)
// that may require us to repaint if the referenced element is reflowed.
// Bug 1496065 has been filed to remove that support though.
bool ObservesReflow() override { return true; }
};
/**
* For content with `background-clip: text`.
*
* This observer is unusual in that the observing frame and observed frame are
* the same frame. This is because the observing frame is observing for reflow
* of its descendant text nodes, since such reflows may not result in the
* frame's nsDisplayBackground changing. In other words, Display List Based
* Invalidation may not invalidate the frame's background, so we need this
* observer to make sure that happens.
*
* XXX: It's questionable whether we should even be [ab]using the SVG observer
* mechanism for `background-clip:text`. Since we know that the observed frame
* is the frame we need to invalidate, we could just check the computed style
* in the (one) place where we pass INVALIDATE_REFLOW and invalidate there...
*/
class BackgroundClipRenderingObserver : public SVGRenderingObserver {
public:
explicit BackgroundClipRenderingObserver(nsIFrame* aFrame) : mFrame(aFrame) {}
NS_DECL_ISUPPORTS
private:
// We do not call StopObserving() since the observing and observed element
// are the same element (and because we could crash - see bug 1556441).
virtual ~BackgroundClipRenderingObserver() = default;
Element* GetReferencedElementWithoutObserving() final {
return mFrame->GetContent()->AsElement();
}
void OnRenderingChange() final;
/**
* Observing for mutations is not enough. A new font loading and applying
* to the text content could cause it to reflow, and we need to invalidate
* for that.
*/
bool ObservesReflow() final { return true; }
// The observer and observee!
nsIFrame* mFrame;
};
NS_IMPL_ISUPPORTS(BackgroundClipRenderingObserver, nsIMutationObserver)
void BackgroundClipRenderingObserver::OnRenderingChange() {
for (nsIFrame* f = mFrame; f;
f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
f->InvalidateFrame();
}
}
static bool IsSVGFilterElement(const Element& aObserved) {
return aObserved.IsSVGElement(nsGkAtoms::filter);
}
/**
* In a filter chain, there can be multiple SVG reference filters.
* e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2);
*
* This class keeps track of one SVG reference filter in a filter chain.
* e.g. url(#svg-filter-1)
*
* It fires invalidations when the SVG filter element's id changes or when
* the SVG filter element's content changes.
*
* The SVGFilterObserverList class manages a list of SVGFilterObservers.
*/
class SVGFilterObserver final : public SVGIDRenderingObserver {
public:
SVGFilterObserver(URLAndReferrerInfo* aURI, nsIContent* aObservingContent,
SVGFilterObserverList* aFilterChainObserver)
: SVGIDRenderingObserver(aURI, aObservingContent, false,
kAttributeChanged | kContentAppended |
kContentInserted | kContentRemoved,
IsSVGFilterElement),
mFilterObserverList(aFilterChainObserver) {}
void DetachFromChainObserver() { mFilterObserverList = nullptr; }
/**
* @return the filter frame, or null if there is no filter frame
*/
SVGFilterFrame* GetAndObserveFilterFrame();
// nsISupports
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(SVGFilterObserver)
// SVGIDRenderingObserver
void OnRenderingChange() override;
protected:
virtual ~SVGFilterObserver() = default; // non-public
SVGFilterObserverList* mFilterObserverList;
};
NS_IMPL_CYCLE_COLLECTING_ADDREF(SVGFilterObserver)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SVGFilterObserver)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGFilterObserver)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_CLASS(SVGFilterObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGFilterObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservedElementTracker)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservingContent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGFilterObserver)
tmp->StopObserving();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservedElementTracker);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservingContent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
SVGFilterFrame* SVGFilterObserver::GetAndObserveFilterFrame() {
return static_cast<SVGFilterFrame*>(
GetAndObserveReferencedFrame(LayoutFrameType::SVGFilter, nullptr));
}
/**
* This class manages a list of SVGFilterObservers, which correspond to
* reference to SVG filters in a list of filters in a given 'filter' property.
* e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2);
*
* In the above example, the SVGFilterObserverList will manage two
* SVGFilterObservers, one for each of the references to SVG filters. CSS
* filters like "blur(10px)" don't reference filter elements, so they don't
* need an SVGFilterObserver. The style system invalidates changes to CSS
* filters.
*
* FIXME(emilio): Why do we need this as opposed to the individual observers we
* create in the constructor?
*/
class SVGFilterObserverList : public nsISupports {
public:
SVGFilterObserverList(Span<const StyleFilter> aFilters,
nsIContent* aFilteredElement,
nsIFrame* aFilteredFrame = nullptr);
const nsTArray<RefPtr<SVGFilterObserver>>& GetObservers() const {
return mObservers;
}
// nsISupports
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(SVGFilterObserverList)
virtual void OnRenderingChange(Element* aObservingContent) = 0;
protected:
virtual ~SVGFilterObserverList();
void DetachObservers() {
for (auto& observer : mObservers) {
observer->DetachFromChainObserver();
}
}
nsTArray<RefPtr<SVGFilterObserver>> mObservers;
};
void SVGFilterObserver::OnRenderingChange() {
SVGIDRenderingObserver::OnRenderingChange();
if (!mTargetIsValid) {
return;
}
if (mFilterObserverList) {
mFilterObserverList->OnRenderingChange(mObservingContent);
}
}
NS_IMPL_CYCLE_COLLECTING_ADDREF(SVGFilterObserverList)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SVGFilterObserverList)
NS_IMPL_CYCLE_COLLECTION_CLASS(SVGFilterObserverList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGFilterObserverList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGFilterObserverList)
tmp->DetachObservers();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGFilterObserverList)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
SVGFilterObserverList::SVGFilterObserverList(Span<const StyleFilter> aFilters,
nsIContent* aFilteredElement,
nsIFrame* aFilteredFrame) {
for (const auto& filter : aFilters) {
if (!filter.IsUrl()) {
continue;
}
const auto& url = filter.AsUrl();
// aFilteredFrame can be null if this filter belongs to a
// CanvasRenderingContext2D.
RefPtr<URLAndReferrerInfo> filterURL;
if (aFilteredFrame) {
filterURL = ResolveURLUsingLocalRef(aFilteredFrame, url);
} else {
nsCOMPtr<nsIURI> resolvedURI = url.ResolveLocalRef(aFilteredElement);
if (resolvedURI) {
filterURL = new URLAndReferrerInfo(resolvedURI, url.ExtraData());
}
}
auto observer =
MakeRefPtr<SVGFilterObserver>(filterURL, aFilteredElement, this);
mObservers.AppendElement(std::move(observer));
}
}
SVGFilterObserverList::~SVGFilterObserverList() { DetachObservers(); }
class SVGFilterObserverListForCSSProp final : public SVGFilterObserverList {
public:
SVGFilterObserverListForCSSProp(Span<const StyleFilter> aFilters,
nsIFrame* aFilteredFrame)
: SVGFilterObserverList(aFilters, aFilteredFrame->GetContent(),
aFilteredFrame) {}
protected:
void OnRenderingChange(Element* aObservingContent) override;
};
void SVGFilterObserverListForCSSProp::OnRenderingChange(
Element* aObservingContent) {
nsIFrame* frame = aObservingContent->GetPrimaryFrame();
if (!frame) {
return;
}
// Repaint asynchronously in case the filter frame is being torn down
auto changeHint = nsChangeHint_RepaintFrame;
// Since we don't call SVGRenderingObserverProperty::
// OnRenderingChange, we have to add this bit ourselves.
if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
// Changes should propagate out to things that might be observing
// the referencing frame or its ancestors.
changeHint |= nsChangeHint_InvalidateRenderingObservers;
}
// Don't need to request UpdateOverflow if we're being reflowed.
if (!frame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
changeHint |= nsChangeHint_UpdateOverflow;
}
frame->PresContext()->RestyleManager()->PostRestyleEvent(
aObservingContent, RestyleHint{0}, changeHint);
}
class SVGFilterObserverListForCanvasContext final
: public SVGFilterObserverList {
public:
SVGFilterObserverListForCanvasContext(CanvasRenderingContext2D* aContext,
Element* aCanvasElement,
Span<const StyleFilter> aFilters)
: SVGFilterObserverList(aFilters, aCanvasElement), mContext(aContext) {}
void OnRenderingChange(Element* aObservingContent) override;
void DetachFromContext() { mContext = nullptr; }
private:
CanvasRenderingContext2D* mContext;
};
void SVGFilterObserverListForCanvasContext::OnRenderingChange(
Element* aObservingContent) {
if (!mContext) {
NS_WARNING(
"GFX: This should never be called without a context, except during "
"cycle collection (when DetachFromContext has been called)");
return;
}
// Refresh the cached FilterDescription in mContext->CurrentState().filter.
// If this filter is not at the top of the state stack, we'll refresh the
// wrong filter, but that's ok, because we'll refresh the right filter
// when we pop the state stack in CanvasRenderingContext2D::Restore().
//
// We don't need to flush, we're called by layout.
RefPtr<CanvasRenderingContext2D> kungFuDeathGrip(mContext);
kungFuDeathGrip->UpdateFilter(/* aFlushIfNeeded = */ false);
}
class SVGMaskObserverList final : public nsISupports {
public:
explicit SVGMaskObserverList(nsIFrame* aFrame);
// nsISupports
NS_DECL_ISUPPORTS
const nsTArray<RefPtr<SVGPaintingProperty>>& GetObservers() const {
return mProperties;
}
void ResolveImage(uint32_t aIndex);
private:
virtual ~SVGMaskObserverList() = default; // non-public
nsTArray<RefPtr<SVGPaintingProperty>> mProperties;
nsIFrame* mFrame;
};