fune/layout/svg/SVGObserverUtils.cpp
Emilio Cobos Álvarez 68f214df2a Bug 1450250: Make svg:use use an actual shadow tree. r=heycam
Summary:
This fixes a couple fuzz bugs and prevents special-casing <svg:use> even more in
bug 1431255.

Unfortunately not as many hacks went away as I'd have hoped, since we still need
to match document rules, see the linked SVGWG issues.

But blocks_ancestor_combinators goes away, which is nice since it's on a very
hot path.

Test Plan: WPT for style invalidation, covered by existing tests otherwise.

Reviewers: heycam

Tags: #secure-revision

Bug #: 1450250

Differential Revision: https://phabricator.services.mozilla.com/D2154

MozReview-Commit-ID: C4mthjoSNFh
2018-07-20 14:44:51 +02:00

1068 lines
32 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/. */
// Main header first:
#include "SVGObserverUtils.h"
// Keep others in (case-insensitive) order:
#include "mozilla/RestyleManager.h"
#include "nsCSSFrameConstructor.h"
#include "nsISupportsImpl.h"
#include "nsSVGClipPathFrame.h"
#include "nsSVGPaintServerFrame.h"
#include "nsSVGFilterFrame.h"
#include "nsSVGMaskFrame.h"
#include "nsIReflowCallback.h"
#include "nsCycleCollectionParticipant.h"
#include "SVGGeometryElement.h"
#include "SVGUseElement.h"
#include "ImageLoader.h"
using namespace mozilla;
using namespace mozilla::dom;
void
nsSVGRenderingObserver::StartObserving()
{
Element* target = GetTarget();
if (target) {
target->AddMutationObserver(this);
}
}
void
nsSVGRenderingObserver::StopObserving()
{
Element* target = GetTarget();
if (target) {
target->RemoveMutationObserver(this);
if (mInObserverList) {
SVGObserverUtils::RemoveRenderingObserver(target, this);
mInObserverList = false;
}
}
NS_ASSERTION(!mInObserverList, "still in an observer list?");
}
static nsSVGRenderingObserverList *
GetObserverList(Element *aElement)
{
return static_cast<nsSVGRenderingObserverList*>
(aElement->GetProperty(nsGkAtoms::renderingobserverlist));
}
Element*
nsSVGRenderingObserver::GetReferencedElement()
{
Element* target = GetTarget();
#ifdef DEBUG
if (target) {
nsSVGRenderingObserverList *observerList = GetObserverList(target);
bool inObserverList = observerList && observerList->Contains(this);
NS_ASSERTION(inObserverList == mInObserverList, "failed to track whether we're in our referenced element's observer list!");
} else {
NS_ASSERTION(!mInObserverList, "In whose observer list are we, then?");
}
#endif
if (target && !mInObserverList) {
SVGObserverUtils::AddRenderingObserver(target, this);
mInObserverList = true;
}
return target;
}
nsIFrame*
nsSVGRenderingObserver::GetReferencedFrame()
{
Element* referencedElement = GetReferencedElement();
return referencedElement ? referencedElement->GetPrimaryFrame() : nullptr;
}
nsIFrame*
nsSVGRenderingObserver::GetReferencedFrame(LayoutFrameType aFrameType,
bool* aOK)
{
nsIFrame* frame = GetReferencedFrame();
if (frame) {
if (frame->Type() == aFrameType)
return frame;
if (aOK) {
*aOK = false;
}
}
return nullptr;
}
void
nsSVGRenderingObserver::OnNonDOMMutationRenderingChange()
{
mInObserverList = false;
OnRenderingChange();
}
void
nsSVGRenderingObserver::NotifyEvictedFromRenderingObserverList()
{
mInObserverList = false; // We've been removed from rendering-obs. list.
StopObserving(); // Remove ourselves from mutation-obs. list.
}
void
nsSVGRenderingObserver::AttributeChanged(dom::Element* aElement,
int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType,
const nsAttrValue* aOldValue)
{
// 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
nsSVGRenderingObserver::ContentAppended(nsIContent* aFirstNewContent)
{
OnRenderingChange();
}
void
nsSVGRenderingObserver::ContentInserted(nsIContent* aChild)
{
OnRenderingChange();
}
void
nsSVGRenderingObserver::ContentRemoved(nsIContent* aChild,
nsIContent* aPreviousSibling)
{
OnRenderingChange();
}
/**
* Note that in the current setup there are two separate observer lists.
*
* In nsSVGIDRenderingObserver's ctor, the new object adds itself to the
* mutation observer list maintained by the referenced element. In this way the
* nsSVGIDRenderingObserver is notified if there are any attribute or content
* tree changes to the element or any of its *descendants*.
*
* In nsSVGIDRenderingObserver::GetReferencedElement() the
* nsSVGIDRenderingObserver object also adds itself to an
* nsSVGRenderingObserverList 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.
*/
nsSVGIDRenderingObserver::nsSVGIDRenderingObserver(nsIURI* aURI,
nsIContent* aObservingContent,
bool aReferenceImage)
: mObservedElementTracker(this)
{
// Start watching the target element
mObservedElementTracker.Reset(aObservingContent, aURI, true, aReferenceImage);
StartObserving();
}
nsSVGIDRenderingObserver::~nsSVGIDRenderingObserver()
{
StopObserving();
}
void
nsSVGIDRenderingObserver::OnRenderingChange()
{
if (mObservedElementTracker.get() && mInObserverList) {
SVGObserverUtils::RemoveRenderingObserver(mObservedElementTracker.get(), this);
mInObserverList = false;
}
}
void
nsSVGFrameReferenceFromProperty::Detach()
{
mFrame = nullptr;
mFramePresShell = nullptr;
}
nsIFrame*
nsSVGFrameReferenceFromProperty::Get()
{
if (mFramePresShell && mFramePresShell->IsDestroying()) {
// mFrame is no longer valid.
Detach();
}
return mFrame;
}
NS_IMPL_ISUPPORTS(nsSVGRenderingObserverProperty, nsIMutationObserver)
void
nsSVGRenderingObserverProperty::OnRenderingChange()
{
nsSVGIDRenderingObserver::OnRenderingChange();
nsIFrame* frame = mFrameReference.Get();
if (frame && frame->HasAllStateBits(NS_FRAME_SVG_LAYOUT)) {
// Changes should propagate out to things that might be observing
// the referencing frame or its ancestors.
nsLayoutUtils::PostRestyleEvent(
frame->GetContent()->AsElement(), nsRestyleHint(0),
nsChangeHint_InvalidateRenderingObservers);
}
}
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSVGFilterReference)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSVGFilterReference)
NS_IMPL_CYCLE_COLLECTION_CLASS(nsSVGFilterReference)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsSVGFilterReference)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservedElementTracker)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsSVGFilterReference)
tmp->StopObserving();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservedElementTracker);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSVGFilterReference)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsSVGIDRenderingObserver)
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
NS_INTERFACE_MAP_ENTRY(nsISVGFilterReference)
NS_INTERFACE_MAP_END
nsSVGFilterFrame *
nsSVGFilterReference::GetFilterFrame()
{
return static_cast<nsSVGFilterFrame*>(
GetReferencedFrame(LayoutFrameType::SVGFilter, nullptr));
}
void
nsSVGFilterReference::OnRenderingChange()
{
nsSVGIDRenderingObserver::OnRenderingChange();
if (mFilterChainObserver) {
mFilterChainObserver->Invalidate();
}
}
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSVGFilterChainObserver)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSVGFilterChainObserver)
NS_IMPL_CYCLE_COLLECTION_CLASS(nsSVGFilterChainObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsSVGFilterChainObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReferences)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsSVGFilterChainObserver)
tmp->DetachReferences();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferences);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSVGFilterChainObserver)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
nsSVGFilterChainObserver::nsSVGFilterChainObserver(const nsTArray<nsStyleFilter>& aFilters,
nsIContent* aFilteredElement,
nsIFrame* aFilteredFrame)
{
for (uint32_t i = 0; i < aFilters.Length(); i++) {
if (aFilters[i].GetType() != NS_STYLE_FILTER_URL)
continue;
// aFilteredFrame can be null if this filter belongs to a
// CanvasRenderingContext2D.
nsCOMPtr<nsIURI> filterURL = aFilteredFrame
? SVGObserverUtils::GetFilterURI(aFilteredFrame, i)
: aFilters[i].GetURL()->ResolveLocalRef(aFilteredElement);
RefPtr<nsSVGFilterReference> reference =
new nsSVGFilterReference(filterURL, aFilteredElement, this);
mReferences.AppendElement(reference);
}
}
nsSVGFilterChainObserver::~nsSVGFilterChainObserver()
{
DetachReferences();
}
bool
nsSVGFilterChainObserver::ReferencesValidResources()
{
for (uint32_t i = 0; i < mReferences.Length(); i++) {
if (!mReferences[i]->ReferencesValidResource())
return false;
}
return true;
}
bool
nsSVGFilterChainObserver::IsInObserverLists() const
{
for (uint32_t i = 0; i < mReferences.Length(); i++) {
if (!mReferences[i]->IsInObserverList())
return false;
}
return true;
}
void
nsSVGFilterProperty::OnRenderingChange()
{
nsIFrame* frame = mFrameReference.Get();
if (!frame)
return;
// Repaint asynchronously in case the filter frame is being torn down
nsChangeHint changeHint =
nsChangeHint(nsChangeHint_RepaintFrame);
// Since we don't call nsSVGRenderingObserverProperty::
// OnRenderingChange, we have to add this bit ourselves.
if (frame->HasAllStateBits(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->GetStateBits() & NS_FRAME_IN_REFLOW)) {
changeHint |= nsChangeHint_UpdateOverflow;
}
frame->PresContext()->RestyleManager()->PostRestyleEvent(
frame->GetContent()->AsElement(), nsRestyleHint(0), changeHint);
}
void
nsSVGMarkerProperty::OnRenderingChange()
{
nsSVGRenderingObserverProperty::OnRenderingChange();
nsIFrame* frame = mFrameReference.Get();
if (!frame)
return;
NS_ASSERTION(frame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
// Don't need to request ReflowFrame if we're being reflowed.
if (!(frame->GetStateBits() & 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!!!
nsSVGUtils::ScheduleReflowSVG(frame);
}
frame->PresContext()->RestyleManager()->PostRestyleEvent(
frame->GetContent()->AsElement(), nsRestyleHint(0),
nsChangeHint_RepaintFrame);
}
NS_IMPL_ISUPPORTS(nsSVGMaskProperty, nsISupports)
nsSVGMaskProperty::nsSVGMaskProperty(nsIFrame* aFrame)
: mFrame(aFrame)
{
const nsStyleSVGReset *svgReset = aFrame->StyleSVGReset();
for (uint32_t i = 0; i < svgReset->mMask.mImageCount; i++) {
nsCOMPtr<nsIURI> maskUri = SVGObserverUtils::GetMaskURI(aFrame, i);
bool hasRef = false;
if (maskUri) {
maskUri->GetHasRef(&hasRef);
}
// Accrording to maskUri, nsSVGPaintingProperty's ctor may trigger an
// external SVG resource download, so we should pass maskUri in only if
// maskUri has a chance pointing to an SVG mask resource.
//
// And, an URL may refer to an SVG mask resource if it consists of
// a fragment.
nsSVGPaintingProperty* prop =
new nsSVGPaintingProperty(hasRef ? maskUri.get() : nullptr,
aFrame, false);
mProperties.AppendElement(prop);
}
}
void
nsSVGMaskProperty::ResolveImage(uint32_t aIndex)
{
const nsStyleSVGReset* svgReset = mFrame->StyleSVGReset();
MOZ_ASSERT(aIndex < svgReset->mMask.mImageCount);
nsStyleImage& image =
const_cast<nsStyleImage&>(svgReset->mMask.mLayers[aIndex].mImage);
if (!image.IsResolved()) {
MOZ_ASSERT(image.GetType() == nsStyleImageType::eStyleImageType_Image);
image.ResolveImage(mFrame->PresContext(), nullptr);
mozilla::css::ImageLoader* imageLoader =
mFrame->PresContext()->Document()->StyleImageLoader();
if (imgRequestProxy* req = image.GetImageData()) {
imageLoader->AssociateRequestToFrame(req, mFrame, 0);
}
}
}
bool
nsSVGTextPathProperty::TargetIsValid()
{
Element* target = GetTarget();
return target && target->IsSVGElement(nsGkAtoms::path);
}
void
nsSVGTextPathProperty::OnRenderingChange()
{
nsSVGRenderingObserverProperty::OnRenderingChange();
nsIFrame* frame = mFrameReference.Get();
if (!frame)
return;
NS_ASSERTION(frame->IsFrameOfType(nsIFrame::eSVG) ||
nsSVGUtils::IsInSVGTextSubtree(frame),
"SVG frame expected");
// Avoid getting into an infinite loop of reflows if the <textPath> is
// pointing to one of its ancestors. TargetIsValid returns true iff
// the target element is a <path> element, and we would not have this
// nsSVGTextPathProperty if this <textPath> were a descendant of the
// target <path>.
//
// Note that we still have to post the restyle event when we
// change from being valid to invalid, so that mPositions on the
// SVGTextFrame gets updated, skipping the <textPath>, ensuring
// that nothing gets painted for that element.
bool nowValid = TargetIsValid();
if (!mValid && !nowValid) {
// Just return if we were previously invalid, and are still invalid.
return;
}
mValid = nowValid;
// Repaint asynchronously in case the path frame is being torn down
nsChangeHint changeHint =
nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_UpdateTextPath);
frame->PresContext()->RestyleManager()->PostRestyleEvent(
frame->GetContent()->AsElement(), nsRestyleHint(0), changeHint);
}
static void
InvalidateAllContinuations(nsIFrame* aFrame)
{
for (nsIFrame* f = aFrame; f;
f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
f->InvalidateFrame();
}
}
void
nsSVGPaintingProperty::OnRenderingChange()
{
nsSVGRenderingObserverProperty::OnRenderingChange();
nsIFrame* frame = mFrameReference.Get();
if (!frame)
return;
if (frame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
frame->InvalidateFrameSubtree();
} else {
InvalidateAllContinuations(frame);
}
}
static nsSVGFilterProperty*
GetOrCreateFilterProperty(nsIFrame* aFrame)
{
const nsStyleEffects* effects = aFrame->StyleEffects();
if (!effects->HasFilters())
return nullptr;
nsSVGFilterProperty *prop =
aFrame->GetProperty(SVGObserverUtils::FilterProperty());
if (prop)
return prop;
prop = new nsSVGFilterProperty(effects->mFilters, aFrame);
NS_ADDREF(prop);
aFrame->SetProperty(SVGObserverUtils::FilterProperty(), prop);
return prop;
}
static nsSVGMaskProperty*
GetOrCreateMaskProperty(nsIFrame* aFrame)
{
nsSVGMaskProperty *prop =
aFrame->GetProperty(SVGObserverUtils::MaskProperty());
if (prop)
return prop;
prop = new nsSVGMaskProperty(aFrame);
NS_ADDREF(prop);
aFrame->SetProperty(SVGObserverUtils::MaskProperty(), prop);
return prop;
}
template<class T>
static T*
GetEffectProperty(nsIURI* aURI, nsIFrame* aFrame,
const mozilla::FramePropertyDescriptor<T>* aProperty)
{
if (!aURI)
return nullptr;
T* prop = aFrame->GetProperty(aProperty);
if (prop)
return prop;
prop = new T(aURI, aFrame, false);
NS_ADDREF(prop);
aFrame->SetProperty(aProperty, prop);
return prop;
}
nsSVGMarkerProperty*
SVGObserverUtils::GetMarkerProperty(nsIURI* aURI, nsIFrame* aFrame,
const mozilla::FramePropertyDescriptor<nsSVGMarkerProperty>* aProperty)
{
MOZ_ASSERT(aFrame->IsSVGGeometryFrame() &&
static_cast<SVGGeometryElement*>(aFrame->GetContent())->IsMarkable(),
"Bad frame");
return GetEffectProperty(aURI, aFrame, aProperty);
}
nsSVGTextPathProperty*
SVGObserverUtils::GetTextPathProperty(nsIURI* aURI, nsIFrame* aFrame,
const mozilla::FramePropertyDescriptor<nsSVGTextPathProperty>* aProperty)
{
return GetEffectProperty(aURI, aFrame, aProperty);
}
nsSVGPaintingProperty*
SVGObserverUtils::GetPaintingProperty(nsIURI* aURI, nsIFrame* aFrame,
const mozilla::FramePropertyDescriptor<nsSVGPaintingProperty>* aProperty)
{
return GetEffectProperty(aURI, aFrame, aProperty);
}
nsSVGPaintingProperty*
SVGObserverUtils::GetPaintingPropertyForURI(nsIURI* aURI, nsIFrame* aFrame,
URIObserverHashtablePropertyDescriptor aProperty)
{
if (!aURI)
return nullptr;
SVGObserverUtils::URIObserverHashtable *hashtable =
aFrame->GetProperty(aProperty);
if (!hashtable) {
hashtable = new SVGObserverUtils::URIObserverHashtable();
aFrame->SetProperty(aProperty, hashtable);
}
nsSVGPaintingProperty* prop =
static_cast<nsSVGPaintingProperty*>(hashtable->GetWeak(aURI));
if (!prop) {
bool watchImage = aProperty == SVGObserverUtils::BackgroundImageProperty();
prop = new nsSVGPaintingProperty(aURI, aFrame, watchImage);
hashtable->Put(aURI, prop);
}
return prop;
}
SVGObserverUtils::EffectProperties
SVGObserverUtils::GetEffectProperties(nsIFrame* aFrame)
{
NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
EffectProperties result;
const nsStyleSVGReset *style = aFrame->StyleSVGReset();
result.mFilter = GetOrCreateFilterProperty(aFrame);
if (style->mClipPath.GetType() == StyleShapeSourceType::URL) {
nsCOMPtr<nsIURI> pathURI = SVGObserverUtils::GetClipPathURI(aFrame);
result.mClipPath =
GetPaintingProperty(pathURI, aFrame, ClipPathProperty());
} else {
result.mClipPath = nullptr;
}
MOZ_ASSERT(style->mMask.mImageCount > 0);
result.mMask = style->HasMask()
? GetOrCreateMaskProperty(aFrame) : nullptr;
return result;
}
nsSVGPaintServerFrame *
SVGObserverUtils::GetPaintServer(nsIFrame* aTargetFrame,
nsStyleSVGPaint nsStyleSVG::* aPaint,
PaintingPropertyDescriptor aType)
{
// If we're looking at a frame within SVG text, then we need to look up
// to find the right frame to get the painting property off. We should at
// least look up past a text frame, and if the text frame's parent is the
// anonymous block frame, then we look up to its parent (the SVGTextFrame).
nsIFrame* frame = aTargetFrame;
if (frame->GetContent()->IsText()) {
frame = frame->GetParent();
nsIFrame* grandparent = frame->GetParent();
if (grandparent && grandparent->IsSVGTextFrame()) {
frame = grandparent;
}
}
const nsStyleSVG* svgStyle = frame->StyleSVG();
if ((svgStyle->*aPaint).Type() != eStyleSVGPaintType_Server)
return nullptr;
nsCOMPtr<nsIURI> paintServerURL =
SVGObserverUtils::GetPaintURI(frame, aPaint);
nsSVGPaintingProperty *property =
SVGObserverUtils::GetPaintingProperty(paintServerURL, frame, aType);
if (!property)
return nullptr;
nsIFrame* result = property->GetReferencedFrame();
if (!result)
return nullptr;
LayoutFrameType type = result->Type();
if (type != LayoutFrameType::SVGLinearGradient &&
type != LayoutFrameType::SVGRadialGradient &&
type != LayoutFrameType::SVGPattern)
return nullptr;
return static_cast<nsSVGPaintServerFrame*>(result);
}
nsSVGClipPathFrame *
SVGObserverUtils::EffectProperties::GetClipPathFrame()
{
if (!mClipPath)
return nullptr;
nsSVGClipPathFrame* frame = static_cast<nsSVGClipPathFrame*>(
mClipPath->GetReferencedFrame(LayoutFrameType::SVGClipPath, nullptr));
return frame;
}
nsTArray<nsSVGMaskFrame *>
SVGObserverUtils::EffectProperties::GetMaskFrames()
{
nsTArray<nsSVGMaskFrame *> result;
if (!mMask)
return result;
bool ok = true;
const nsTArray<RefPtr<nsSVGPaintingProperty>>& props = mMask->GetProps();
for (size_t i = 0; i < props.Length(); i++) {
nsSVGMaskFrame* maskFrame = static_cast<nsSVGMaskFrame*>(
props[i]->GetReferencedFrame(LayoutFrameType::SVGMask, &ok));
MOZ_ASSERT(!maskFrame || ok);
if (!ok) {
// We can not find the specific SVG mask resource in the downloaded SVG
// document. There are two possibilities:
// 1. The given resource id is invalid.
// 2. The given resource id refers to a viewbox.
//
// Hand it over to the style image.
mMask->ResolveImage(i);
}
result.AppendElement(maskFrame);
}
return result;
}
bool
SVGObserverUtils::EffectProperties::HasNoOrValidEffects()
{
return HasNoOrValidClipPath() && HasNoOrValidMask() && HasNoOrValidFilter();
}
bool
SVGObserverUtils::EffectProperties::HasNoOrValidClipPath()
{
if (mClipPath) {
bool ok = true;
nsSVGClipPathFrame* frame = static_cast<nsSVGClipPathFrame*>(
mClipPath->GetReferencedFrame(LayoutFrameType::SVGClipPath, &ok));
if (!ok || (frame && !frame->IsValid())) {
return false;
}
}
return true;
}
bool
SVGObserverUtils::EffectProperties::HasNoOrValidMask()
{
if (mMask) {
bool ok = true;
const nsTArray<RefPtr<nsSVGPaintingProperty>>& props = mMask->GetProps();
for (size_t i = 0; i < props.Length(); i++) {
props[i]->GetReferencedFrame(LayoutFrameType::SVGMask, &ok);
if (!ok) {
return false;
}
}
}
return true;
}
void
SVGObserverUtils::UpdateEffects(nsIFrame* aFrame)
{
NS_ASSERTION(aFrame->GetContent()->IsElement(),
"aFrame's content should be an element");
aFrame->DeleteProperty(FilterProperty());
aFrame->DeleteProperty(MaskProperty());
aFrame->DeleteProperty(ClipPathProperty());
aFrame->DeleteProperty(MarkerBeginProperty());
aFrame->DeleteProperty(MarkerMiddleProperty());
aFrame->DeleteProperty(MarkerEndProperty());
aFrame->DeleteProperty(FillProperty());
aFrame->DeleteProperty(StrokeProperty());
aFrame->DeleteProperty(BackgroundImageProperty());
// Ensure that the filter is repainted correctly
// We can't do that in OnRenderingChange as the referenced frame may
// not be valid
GetOrCreateFilterProperty(aFrame);
if (aFrame->IsSVGGeometryFrame() &&
static_cast<SVGGeometryElement*>(aFrame->GetContent())->IsMarkable()) {
// Set marker properties here to avoid reference loops
nsCOMPtr<nsIURI> markerURL =
GetMarkerURI(aFrame, &nsStyleSVG::mMarkerStart);
GetMarkerProperty(markerURL, aFrame, MarkerBeginProperty());
markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerMid);
GetMarkerProperty(markerURL, aFrame, MarkerMiddleProperty());
markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerEnd);
GetMarkerProperty(markerURL, aFrame, MarkerEndProperty());
}
}
nsSVGFilterProperty*
SVGObserverUtils::GetFilterProperty(nsIFrame* aFrame)
{
NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
if (!aFrame->StyleEffects()->HasFilters())
return nullptr;
return aFrame->GetProperty(FilterProperty());
}
void
nsSVGRenderingObserverList::InvalidateAll()
{
if (mObservers.Count() == 0)
return;
AutoTArray<nsSVGRenderingObserver*,10> observers;
for (auto it = mObservers.Iter(); !it.Done(); it.Next()) {
observers.AppendElement(it.Get()->GetKey());
}
mObservers.Clear();
for (uint32_t i = 0; i < observers.Length(); ++i) {
observers[i]->OnNonDOMMutationRenderingChange();
}
}
void
nsSVGRenderingObserverList::InvalidateAllForReflow()
{
if (mObservers.Count() == 0)
return;
AutoTArray<nsSVGRenderingObserver*,10> observers;
for (auto it = mObservers.Iter(); !it.Done(); it.Next()) {
nsSVGRenderingObserver* obs = it.Get()->GetKey();
if (obs->ObservesReflow()) {
observers.AppendElement(obs);
it.Remove();
}
}
for (uint32_t i = 0; i < observers.Length(); ++i) {
observers[i]->OnNonDOMMutationRenderingChange();
}
}
void
nsSVGRenderingObserverList::RemoveAll()
{
AutoTArray<nsSVGRenderingObserver*,10> observers;
for (auto it = mObservers.Iter(); !it.Done(); it.Next()) {
observers.AppendElement(it.Get()->GetKey());
}
mObservers.Clear();
// Our list is now cleared. We need to notify the observers we've removed,
// so they can update their state & remove themselves as mutation-observers.
for (uint32_t i = 0; i < observers.Length(); ++i) {
observers[i]->NotifyEvictedFromRenderingObserverList();
}
}
void
SVGObserverUtils::AddRenderingObserver(Element* aElement,
nsSVGRenderingObserver* aObserver)
{
nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
if (!observerList) {
observerList = new nsSVGRenderingObserverList();
if (!observerList)
return;
aElement->SetProperty(nsGkAtoms::renderingobserverlist, observerList,
nsINode::DeleteProperty<nsSVGRenderingObserverList>);
}
aElement->SetHasRenderingObservers(true);
observerList->Add(aObserver);
}
void
SVGObserverUtils::RemoveRenderingObserver(Element* aElement,
nsSVGRenderingObserver* aObserver)
{
nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
if (observerList) {
NS_ASSERTION(observerList->Contains(aObserver),
"removing observer from an element we're not observing?");
observerList->Remove(aObserver);
if (observerList->IsEmpty()) {
aElement->SetHasRenderingObservers(false);
}
}
}
void
SVGObserverUtils::RemoveAllRenderingObservers(Element* aElement)
{
nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
if (observerList) {
observerList->RemoveAll();
aElement->SetHasRenderingObservers(false);
}
}
void
SVGObserverUtils::InvalidateRenderingObservers(nsIFrame* aFrame)
{
NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame must be first continuation");
nsIContent* content = aFrame->GetContent();
if (!content || !content->IsElement())
return;
// If the rendering has changed, the bounds may well have changed too:
aFrame->DeleteProperty(nsSVGUtils::ObjectBoundingBoxProperty());
nsSVGRenderingObserverList *observerList =
GetObserverList(content->AsElement());
if (observerList) {
observerList->InvalidateAll();
return;
}
// Check ancestor SVG containers. The root frame cannot be of type
// eSVGContainer so we don't have to check f for null here.
for (nsIFrame *f = aFrame->GetParent();
f->IsFrameOfType(nsIFrame::eSVGContainer); f = f->GetParent()) {
if (f->GetContent()->IsElement()) {
observerList = GetObserverList(f->GetContent()->AsElement());
if (observerList) {
observerList->InvalidateAll();
return;
}
}
}
}
void
SVGObserverUtils::InvalidateDirectRenderingObservers(Element* aElement,
uint32_t aFlags /* = 0 */)
{
nsIFrame* frame = aElement->GetPrimaryFrame();
if (frame) {
// If the rendering has changed, the bounds may well have changed too:
frame->DeleteProperty(nsSVGUtils::ObjectBoundingBoxProperty());
}
if (aElement->HasRenderingObservers()) {
nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
if (observerList) {
if (aFlags & INVALIDATE_REFLOW) {
observerList->InvalidateAllForReflow();
} else {
observerList->InvalidateAll();
}
}
}
}
void
SVGObserverUtils::InvalidateDirectRenderingObservers(nsIFrame* aFrame,
uint32_t aFlags /* = 0 */)
{
nsIContent* content = aFrame->GetContent();
if (content && content->IsElement()) {
InvalidateDirectRenderingObservers(content->AsElement(), aFlags);
}
}
already_AddRefed<nsIURI>
SVGObserverUtils::GetBaseURLForLocalRef(nsIContent* content, nsIURI* aDocURI)
{
MOZ_ASSERT(content);
// For a local-reference URL, resolve that fragment against the current
// document that relative URLs are resolved against.
nsCOMPtr<nsIURI> baseURI = content->OwnerDoc()->GetDocumentURI();
nsCOMPtr<nsIURI> originalURI;
// Content is in a shadow tree. If this URL was specified in the subtree
// referenced by the <use>(or -moz-binding) 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()) {
originalURI = use->GetSourceDocURI();
} else if (content->IsInAnonymousSubtree()) {
nsIContent* bindingParent = content->GetBindingParent();
if (bindingParent) {
nsXBLBinding* binding = bindingParent->GetXBLBinding();
if (binding) {
originalURI = binding->GetSourceDocURI();
} else {
MOZ_ASSERT(content->IsInNativeAnonymousSubtree(),
"a non-native anonymous tree which is not from "
"an XBL binding?");
}
}
}
if (originalURI) {
bool isEqualsExceptRef = false;
aDocURI->EqualsExceptRef(originalURI, &isEqualsExceptRef);
if (isEqualsExceptRef) {
return originalURI.forget();
}
}
return baseURI.forget();
}
static already_AddRefed<nsIURI>
ResolveURLUsingLocalRef(nsIFrame* aFrame, const css::URLValueData* aURL)
{
MOZ_ASSERT(aFrame);
if (!aURL) {
return nullptr;
}
// Non-local-reference URL.
if (!aURL->IsLocalRef()) {
nsCOMPtr<nsIURI> result = aURL->GetURI();
return result.forget();
}
nsCOMPtr<nsIURI> baseURI =
SVGObserverUtils::GetBaseURLForLocalRef(aFrame->GetContent(),
aURL->GetURI());
return aURL->ResolveLocalRef(baseURI);
}
already_AddRefed<nsIURI>
SVGObserverUtils::GetMarkerURI(nsIFrame* aFrame,
RefPtr<css::URLValue> nsStyleSVG::* aMarker)
{
return ResolveURLUsingLocalRef(aFrame, aFrame->StyleSVG()->*aMarker);
}
already_AddRefed<nsIURI>
SVGObserverUtils::GetClipPathURI(nsIFrame* aFrame)
{
const nsStyleSVGReset* svgResetStyle = aFrame->StyleSVGReset();
MOZ_ASSERT(svgResetStyle->mClipPath.GetType() == StyleShapeSourceType::URL);
css::URLValue* url = svgResetStyle->mClipPath.GetURL();
return ResolveURLUsingLocalRef(aFrame, url);
}
already_AddRefed<nsIURI>
SVGObserverUtils::GetFilterURI(nsIFrame* aFrame, uint32_t aIndex)
{
const nsStyleEffects* effects = aFrame->StyleEffects();
MOZ_ASSERT(effects->mFilters.Length() > aIndex);
MOZ_ASSERT(effects->mFilters[aIndex].GetType() == NS_STYLE_FILTER_URL);
return ResolveURLUsingLocalRef(aFrame, effects->mFilters[aIndex].GetURL());
}
already_AddRefed<nsIURI>
SVGObserverUtils::GetFilterURI(nsIFrame* aFrame, const nsStyleFilter& aFilter)
{
MOZ_ASSERT(aFrame->StyleEffects()->mFilters.Length());
MOZ_ASSERT(aFilter.GetType() == NS_STYLE_FILTER_URL);
return ResolveURLUsingLocalRef(aFrame, aFilter.GetURL());
}
already_AddRefed<nsIURI>
SVGObserverUtils::GetPaintURI(nsIFrame* aFrame,
nsStyleSVGPaint nsStyleSVG::* aPaint)
{
const nsStyleSVG* svgStyle = aFrame->StyleSVG();
MOZ_ASSERT((svgStyle->*aPaint).Type() ==
nsStyleSVGPaintType::eStyleSVGPaintType_Server);
return ResolveURLUsingLocalRef(aFrame,
(svgStyle->*aPaint).GetPaintServer());
}
already_AddRefed<nsIURI>
SVGObserverUtils::GetMaskURI(nsIFrame* aFrame, uint32_t aIndex)
{
const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
MOZ_ASSERT(svgReset->mMask.mLayers.Length() > aIndex);
mozilla::css::URLValueData* data =
svgReset->mMask.mLayers[aIndex].mImage.GetURLValue();
return ResolveURLUsingLocalRef(aFrame, data);
}