/* -*- 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 "mozilla/RestyleManagerInlines.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);
}
}
}
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()->IsNodeOfType(nsINode::eTEXT)) {
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();
if (content->IsInAnonymousSubtree()) {
nsIContent* bindingParent = content->GetBindingParent();
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 (bindingParent) {
if (content->IsAnonymousContentInSVGUseSubtree()) {
SVGUseElement* useElement = static_cast<SVGUseElement*>(bindingParent);
originalURI = useElement->GetSourceDocURI();
} else {
nsXBLBinding* binding = bindingParent->GetXBLBinding();
if (binding) {
originalURI = binding->GetSourceDocURI();
} else {
MOZ_ASSERT(content->IsInNativeAnonymousSubtree(),
"an non-native anonymous tree which is not from "
"an XBL binding?");
}
}
if (originalURI) {
bool isEqualsExceptRef = false;
aDocURI->EqualsExceptRef(originalURI, &isEqualsExceptRef);
if (isEqualsExceptRef) {
baseURI = originalURI;
}
}
}
}
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);
}