gecko-dev/layout/svg/nsSVGEffects.cpp

698 lines
21 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "nsSVGEffects.h"
// Keep others in (case-insensitive) order:
#include "nsCSSFrameConstructor.h"
#include "nsISupportsImpl.h"
#include "nsSVGClipPathFrame.h"
#include "nsSVGPaintServerFrame.h"
#include "nsSVGPathGeometryElement.h"
#include "nsSVGFilterFrame.h"
#include "nsSVGMaskFrame.h"
#include "nsSVGTextPathFrame.h"
#include "nsIReflowCallback.h"
using namespace mozilla;
using namespace mozilla::dom;
// nsSVGRenderingObserver impl
NS_IMPL_ISUPPORTS1(nsSVGRenderingObserver, nsIMutationObserver)
void
nsSVGRenderingObserver::StartListening()
{
Element* target = GetTarget();
if (target) {
target->AddMutationObserver(this);
}
}
void
nsSVGRenderingObserver::StopListening()
{
Element* target = GetTarget();
if (target) {
target->RemoveMutationObserver(this);
if (mInObserverList) {
nsSVGEffects::RemoveRenderingObserver(target, this);
mInObserverList = false;
}
}
NS_ASSERTION(!mInObserverList, "still in an observer list?");
}
/**
* 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.
*/
#ifdef _MSC_VER
// Disable "warning C4355: 'this' : used in base member initializer list".
// We can ignore that warning because we know that mElement's constructor
// doesn't dereference the pointer passed to it.
#pragma warning(push)
#pragma warning(disable:4355)
#endif
nsSVGIDRenderingObserver::nsSVGIDRenderingObserver(nsIURI *aURI,
nsIFrame *aFrame,
bool aReferenceImage)
: mElement(this), mFrame(aFrame),
mFramePresShell(aFrame->PresContext()->PresShell())
#ifdef _MSC_VER
#pragma warning(pop)
#endif
{
// Start watching the target element
mElement.Reset(aFrame->GetContent(), aURI, true, aReferenceImage);
StartListening();
}
nsSVGIDRenderingObserver::~nsSVGIDRenderingObserver()
{
StopListening();
}
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) {
nsSVGEffects::AddRenderingObserver(target, this);
mInObserverList = true;
}
return target;
}
nsIFrame*
nsSVGRenderingObserver::GetReferencedFrame()
{
Element* referencedElement = GetReferencedElement();
return referencedElement ? referencedElement->GetPrimaryFrame() : nullptr;
}
nsIFrame*
nsSVGRenderingObserver::GetReferencedFrame(nsIAtom* aFrameType, bool* aOK)
{
nsIFrame* frame = GetReferencedFrame();
if (frame) {
if (frame->GetType() == aFrameType)
return frame;
if (aOK) {
*aOK = false;
}
}
return nullptr;
}
void
nsSVGIDRenderingObserver::DoUpdate()
{
if (mFramePresShell->IsDestroying()) {
// mFrame is no longer valid. Bail out.
mFrame = nullptr;
return;
}
if (mElement.get() && mInObserverList) {
nsSVGEffects::RemoveRenderingObserver(mElement.get(), this);
mInObserverList = false;
}
if (mFrame && mFrame->IsFrameOfType(nsIFrame::eSVG)) {
// Changes should propagate out to things that might be observing
// the referencing frame or its ancestors.
nsSVGEffects::InvalidateRenderingObservers(mFrame);
}
}
void
nsSVGRenderingObserver::InvalidateViaReferencedElement()
{
mInObserverList = false;
DoUpdate();
}
void
nsSVGRenderingObserver::NotifyEvictedFromRenderingObserverList()
{
mInObserverList = false; // We've been removed from rendering-obs. list.
StopListening(); // Remove ourselves from mutation-obs. list.
}
void
nsSVGRenderingObserver::AttributeChanged(nsIDocument* aDocument,
dom::Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType)
{
// 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.
DoUpdate();
}
void
nsSVGRenderingObserver::ContentAppended(nsIDocument *aDocument,
nsIContent *aContainer,
nsIContent *aFirstNewContent,
int32_t /* unused */)
{
DoUpdate();
}
void
nsSVGRenderingObserver::ContentInserted(nsIDocument *aDocument,
nsIContent *aContainer,
nsIContent *aChild,
int32_t /* unused */)
{
DoUpdate();
}
void
nsSVGRenderingObserver::ContentRemoved(nsIDocument *aDocument,
nsIContent *aContainer,
nsIContent *aChild,
int32_t aIndexInContainer,
nsIContent *aPreviousSibling)
{
DoUpdate();
}
NS_IMPL_ISUPPORTS_INHERITED1(nsSVGFilterProperty,
nsSVGIDRenderingObserver,
nsISVGFilterProperty)
nsSVGFilterFrame *
nsSVGFilterProperty::GetFilterFrame()
{
return static_cast<nsSVGFilterFrame *>
(GetReferencedFrame(nsGkAtoms::svgFilterFrame, nullptr));
}
static void
InvalidateAllContinuations(nsIFrame* aFrame)
{
for (nsIFrame* f = aFrame; f;
f = nsLayoutUtils::GetNextContinuationOrSpecialSibling(f)) {
f->InvalidateFrame();
}
}
void
nsSVGFilterProperty::DoUpdate()
{
nsSVGIDRenderingObserver::DoUpdate();
if (!mFrame)
return;
// Repaint asynchronously in case the filter frame is being torn down
nsChangeHint changeHint =
nsChangeHint(nsChangeHint_RepaintFrame);
// Don't need to request UpdateOverflow if we're being reflowed.
if (!(mFrame->GetStateBits() & NS_FRAME_IN_REFLOW)) {
NS_UpdateHint(changeHint, nsChangeHint_UpdateOverflow);
}
mFramePresShell->FrameConstructor()->PostRestyleEvent(
mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint);
}
void
nsSVGMarkerProperty::DoUpdate()
{
nsSVGIDRenderingObserver::DoUpdate();
if (!mFrame)
return;
NS_ASSERTION(mFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
// Repaint asynchronously in case the marker frame is being torn down
nsChangeHint changeHint =
nsChangeHint(nsChangeHint_RepaintFrame);
// Don't need to request ReflowFrame if we're being reflowed.
if (!(mFrame->GetStateBits() & NS_FRAME_IN_REFLOW)) {
// XXXjwatt: We need to unify SVG into standard reflow so we can just use
// nsChangeHint_NeedReflow | nsChangeHint_NeedDirtyReflow here.
nsSVGUtils::InvalidateAndScheduleReflowSVG(mFrame);
}
mFramePresShell->FrameConstructor()->PostRestyleEvent(
mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint);
}
void
nsSVGTextPathProperty::DoUpdate()
{
nsSVGIDRenderingObserver::DoUpdate();
if (!mFrame)
return;
NS_ASSERTION(mFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
if (mFrame->GetType() == nsGkAtoms::svgTextPathFrame) {
// Repaint asynchronously in case the path frame is being torn down
nsChangeHint changeHint =
nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_UpdateTextPath);
mFramePresShell->FrameConstructor()->PostRestyleEvent(
mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint);
}
}
void
nsSVGPaintingProperty::DoUpdate()
{
nsSVGIDRenderingObserver::DoUpdate();
if (!mFrame)
return;
if (mFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
nsSVGUtils::InvalidateBounds(mFrame);
} else {
InvalidateAllContinuations(mFrame);
}
}
static nsSVGRenderingObserver *
CreateFilterProperty(nsIURI *aURI, nsIFrame *aFrame, bool aReferenceImage)
{ return new nsSVGFilterProperty(aURI, aFrame, aReferenceImage); }
static nsSVGRenderingObserver *
CreateMarkerProperty(nsIURI *aURI, nsIFrame *aFrame, bool aReferenceImage)
{ return new nsSVGMarkerProperty(aURI, aFrame, aReferenceImage); }
static nsSVGRenderingObserver *
CreateTextPathProperty(nsIURI *aURI, nsIFrame *aFrame, bool aReferenceImage)
{ return new nsSVGTextPathProperty(aURI, aFrame, aReferenceImage); }
static nsSVGRenderingObserver *
CreatePaintingProperty(nsIURI *aURI, nsIFrame *aFrame, bool aReferenceImage)
{ return new nsSVGPaintingProperty(aURI, aFrame, aReferenceImage); }
static nsSVGRenderingObserver *
GetEffectProperty(nsIURI *aURI, nsIFrame *aFrame,
const FramePropertyDescriptor *aProperty,
nsSVGRenderingObserver * (* aCreate)(nsIURI *, nsIFrame *, bool))
{
if (!aURI)
return nullptr;
FrameProperties props = aFrame->Properties();
nsSVGRenderingObserver *prop =
static_cast<nsSVGRenderingObserver*>(props.Get(aProperty));
if (prop)
return prop;
prop = aCreate(aURI, aFrame, false);
if (!prop)
return nullptr;
NS_ADDREF(prop);
props.Set(aProperty, static_cast<nsISupports*>(prop));
return prop;
}
nsSVGMarkerProperty *
nsSVGEffects::GetMarkerProperty(nsIURI *aURI, nsIFrame *aFrame,
const FramePropertyDescriptor *aProp)
{
NS_ABORT_IF_FALSE(aFrame->GetType() == nsGkAtoms::svgPathGeometryFrame &&
static_cast<nsSVGPathGeometryElement*>(aFrame->GetContent())->IsMarkable(),
"Bad frame");
return static_cast<nsSVGMarkerProperty*>(
GetEffectProperty(aURI, aFrame, aProp, CreateMarkerProperty));
}
nsSVGTextPathProperty *
nsSVGEffects::GetTextPathProperty(nsIURI *aURI, nsIFrame *aFrame,
const FramePropertyDescriptor *aProp)
{
return static_cast<nsSVGTextPathProperty*>(
GetEffectProperty(aURI, aFrame, aProp, CreateTextPathProperty));
}
nsSVGPaintingProperty *
nsSVGEffects::GetPaintingProperty(nsIURI *aURI, nsIFrame *aFrame,
const FramePropertyDescriptor *aProp)
{
return static_cast<nsSVGPaintingProperty*>(
GetEffectProperty(aURI, aFrame, aProp, CreatePaintingProperty));
}
static nsSVGRenderingObserver *
GetEffectPropertyForURI(nsIURI *aURI, nsIFrame *aFrame,
const FramePropertyDescriptor *aProperty,
nsSVGRenderingObserver * (* aCreate)(nsIURI *, nsIFrame *, bool))
{
if (!aURI)
return nullptr;
FrameProperties props = aFrame->Properties();
nsSVGEffects::URIObserverHashtable *hashtable =
static_cast<nsSVGEffects::URIObserverHashtable*>(props.Get(aProperty));
if (!hashtable) {
hashtable = new nsSVGEffects::URIObserverHashtable();
hashtable->Init();
props.Set(aProperty, hashtable);
}
nsSVGRenderingObserver* prop =
static_cast<nsSVGRenderingObserver*>(hashtable->GetWeak(aURI));
if (!prop) {
bool watchImage = aProperty == nsSVGEffects::BackgroundImageProperty();
prop = aCreate(aURI, aFrame, watchImage);
hashtable->Put(aURI, prop);
}
return prop;
}
nsSVGPaintingProperty *
nsSVGEffects::GetPaintingPropertyForURI(nsIURI *aURI, nsIFrame *aFrame,
const FramePropertyDescriptor *aProp)
{
return static_cast<nsSVGPaintingProperty*>(
GetEffectPropertyForURI(aURI, aFrame, aProp, CreatePaintingProperty));
}
nsSVGEffects::EffectProperties
nsSVGEffects::GetEffectProperties(nsIFrame *aFrame)
{
NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
EffectProperties result;
const nsStyleSVGReset *style = aFrame->GetStyleSVGReset();
result.mFilter = static_cast<nsSVGFilterProperty*>
(GetEffectProperty(style->mFilter, aFrame, FilterProperty(),
CreateFilterProperty));
result.mClipPath =
GetPaintingProperty(style->mClipPath, aFrame, ClipPathProperty());
result.mMask =
GetPaintingProperty(style->mMask, aFrame, MaskProperty());
return result;
}
nsSVGPaintServerFrame *
nsSVGEffects::GetPaintServer(nsIFrame *aTargetFrame, const nsStyleSVGPaint *aPaint,
const FramePropertyDescriptor *aType)
{
if (aPaint->mType != eStyleSVGPaintType_Server)
return nullptr;
nsIFrame *frame = aTargetFrame->GetContent()->IsNodeOfType(nsINode::eTEXT) ?
aTargetFrame->GetParent() : aTargetFrame;
nsSVGPaintingProperty *property =
nsSVGEffects::GetPaintingProperty(aPaint->mPaint.mPaintServer, frame, aType);
if (!property)
return nullptr;
nsIFrame *result = property->GetReferencedFrame();
if (!result)
return nullptr;
nsIAtom *type = result->GetType();
if (type != nsGkAtoms::svgLinearGradientFrame &&
type != nsGkAtoms::svgRadialGradientFrame &&
type != nsGkAtoms::svgPatternFrame)
return nullptr;
return static_cast<nsSVGPaintServerFrame*>(result);
}
nsSVGClipPathFrame *
nsSVGEffects::EffectProperties::GetClipPathFrame(bool *aOK)
{
if (!mClipPath)
return nullptr;
nsSVGClipPathFrame *frame = static_cast<nsSVGClipPathFrame *>
(mClipPath->GetReferencedFrame(nsGkAtoms::svgClipPathFrame, aOK));
if (frame && aOK && *aOK) {
*aOK = frame->IsValid();
}
return frame;
}
nsSVGMaskFrame *
nsSVGEffects::EffectProperties::GetMaskFrame(bool *aOK)
{
if (!mMask)
return nullptr;
return static_cast<nsSVGMaskFrame *>
(mMask->GetReferencedFrame(nsGkAtoms::svgMaskFrame, aOK));
}
void
nsSVGEffects::UpdateEffects(nsIFrame *aFrame)
{
NS_ASSERTION(aFrame->GetContent()->IsElement(),
"aFrame's content should be an element");
FrameProperties props = aFrame->Properties();
props.Delete(FilterProperty());
props.Delete(MaskProperty());
props.Delete(ClipPathProperty());
props.Delete(MarkerBeginProperty());
props.Delete(MarkerMiddleProperty());
props.Delete(MarkerEndProperty());
props.Delete(FillProperty());
props.Delete(StrokeProperty());
props.Delete(BackgroundImageProperty());
// Ensure that the filter is repainted correctly
// We can't do that in DoUpdate as the referenced frame may not be valid
GetEffectProperty(aFrame->GetStyleSVGReset()->mFilter,
aFrame, FilterProperty(), CreateFilterProperty);
if (aFrame->GetType() == nsGkAtoms::svgPathGeometryFrame &&
static_cast<nsSVGPathGeometryElement*>(aFrame->GetContent())->IsMarkable()) {
// Set marker properties here to avoid reference loops
const nsStyleSVG *style = aFrame->GetStyleSVG();
GetEffectProperty(style->mMarkerStart, aFrame, MarkerBeginProperty(),
CreateMarkerProperty);
GetEffectProperty(style->mMarkerMid, aFrame, MarkerMiddleProperty(),
CreateMarkerProperty);
GetEffectProperty(style->mMarkerEnd, aFrame, MarkerEndProperty(),
CreateMarkerProperty);
}
}
nsSVGFilterProperty *
nsSVGEffects::GetFilterProperty(nsIFrame *aFrame)
{
NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
if (!aFrame->GetStyleSVGReset()->mFilter)
return nullptr;
return static_cast<nsSVGFilterProperty *>
(aFrame->Properties().Get(FilterProperty()));
}
static PLDHashOperator
GatherEnumerator(nsPtrHashKey<nsSVGRenderingObserver>* aEntry, void* aArg)
{
nsTArray<nsSVGRenderingObserver*>* array =
static_cast<nsTArray<nsSVGRenderingObserver*>*>(aArg);
array->AppendElement(aEntry->GetKey());
return PL_DHASH_REMOVE;
}
static PLDHashOperator
GatherEnumeratorForReflow(nsPtrHashKey<nsSVGRenderingObserver>* aEntry, void* aArg)
{
if (!aEntry->GetKey()->ObservesReflow()) {
return PL_DHASH_NEXT;
}
nsTArray<nsSVGRenderingObserver*>* array =
static_cast<nsTArray<nsSVGRenderingObserver*>*>(aArg);
array->AppendElement(aEntry->GetKey());
return PL_DHASH_REMOVE;
}
void
nsSVGRenderingObserverList::InvalidateAll()
{
if (mObservers.Count() == 0)
return;
nsAutoTArray<nsSVGRenderingObserver*,10> observers;
// The PL_DHASH_REMOVE in GatherEnumerator drops all our observers here:
mObservers.EnumerateEntries(GatherEnumerator, &observers);
for (uint32_t i = 0; i < observers.Length(); ++i) {
observers[i]->InvalidateViaReferencedElement();
}
}
void
nsSVGRenderingObserverList::InvalidateAllForReflow()
{
if (mObservers.Count() == 0)
return;
nsAutoTArray<nsSVGRenderingObserver*,10> observers;
// The PL_DHASH_REMOVE in GatherEnumerator drops all our observers here:
mObservers.EnumerateEntries(GatherEnumeratorForReflow, &observers);
for (uint32_t i = 0; i < observers.Length(); ++i) {
observers[i]->InvalidateViaReferencedElement();
}
}
void
nsSVGRenderingObserverList::RemoveAll()
{
nsAutoTArray<nsSVGRenderingObserver*,10> observers;
// The PL_DHASH_REMOVE in GatherEnumerator drops all our observers here:
mObservers.EnumerateEntries(GatherEnumerator, &observers);
// 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();
}
}
static void
DestroyObservers(void *aObject, nsIAtom *aPropertyName,
void *aPropertyValue, void *aData)
{
delete static_cast<nsSVGRenderingObserverList*>(aPropertyValue);
}
void
nsSVGEffects::AddRenderingObserver(Element *aElement, nsSVGRenderingObserver *aObserver)
{
nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
if (!observerList) {
observerList = new nsSVGRenderingObserverList();
if (!observerList)
return;
aElement->SetProperty(nsGkAtoms::renderingobserverlist, observerList, DestroyObservers);
}
aElement->SetHasRenderingObservers(true);
observerList->Add(aObserver);
}
void
nsSVGEffects::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
nsSVGEffects::RemoveAllRenderingObservers(Element *aElement)
{
nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
if (observerList) {
observerList->RemoveAll();
aElement->SetHasRenderingObservers(false);
}
}
void
nsSVGEffects::InvalidateRenderingObservers(nsIFrame *aFrame)
{
NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame must be first continuation");
if (!aFrame->GetContent()->IsElement())
return;
nsSVGRenderingObserverList *observerList =
GetObserverList(aFrame->GetContent()->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
nsSVGEffects::InvalidateDirectRenderingObservers(Element *aElement, uint32_t aFlags /* = 0 */)
{
if (aElement->HasRenderingObservers()) {
nsSVGRenderingObserverList *observerList = GetObserverList(aElement);
if (observerList) {
if (aFlags & INVALIDATE_REFLOW) {
observerList->InvalidateAllForReflow();
} else {
observerList->InvalidateAll();
}
}
}
}
void
nsSVGEffects::InvalidateDirectRenderingObservers(nsIFrame *aFrame, uint32_t aFlags /* = 0 */)
{
if (aFrame->GetContent() && aFrame->GetContent()->IsElement()) {
InvalidateDirectRenderingObservers(aFrame->GetContent()->AsElement(), aFlags);
}
}