/* -*- 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 "nsIReflowCallback.h" #include "RestyleManager.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. */ nsSVGIDRenderingObserver::nsSVGIDRenderingObserver(nsIURI *aURI, nsIFrame *aFrame, bool aReferenceImage) : mElement(MOZ_THIS_IN_INITIALIZER_LIST()), mFrame(aFrame), mFramePresShell(aFrame->PresContext()->PresShell()) { // Start watching the target element mElement.Reset(aFrame->GetContent(), aURI, true, aReferenceImage); StartListening(); } nsSVGIDRenderingObserver::~nsSVGIDRenderingObserver() { StopListening(); } static nsSVGRenderingObserverList * GetObserverList(Element *aElement) { return static_cast (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_ISUPPORTS1(nsSVGFilterProperty, nsISupports) nsSVGFilterProperty::nsSVGFilterProperty(const nsTArray &aFilters, nsIFrame *aFilteredFrame) : mFilters(aFilters) { for (uint32_t i = 0; i < mFilters.Length(); i++) { if (mFilters[i].GetType() != NS_STYLE_FILTER_URL) continue; nsSVGFilterReference *reference = new nsSVGFilterReference(mFilters[i].GetURL(), aFilteredFrame); NS_ADDREF(reference); mReferences.AppendElement(reference); } } nsSVGFilterProperty::~nsSVGFilterProperty() { for (uint32_t i = 0; i < mReferences.Length(); i++) { NS_RELEASE(mReferences[i]); } } bool nsSVGFilterProperty::ReferencesValidResources() { for (uint32_t i = 0; i < mReferences.Length(); i++) { if (!mReferences[i]->ReferencesValidResource()) return false; } return true; } bool nsSVGFilterProperty::IsInObserverLists() const { for (uint32_t i = 0; i < mReferences.Length(); i++) { if (!mReferences[i]->IsInObserverList()) return false; } return true; } void nsSVGFilterProperty::Invalidate() { for (uint32_t i = 0; i < mReferences.Length(); i++) { mReferences[i]->Invalidate(); } } NS_IMPL_ISUPPORTS_INHERITED1(nsSVGFilterReference, nsSVGIDRenderingObserver, nsISVGFilterReference); nsSVGFilterFrame * nsSVGFilterReference::GetFilterFrame() { return static_cast (GetReferencedFrame(nsGkAtoms::svgFilterFrame, nullptr)); } static void InvalidateAllContinuations(nsIFrame* aFrame) { for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { f->InvalidateFrame(); } } void nsSVGFilterReference::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->GetPresContext()->RestyleManager()->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. nsSVGEffects::InvalidateRenderingObservers(mFrame); // XXXSDL KILL THIS!!! nsSVGUtils::ScheduleReflowSVG(mFrame); } mFramePresShell->GetPresContext()->RestyleManager()->PostRestyleEvent( mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint); } bool nsSVGTextPathProperty::TargetIsValid() { Element* target = GetTarget(); return target && target->IsSVG(nsGkAtoms::path); } void nsSVGTextPathProperty::DoUpdate() { nsSVGIDRenderingObserver::DoUpdate(); if (!mFrame) return; NS_ASSERTION(mFrame->IsFrameOfType(nsIFrame::eSVG) || mFrame->IsSVGText(), "SVG frame expected"); // Avoid getting into an infinite loop of reflows if the is // pointing to one of its ancestors. TargetIsValid returns true iff // the target element is a element, and we would not have this // nsSVGTextPathProperty if this were a descendant of the // target . // // 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 , 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); mFramePresShell->GetPresContext()->RestyleManager()->PostRestyleEvent( mFrame->GetContent()->AsElement(), nsRestyleHint(0), changeHint); } void nsSVGPaintingProperty::DoUpdate() { nsSVGIDRenderingObserver::DoUpdate(); if (!mFrame) return; if (mFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) { nsSVGEffects::InvalidateRenderingObservers(mFrame); mFrame->InvalidateFrameSubtree(); } else { InvalidateAllContinuations(mFrame); } } 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(props.Get(aProperty)); if (prop) return prop; prop = aCreate(aURI, aFrame, false); if (!prop) return nullptr; NS_ADDREF(prop); props.Set(aProperty, static_cast(prop)); return prop; } static nsSVGFilterProperty* GetOrCreateFilterProperty(nsIFrame *aFrame) { const nsStyleSVGReset* style = aFrame->StyleSVGReset(); if (!style->HasFilters()) return nullptr; FrameProperties props = aFrame->Properties(); nsSVGFilterProperty *prop = static_cast(props.Get(nsSVGEffects::FilterProperty())); if (prop) return prop; prop = new nsSVGFilterProperty(style->mFilters, aFrame); if (!prop) return nullptr; NS_ADDREF(prop); props.Set(nsSVGEffects::FilterProperty(), static_cast(prop)); return prop; } nsSVGMarkerProperty * nsSVGEffects::GetMarkerProperty(nsIURI *aURI, nsIFrame *aFrame, const FramePropertyDescriptor *aProp) { NS_ABORT_IF_FALSE(aFrame->GetType() == nsGkAtoms::svgPathGeometryFrame && static_cast(aFrame->GetContent())->IsMarkable(), "Bad frame"); return static_cast( GetEffectProperty(aURI, aFrame, aProp, CreateMarkerProperty)); } nsSVGTextPathProperty * nsSVGEffects::GetTextPathProperty(nsIURI *aURI, nsIFrame *aFrame, const FramePropertyDescriptor *aProp) { return static_cast( GetEffectProperty(aURI, aFrame, aProp, CreateTextPathProperty)); } nsSVGPaintingProperty * nsSVGEffects::GetPaintingProperty(nsIURI *aURI, nsIFrame *aFrame, const FramePropertyDescriptor *aProp) { return static_cast( 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(props.Get(aProperty)); if (!hashtable) { hashtable = new nsSVGEffects::URIObserverHashtable(); props.Set(aProperty, hashtable); } nsSVGRenderingObserver* prop = static_cast(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( 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->StyleSVGReset(); result.mFilter = GetOrCreateFilterProperty(aFrame); 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(result); } nsSVGClipPathFrame * nsSVGEffects::EffectProperties::GetClipPathFrame(bool *aOK) { if (!mClipPath) return nullptr; nsSVGClipPathFrame *frame = static_cast (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 (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 GetOrCreateFilterProperty(aFrame); if (aFrame->GetType() == nsGkAtoms::svgPathGeometryFrame && static_cast(aFrame->GetContent())->IsMarkable()) { // Set marker properties here to avoid reference loops const nsStyleSVG *style = aFrame->StyleSVG(); 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->StyleSVGReset()->HasFilters()) return nullptr; return static_cast (aFrame->Properties().Get(FilterProperty())); } static PLDHashOperator GatherEnumerator(nsPtrHashKey* aEntry, void* aArg) { nsTArray* array = static_cast*>(aArg); array->AppendElement(aEntry->GetKey()); return PL_DHASH_REMOVE; } static PLDHashOperator GatherEnumeratorForReflow(nsPtrHashKey* aEntry, void* aArg) { if (!aEntry->GetKey()->ObservesReflow()) { return PL_DHASH_NEXT; } nsTArray* array = static_cast*>(aArg); array->AppendElement(aEntry->GetKey()); return PL_DHASH_REMOVE; } void nsSVGRenderingObserverList::InvalidateAll() { if (mObservers.Count() == 0) return; nsAutoTArray 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 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 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(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); } }