/* -*- 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/. */ #ifndef NSSVGEFFECTS_H_ #define NSSVGEFFECTS_H_ #include "mozilla/Attributes.h" #include "FramePropertyTable.h" #include "mozilla/dom/Element.h" #include "nsHashKeys.h" #include "nsID.h" #include "nsIFrame.h" #include "nsIMutationObserver.h" #include "nsInterfaceHashtable.h" #include "nsISupportsBase.h" #include "nsISupportsImpl.h" #include "nsReferencedElement.h" #include "nsStubMutationObserver.h" #include "nsSVGUtils.h" #include "nsTHashtable.h" #include "nsURIHashKey.h" #include "nsCycleCollectionParticipant.h" class nsIAtom; class nsIPresShell; class nsIURI; class nsSVGClipPathFrame; class nsSVGPaintServerFrame; class nsSVGFilterFrame; class nsSVGMaskFrame; class nsSVGFilterChainObserver; /* * This interface allows us to be notified when a piece of SVG content is * re-rendered. * * Concrete implementations of this interface need to implement * "GetTarget()" to specify the piece of SVG content that they'd like to * monitor, and they need to implement "DoUpdate" to specify how we'll react * when that content gets re-rendered. They also need to implement a * constructor and destructor, which should call StartListening and * StopListening, respectively. */ class nsSVGRenderingObserver : public nsStubMutationObserver { protected: virtual ~nsSVGRenderingObserver() {} public: typedef mozilla::dom::Element Element; nsSVGRenderingObserver() : mInObserverList(false) {} // nsIMutationObserver NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED void InvalidateViaReferencedElement(); // When a nsSVGRenderingObserver list gets forcibly cleared, it uses this // callback to notify every observer that's cleared from it, so they can // react. void NotifyEvictedFromRenderingObserverList(); bool IsInObserverList() const { return mInObserverList; } nsIFrame* GetReferencedFrame(); /** * @param aOK this is only for the convenience of callers. We set *aOK to false * if the frame is the wrong type */ nsIFrame* GetReferencedFrame(nsIAtom* aFrameType, bool* aOK); Element* GetReferencedElement(); virtual bool ObservesReflow() { return true; } protected: // Non-virtual protected methods void StartListening(); void StopListening(); // Virtual protected methods virtual void DoUpdate() = 0; // called when the referenced resource changes. // This is an internally-used version of GetReferencedElement that doesn't // forcibly add us as an observer. (whereas GetReferencedElement does) virtual Element* GetTarget() = 0; // Whether we're in our referenced element's observer list at this time. bool mInObserverList; }; /* * SVG elements reference supporting resources by element ID. We need to * track when those resources change and when the DOM changes in ways * that affect which element is referenced by a given ID (e.g., when * element IDs change). The code here is responsible for that. * * When a frame references a supporting resource, we create a property * object derived from nsSVGIDRenderingObserver to manage the relationship. The * property object is attached to the referencing frame. */ class nsSVGIDRenderingObserver : public nsSVGRenderingObserver { public: typedef mozilla::dom::Element Element; nsSVGIDRenderingObserver(nsIURI* aURI, nsIContent* aObservingContent, bool aReferenceImage); virtual ~nsSVGIDRenderingObserver(); protected: Element* GetTarget() override { return mElement.get(); } // This is called when the referenced resource changes. virtual void DoUpdate() override; class SourceReference : public nsReferencedElement { public: explicit SourceReference(nsSVGIDRenderingObserver* aContainer) : mContainer(aContainer) {} protected: virtual void ElementChanged(Element* aFrom, Element* aTo) override { mContainer->StopListening(); nsReferencedElement::ElementChanged(aFrom, aTo); mContainer->StartListening(); mContainer->DoUpdate(); } /** * Override IsPersistent because we want to keep tracking the element * for the ID even when it changes. */ virtual bool IsPersistent() override { return true; } private: nsSVGIDRenderingObserver* mContainer; }; SourceReference mElement; }; struct nsSVGFrameReferenceFromProperty { explicit nsSVGFrameReferenceFromProperty(nsIFrame* aFrame) : mFrame(aFrame) , mFramePresShell(aFrame->PresContext()->PresShell()) {} // Clear our reference to the frame. void Detach(); // null if the frame has become invalid nsIFrame* Get(); private: // The frame that this property is attached to, may be null nsIFrame *mFrame; // When a presshell is torn down, we don't delete the properties for // each frame until after the frames are destroyed. So here we remember // the presshell for the frames we care about and, before we use the frame, // we test the presshell to see if it's destroying itself. If it is, // then the frame pointer is not valid and we know the frame has gone away. // mFramePresShell may be null, but when mFrame is non-null, mFramePresShell // is guaranteed to be non-null, too. nsIPresShell *mFramePresShell; }; class nsSVGRenderingObserverProperty : public nsSVGIDRenderingObserver { public: NS_DECL_ISUPPORTS nsSVGRenderingObserverProperty(nsIURI* aURI, nsIFrame *aFrame, bool aReferenceImage) : nsSVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage) , mFrameReference(aFrame) {} protected: virtual ~nsSVGRenderingObserverProperty() {} virtual void DoUpdate() override; nsSVGFrameReferenceFromProperty mFrameReference; }; /** * In a filter chain, there can be multiple SVG reference filters. * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2); * * This class keeps track of one SVG reference filter in a filter chain. * e.g. url(#svg-filter-1) * * It fires invalidations when the SVG filter element's id changes or when * the SVG filter element's content changes. * * The nsSVGFilterChainObserver class manages a list of nsSVGFilterReferences. */ class nsSVGFilterReference final : public nsSVGIDRenderingObserver , public nsISVGFilterReference { public: nsSVGFilterReference(nsIURI* aURI, nsIContent* aObservingContent, nsSVGFilterChainObserver* aFilterChainObserver) : nsSVGIDRenderingObserver(aURI, aObservingContent, false) , mFilterChainObserver(aFilterChainObserver) { } bool ReferencesValidResource() { return GetFilterFrame(); } void DetachFromChainObserver() { mFilterChainObserver = nullptr; } /** * @return the filter frame, or null if there is no filter frame */ nsSVGFilterFrame *GetFilterFrame(); // nsISupports NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsSVGFilterReference, nsSVGIDRenderingObserver) // nsISVGFilterReference virtual void Invalidate() override { DoUpdate(); }; protected: virtual ~nsSVGFilterReference() {} // nsSVGIDRenderingObserver virtual void DoUpdate() override; private: nsSVGFilterChainObserver* mFilterChainObserver; }; /** * This class manages a list of nsSVGFilterReferences, which represent SVG * reference filters in a filter chain. * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2); * * In the above example, the nsSVGFilterChainObserver will manage two * nsSVGFilterReferences, one for each SVG reference filter. CSS filters like * "blur(10px)" don't reference filter elements, so they don't need an * nsSVGFilterReference. The style system invalidates changes to CSS filters. */ class nsSVGFilterChainObserver : public nsISupports { public: nsSVGFilterChainObserver(const nsTArray<nsStyleFilter>& aFilters, nsIContent* aFilteredElement, nsIFrame* aFiltedFrame = nullptr); bool ReferencesValidResources(); bool IsInObserverLists() const; void Invalidate() { DoUpdate(); } // nsISupports NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(nsSVGFilterChainObserver) protected: virtual ~nsSVGFilterChainObserver(); virtual void DoUpdate() = 0; private: void DetachReferences() { for (uint32_t i = 0; i < mReferences.Length(); i++) { mReferences[i]->DetachFromChainObserver(); } } nsTArray<RefPtr<nsSVGFilterReference>> mReferences; }; class nsSVGFilterProperty : public nsSVGFilterChainObserver { public: nsSVGFilterProperty(const nsTArray<nsStyleFilter>& aFilters, nsIFrame* aFilteredFrame) : nsSVGFilterChainObserver(aFilters, aFilteredFrame->GetContent(), aFilteredFrame) , mFrameReference(aFilteredFrame) {} void DetachFromFrame() { mFrameReference.Detach(); } protected: virtual void DoUpdate() override; nsSVGFrameReferenceFromProperty mFrameReference; }; class nsSVGMarkerProperty final: public nsSVGRenderingObserverProperty { public: nsSVGMarkerProperty(nsIURI* aURI, nsIFrame* aFrame, bool aReferenceImage) : nsSVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {} protected: virtual void DoUpdate() override; }; class nsSVGTextPathProperty final : public nsSVGRenderingObserverProperty { public: nsSVGTextPathProperty(nsIURI* aURI, nsIFrame* aFrame, bool aReferenceImage) : nsSVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) , mValid(true) {} virtual bool ObservesReflow() override { return false; } protected: virtual void DoUpdate() override; private: /** * Returns true if the target of the textPath is the frame of a 'path' element. */ bool TargetIsValid(); bool mValid; }; class nsSVGPaintingProperty final : public nsSVGRenderingObserverProperty { public: nsSVGPaintingProperty(nsIURI* aURI, nsIFrame* aFrame, bool aReferenceImage) : nsSVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {} protected: virtual void DoUpdate() override; }; class nsSVGMaskProperty final : public nsISupports { public: explicit nsSVGMaskProperty(nsIFrame* aFrame); // nsISupports NS_DECL_ISUPPORTS const nsTArray<RefPtr<nsSVGPaintingProperty>>& GetProps() const { return mProperties; } private: virtual ~nsSVGMaskProperty() {} nsTArray<RefPtr<nsSVGPaintingProperty>> mProperties; }; /** * A manager for one-shot nsSVGRenderingObserver tracking. * nsSVGRenderingObservers can be added or removed. They are not strongly * referenced so an observer must be removed before it dies. * When InvalidateAll is called, all outstanding references get * InvalidateViaReferencedElement() * called on them and the list is cleared. The intent is that * the observer will force repainting of whatever part of the document * is needed, and then at paint time the observer will do a clean lookup * of the referenced element and [re-]add itself to the element's observer list. * * InvalidateAll must be called before this object is destroyed, i.e. * before the referenced frame is destroyed. This should normally happen * via nsSVGContainerFrame::RemoveFrame, since only frames in the frame * tree should be referenced. */ class nsSVGRenderingObserverList { public: nsSVGRenderingObserverList() : mObservers(4) { MOZ_COUNT_CTOR(nsSVGRenderingObserverList); } ~nsSVGRenderingObserverList() { InvalidateAll(); MOZ_COUNT_DTOR(nsSVGRenderingObserverList); } void Add(nsSVGRenderingObserver* aObserver) { mObservers.PutEntry(aObserver); } void Remove(nsSVGRenderingObserver* aObserver) { mObservers.RemoveEntry(aObserver); } #ifdef DEBUG bool Contains(nsSVGRenderingObserver* aObserver) { return (mObservers.GetEntry(aObserver) != nullptr); } #endif bool IsEmpty() { return mObservers.Count() == 0; } /** * Drop all our observers, and notify them that we have changed and dropped * our reference to them. */ void InvalidateAll(); /** * Drop all observers that observe reflow, and notify them that we have changed and dropped * our reference to them. */ void InvalidateAllForReflow(); /** * Drop all our observers, and notify them that we have dropped our reference * to them. */ void RemoveAll(); private: nsTHashtable<nsPtrHashKey<nsSVGRenderingObserver> > mObservers; }; class nsSVGEffects { public: typedef mozilla::dom::Element Element; typedef nsInterfaceHashtable<nsURIHashKey, nsIMutationObserver> URIObserverHashtable; using PaintingPropertyDescriptor = const mozilla::FramePropertyDescriptor<nsSVGPaintingProperty>*; using URIObserverHashtablePropertyDescriptor = const mozilla::FramePropertyDescriptor<URIObserverHashtable>*; static void DestroyFilterProperty(nsSVGFilterProperty* aProp) { // nsSVGFilterProperty is cycle-collected, so dropping the last reference // doesn't necessarily destroy it. We need to tell it that the frame // has now become invalid. aProp->DetachFromFrame(); aProp->Release(); } NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(FilterProperty, nsSVGFilterProperty, DestroyFilterProperty) NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MaskProperty, nsSVGMaskProperty) NS_DECLARE_FRAME_PROPERTY_RELEASABLE(ClipPathProperty, nsSVGPaintingProperty) NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerBeginProperty, nsSVGMarkerProperty) NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerMiddleProperty, nsSVGMarkerProperty) NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerEndProperty, nsSVGMarkerProperty) NS_DECLARE_FRAME_PROPERTY_RELEASABLE(FillProperty, nsSVGPaintingProperty) NS_DECLARE_FRAME_PROPERTY_RELEASABLE(StrokeProperty, nsSVGPaintingProperty) NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefAsTextPathProperty, nsSVGTextPathProperty) NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefAsPaintingProperty, nsSVGPaintingProperty) NS_DECLARE_FRAME_PROPERTY_DELETABLE(BackgroundImageProperty, URIObserverHashtable) /** * Get the paint server for a aTargetFrame. */ static nsSVGPaintServerFrame *GetPaintServer(nsIFrame* aTargetFrame, nsStyleSVGPaint nsStyleSVG::* aPaint, PaintingPropertyDescriptor aProperty); struct EffectProperties { nsSVGFilterProperty* mFilter; nsSVGMaskProperty* mMask; nsSVGPaintingProperty* mClipPath; /** * @return the clip-path frame, or null if there is no clip-path frame * @param aOK if a clip-path was specified and the designated element * exists but is an element of the wrong type, *aOK is set to false. * Otherwise *aOK is untouched. */ nsSVGClipPathFrame *GetClipPathFrame(bool* aOK); /** * @return the first mask frame, or null if there is no mask frame * @param aOK if a mask was specified and the designated element * exists but is an element of the wrong type, *aOK is set to false. * Otherwise *aOK is untouched. */ nsSVGMaskFrame *GetFirstMaskFrame(bool* aOK = nullptr); /** * @return an array which contains all SVG mask frames. */ nsTArray<nsSVGMaskFrame*> GetMaskFrames(); bool HasValidFilter() { return mFilter && mFilter->ReferencesValidResources(); } bool HasNoFilterOrHasValidFilter() { return !mFilter || mFilter->ReferencesValidResources(); } }; /** * @param aFrame should be the first continuation */ static EffectProperties GetEffectProperties(nsIFrame* aFrame); /** * Called when changes to an element (e.g. CSS property changes) cause its * frame to start/stop referencing (or reference different) SVG resource * elements. (_Not_ called for changes to referenced resource elements.) * * This function handles such changes by discarding _all_ the frame's SVG * effects frame properties (causing those properties to stop watching their * target element). It also synchronously (re)creates the filter and marker * frame properties (XXX why not the other properties?), which makes it * useful for initializing those properties during first reflow. * * XXX rename to something more meaningful like RefreshResourceReferences? */ static void UpdateEffects(nsIFrame* aFrame); /** * @param aFrame should be the first continuation */ static nsSVGFilterProperty *GetFilterProperty(nsIFrame* aFrame); /** * @param aFrame must be a first-continuation. */ static void AddRenderingObserver(Element* aElement, nsSVGRenderingObserver *aObserver); /** * @param aFrame must be a first-continuation. */ static void RemoveRenderingObserver(Element* aElement, nsSVGRenderingObserver *aObserver); /** * Removes all rendering observers from aElement. */ static void RemoveAllRenderingObservers(Element* aElement); /** * This can be called on any frame. We invalidate the observers of aFrame's * element, if any, or else walk up to the nearest observable SVG parent * frame with observers and invalidate them instead. * * Note that this method is very different to e.g. * nsNodeUtils::AttributeChanged which walks up the content node tree all the * way to the root node (not stopping if it encounters a non-container SVG * node) invalidating all mutation observers (not just * nsSVGRenderingObservers) on all nodes along the way (not just the first * node it finds with observers). In other words, by doing all the * things in parentheses in the preceding sentence, this method uses * knowledge about our implementation and what can be affected by SVG effects * to make invalidation relatively lightweight when an SVG effect changes. */ static void InvalidateRenderingObservers(nsIFrame* aFrame); enum { INVALIDATE_REFLOW = 1 }; /** * This can be called on any element or frame. Only direct observers of this * (frame's) element, if any, are invalidated. */ static void InvalidateDirectRenderingObservers(Element* aElement, uint32_t aFlags = 0); static void InvalidateDirectRenderingObservers(nsIFrame* aFrame, uint32_t aFlags = 0); /** * Get an nsSVGMarkerProperty for the frame, creating a fresh one if necessary */ static nsSVGMarkerProperty * GetMarkerProperty(nsIURI* aURI, nsIFrame* aFrame, const mozilla::FramePropertyDescriptor<nsSVGMarkerProperty>* aProperty); /** * Get an nsSVGTextPathProperty for the frame, creating a fresh one if necessary */ static nsSVGTextPathProperty * GetTextPathProperty(nsIURI* aURI, nsIFrame* aFrame, const mozilla::FramePropertyDescriptor<nsSVGTextPathProperty>* aProperty); /** * Get an nsSVGPaintingProperty for the frame, creating a fresh one if necessary */ static nsSVGPaintingProperty* GetPaintingProperty(nsIURI* aURI, nsIFrame* aFrame, const mozilla::FramePropertyDescriptor<nsSVGPaintingProperty>* aProperty); /** * Get an nsSVGPaintingProperty for the frame for that URI, creating a fresh * one if necessary */ static nsSVGPaintingProperty* GetPaintingPropertyForURI(nsIURI* aURI, nsIFrame* aFrame, URIObserverHashtablePropertyDescriptor aProp); /** * A helper function to resolve marker's URL. */ static already_AddRefed<nsIURI> GetMarkerURI(nsIFrame* aFrame, RefPtr<mozilla::css::URLValue> nsStyleSVG::* aMarker); /** * A helper function to resolve clip-path URL. */ static already_AddRefed<nsIURI> GetClipPathURI(nsIFrame* aFrame); /** * A helper function to resolve filter URL. */ static already_AddRefed<nsIURI> GetFilterURI(nsIFrame* aFrame, uint32_t aIndex); /** * A helper function to resolve filter URL. */ static already_AddRefed<nsIURI> GetFilterURI(nsIFrame* aFrame, const nsStyleFilter& aFilter); /** * A helper function to resolve paint-server URL. */ static already_AddRefed<nsIURI> GetPaintURI(nsIFrame* aFrame, nsStyleSVGPaint nsStyleSVG::* aPaint); /** * A helper function to resolve SVG mask URL. */ static already_AddRefed<nsIURI> GetMaskURI(nsIFrame* aFrame, uint32_t aIndex); /** * Return a baseURL for resolving a local-ref URL. * * @param aContent an element which uses a local-ref property. Here are some * examples: * <rect fill=url(#foo)> * <circle clip-path=url(#foo)> * <use xlink:href="#foo"> */ static already_AddRefed<nsIURI> GetBaseURLForLocalRef(nsIContent* aContent, nsIURI* aDocURI); }; #endif /*NSSVGEFFECTS_H_*/