michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef NSSVGEFFECTS_H_ michael@0: #define NSSVGEFFECTS_H_ michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "FramePropertyTable.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsHashKeys.h" michael@0: #include "nsID.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsIMutationObserver.h" michael@0: #include "nsInterfaceHashtable.h" michael@0: #include "nsISupportsBase.h" michael@0: #include "nsISupportsImpl.h" michael@0: #include "nsReferencedElement.h" michael@0: #include "nsStubMutationObserver.h" michael@0: #include "nsSVGUtils.h" michael@0: #include "nsTHashtable.h" michael@0: #include "nsURIHashKey.h" michael@0: michael@0: class nsIAtom; michael@0: class nsIPresShell; michael@0: class nsIURI; michael@0: class nsSVGClipPathFrame; michael@0: class nsSVGPaintServerFrame; michael@0: class nsSVGFilterFrame; michael@0: class nsSVGMaskFrame; michael@0: michael@0: /* michael@0: * This interface allows us to be notified when a piece of SVG content is michael@0: * re-rendered. michael@0: * michael@0: * Concrete implementations of this interface need to implement michael@0: * "GetTarget()" to specify the piece of SVG content that they'd like to michael@0: * monitor, and they need to implement "DoUpdate" to specify how we'll react michael@0: * when that content gets re-rendered. They also need to implement a michael@0: * constructor and destructor, which should call StartListening and michael@0: * StopListening, respectively. michael@0: */ michael@0: class nsSVGRenderingObserver : public nsStubMutationObserver { michael@0: public: michael@0: typedef mozilla::dom::Element Element; michael@0: nsSVGRenderingObserver() michael@0: : mInObserverList(false) michael@0: {} michael@0: virtual ~nsSVGRenderingObserver() michael@0: {} michael@0: michael@0: // nsISupports michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: // nsIMutationObserver michael@0: NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED michael@0: NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED michael@0: NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED michael@0: NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED michael@0: michael@0: void InvalidateViaReferencedElement(); michael@0: michael@0: // When a nsSVGRenderingObserver list gets forcibly cleared, it uses this michael@0: // callback to notify every observer that's cleared from it, so they can michael@0: // react. michael@0: void NotifyEvictedFromRenderingObserverList(); michael@0: michael@0: bool IsInObserverList() const { return mInObserverList; } michael@0: michael@0: nsIFrame* GetReferencedFrame(); michael@0: /** michael@0: * @param aOK this is only for the convenience of callers. We set *aOK to false michael@0: * if the frame is the wrong type michael@0: */ michael@0: nsIFrame* GetReferencedFrame(nsIAtom* aFrameType, bool* aOK); michael@0: michael@0: Element* GetReferencedElement(); michael@0: michael@0: virtual bool ObservesReflow() { return true; } michael@0: michael@0: protected: michael@0: // Non-virtual protected methods michael@0: void StartListening(); michael@0: void StopListening(); michael@0: michael@0: // Virtual protected methods michael@0: virtual void DoUpdate() = 0; // called when the referenced resource changes. michael@0: michael@0: // This is an internally-used version of GetReferencedElement that doesn't michael@0: // forcibly add us as an observer. (whereas GetReferencedElement does) michael@0: virtual Element* GetTarget() = 0; michael@0: michael@0: // Whether we're in our referenced element's observer list at this time. michael@0: bool mInObserverList; michael@0: }; michael@0: michael@0: michael@0: /* michael@0: * SVG elements reference supporting resources by element ID. We need to michael@0: * track when those resources change and when the DOM changes in ways michael@0: * that affect which element is referenced by a given ID (e.g., when michael@0: * element IDs change). The code here is responsible for that. michael@0: * michael@0: * When a frame references a supporting resource, we create a property michael@0: * object derived from nsSVGIDRenderingObserver to manage the relationship. The michael@0: * property object is attached to the referencing frame. michael@0: */ michael@0: class nsSVGIDRenderingObserver : public nsSVGRenderingObserver { michael@0: public: michael@0: typedef mozilla::dom::Element Element; michael@0: nsSVGIDRenderingObserver(nsIURI* aURI, nsIFrame *aFrame, michael@0: bool aReferenceImage); michael@0: virtual ~nsSVGIDRenderingObserver(); michael@0: michael@0: protected: michael@0: Element* GetTarget() MOZ_OVERRIDE { return mElement.get(); } michael@0: michael@0: // This is called when the referenced resource changes. michael@0: virtual void DoUpdate() MOZ_OVERRIDE; michael@0: michael@0: class SourceReference : public nsReferencedElement { michael@0: public: michael@0: SourceReference(nsSVGIDRenderingObserver* aContainer) : mContainer(aContainer) {} michael@0: protected: michael@0: virtual void ElementChanged(Element* aFrom, Element* aTo) MOZ_OVERRIDE { michael@0: mContainer->StopListening(); michael@0: nsReferencedElement::ElementChanged(aFrom, aTo); michael@0: mContainer->StartListening(); michael@0: mContainer->DoUpdate(); michael@0: } michael@0: /** michael@0: * Override IsPersistent because we want to keep tracking the element michael@0: * for the ID even when it changes. michael@0: */ michael@0: virtual bool IsPersistent() MOZ_OVERRIDE { return true; } michael@0: private: michael@0: nsSVGIDRenderingObserver* mContainer; michael@0: }; michael@0: michael@0: SourceReference mElement; michael@0: // The frame that this property is attached to michael@0: nsIFrame *mFrame; michael@0: // When a presshell is torn down, we don't delete the properties for michael@0: // each frame until after the frames are destroyed. So here we remember michael@0: // the presshell for the frames we care about and, before we use the frame, michael@0: // we test the presshell to see if it's destroying itself. If it is, michael@0: // then the frame pointer is not valid and we know the frame has gone away. michael@0: nsIPresShell *mFramePresShell; michael@0: }; michael@0: michael@0: /** michael@0: * In a filter chain, there can be multiple SVG reference filters. michael@0: * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2); michael@0: * michael@0: * This class keeps track of one SVG reference filter in a filter chain. michael@0: * e.g. url(#svg-filter-1) michael@0: * michael@0: * It fires invalidations when the SVG filter element's id changes or when michael@0: * the SVG filter element's content changes. michael@0: * michael@0: * The nsSVGFilterProperty class manages a list of nsSVGFilterReferences. michael@0: */ michael@0: class nsSVGFilterReference : michael@0: public nsSVGIDRenderingObserver, public nsISVGFilterReference { michael@0: public: michael@0: nsSVGFilterReference(nsIURI *aURI, nsIFrame *aFilteredFrame) michael@0: : nsSVGIDRenderingObserver(aURI, aFilteredFrame, false) {} michael@0: michael@0: bool ReferencesValidResource() { return GetFilterFrame(); } michael@0: michael@0: /** michael@0: * @return the filter frame, or null if there is no filter frame michael@0: */ michael@0: nsSVGFilterFrame *GetFilterFrame(); michael@0: michael@0: // nsISupports michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: // nsISVGFilterReference michael@0: virtual void Invalidate() MOZ_OVERRIDE { DoUpdate(); }; michael@0: michael@0: private: michael@0: // nsSVGIDRenderingObserver michael@0: virtual void DoUpdate() MOZ_OVERRIDE; michael@0: }; michael@0: michael@0: /** michael@0: * This class manages a list of nsSVGFilterReferences, which represent SVG michael@0: * reference filters in a filter chain. michael@0: * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2); michael@0: * michael@0: * In the above example, the nsSVGFilterProperty will manage two michael@0: * nsSVGFilterReferences, one for each SVG reference filter. CSS filters like michael@0: * "blur(10px)" don't reference filter elements, so they don't need an michael@0: * nsSVGFilterReference. The style system invalidates changes to CSS filters. michael@0: */ michael@0: class nsSVGFilterProperty : public nsISupports { michael@0: public: michael@0: nsSVGFilterProperty(const nsTArray &aFilters, michael@0: nsIFrame *aFilteredFrame); michael@0: virtual ~nsSVGFilterProperty(); michael@0: michael@0: const nsTArray& GetFilters() { return mFilters; } michael@0: bool ReferencesValidResources(); michael@0: bool IsInObserverLists() const; michael@0: void Invalidate(); michael@0: michael@0: // nsISupports michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: private: michael@0: nsTArray mReferences; michael@0: nsTArray mFilters; michael@0: }; michael@0: michael@0: class nsSVGMarkerProperty : public nsSVGIDRenderingObserver { michael@0: public: michael@0: nsSVGMarkerProperty(nsIURI *aURI, nsIFrame *aFrame, bool aReferenceImage) michael@0: : nsSVGIDRenderingObserver(aURI, aFrame, aReferenceImage) {} michael@0: michael@0: protected: michael@0: virtual void DoUpdate() MOZ_OVERRIDE; michael@0: }; michael@0: michael@0: class nsSVGTextPathProperty : public nsSVGIDRenderingObserver { michael@0: public: michael@0: nsSVGTextPathProperty(nsIURI *aURI, nsIFrame *aFrame, bool aReferenceImage) michael@0: : nsSVGIDRenderingObserver(aURI, aFrame, aReferenceImage) michael@0: , mValid(true) {} michael@0: michael@0: virtual bool ObservesReflow() MOZ_OVERRIDE { return false; } michael@0: michael@0: protected: michael@0: virtual void DoUpdate() MOZ_OVERRIDE; michael@0: michael@0: private: michael@0: /** michael@0: * Returns true if the target of the textPath is the frame of a 'path' element. michael@0: */ michael@0: bool TargetIsValid(); michael@0: michael@0: bool mValid; michael@0: }; michael@0: michael@0: class nsSVGPaintingProperty : public nsSVGIDRenderingObserver { michael@0: public: michael@0: nsSVGPaintingProperty(nsIURI *aURI, nsIFrame *aFrame, bool aReferenceImage) michael@0: : nsSVGIDRenderingObserver(aURI, aFrame, aReferenceImage) {} michael@0: michael@0: protected: michael@0: virtual void DoUpdate() MOZ_OVERRIDE; michael@0: }; michael@0: michael@0: /** michael@0: * A manager for one-shot nsSVGRenderingObserver tracking. michael@0: * nsSVGRenderingObservers can be added or removed. They are not strongly michael@0: * referenced so an observer must be removed before it dies. michael@0: * When InvalidateAll is called, all outstanding references get michael@0: * InvalidateViaReferencedElement() michael@0: * called on them and the list is cleared. The intent is that michael@0: * the observer will force repainting of whatever part of the document michael@0: * is needed, and then at paint time the observer will do a clean lookup michael@0: * of the referenced element and [re-]add itself to the element's observer list. michael@0: * michael@0: * InvalidateAll must be called before this object is destroyed, i.e. michael@0: * before the referenced frame is destroyed. This should normally happen michael@0: * via nsSVGContainerFrame::RemoveFrame, since only frames in the frame michael@0: * tree should be referenced. michael@0: */ michael@0: class nsSVGRenderingObserverList { michael@0: public: michael@0: nsSVGRenderingObserverList() michael@0: : mObservers(5) michael@0: { michael@0: MOZ_COUNT_CTOR(nsSVGRenderingObserverList); michael@0: } michael@0: michael@0: ~nsSVGRenderingObserverList() { michael@0: InvalidateAll(); michael@0: MOZ_COUNT_DTOR(nsSVGRenderingObserverList); michael@0: } michael@0: michael@0: void Add(nsSVGRenderingObserver* aObserver) michael@0: { mObservers.PutEntry(aObserver); } michael@0: void Remove(nsSVGRenderingObserver* aObserver) michael@0: { mObservers.RemoveEntry(aObserver); } michael@0: #ifdef DEBUG michael@0: bool Contains(nsSVGRenderingObserver* aObserver) michael@0: { return (mObservers.GetEntry(aObserver) != nullptr); } michael@0: #endif michael@0: bool IsEmpty() michael@0: { return mObservers.Count() == 0; } michael@0: michael@0: /** michael@0: * Drop all our observers, and notify them that we have changed and dropped michael@0: * our reference to them. michael@0: */ michael@0: void InvalidateAll(); michael@0: michael@0: /** michael@0: * Drop all observers that observe reflow, and notify them that we have changed and dropped michael@0: * our reference to them. michael@0: */ michael@0: void InvalidateAllForReflow(); michael@0: michael@0: /** michael@0: * Drop all our observers, and notify them that we have dropped our reference michael@0: * to them. michael@0: */ michael@0: void RemoveAll(); michael@0: michael@0: private: michael@0: nsTHashtable > mObservers; michael@0: }; michael@0: michael@0: class nsSVGEffects { michael@0: public: michael@0: typedef mozilla::dom::Element Element; michael@0: typedef mozilla::FramePropertyDescriptor FramePropertyDescriptor; michael@0: typedef nsInterfaceHashtable michael@0: URIObserverHashtable; michael@0: michael@0: static void DestroySupports(void* aPropertyValue) michael@0: { michael@0: (static_cast(aPropertyValue))->Release(); michael@0: } michael@0: michael@0: static void DestroyHashtable(void* aPropertyValue) michael@0: { michael@0: delete static_cast (aPropertyValue); michael@0: } michael@0: michael@0: NS_DECLARE_FRAME_PROPERTY(FilterProperty, DestroySupports) michael@0: NS_DECLARE_FRAME_PROPERTY(MaskProperty, DestroySupports) michael@0: NS_DECLARE_FRAME_PROPERTY(ClipPathProperty, DestroySupports) michael@0: NS_DECLARE_FRAME_PROPERTY(MarkerBeginProperty, DestroySupports) michael@0: NS_DECLARE_FRAME_PROPERTY(MarkerMiddleProperty, DestroySupports) michael@0: NS_DECLARE_FRAME_PROPERTY(MarkerEndProperty, DestroySupports) michael@0: NS_DECLARE_FRAME_PROPERTY(FillProperty, DestroySupports) michael@0: NS_DECLARE_FRAME_PROPERTY(StrokeProperty, DestroySupports) michael@0: NS_DECLARE_FRAME_PROPERTY(HrefProperty, DestroySupports) michael@0: NS_DECLARE_FRAME_PROPERTY(BackgroundImageProperty, DestroyHashtable) michael@0: michael@0: /** michael@0: * Get the paint server for a aTargetFrame. michael@0: */ michael@0: static nsSVGPaintServerFrame *GetPaintServer(nsIFrame *aTargetFrame, michael@0: const nsStyleSVGPaint *aPaint, michael@0: const FramePropertyDescriptor *aProperty); michael@0: michael@0: struct EffectProperties { michael@0: nsSVGFilterProperty* mFilter; michael@0: nsSVGPaintingProperty* mMask; michael@0: nsSVGPaintingProperty* mClipPath; michael@0: michael@0: /** michael@0: * @return the clip-path frame, or null if there is no clip-path frame michael@0: * @param aOK if a clip-path was specified and the designated element michael@0: * exists but is an element of the wrong type, *aOK is set to false. michael@0: * Otherwise *aOK is untouched. michael@0: */ michael@0: nsSVGClipPathFrame *GetClipPathFrame(bool *aOK); michael@0: /** michael@0: * @return the mask frame, or null if there is no mask frame michael@0: * @param aOK if a mask was specified and the designated element michael@0: * exists but is an element of the wrong type, *aOK is set to false. michael@0: * Otherwise *aOK is untouched. michael@0: */ michael@0: nsSVGMaskFrame *GetMaskFrame(bool *aOK); michael@0: michael@0: bool HasValidFilter() { michael@0: return mFilter && mFilter->ReferencesValidResources(); michael@0: } michael@0: michael@0: bool HasNoFilterOrHasValidFilter() { michael@0: return !mFilter || mFilter->ReferencesValidResources(); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * @param aFrame should be the first continuation michael@0: */ michael@0: static EffectProperties GetEffectProperties(nsIFrame *aFrame); michael@0: michael@0: /** michael@0: * Called when changes to an element (e.g. CSS property changes) cause its michael@0: * frame to start/stop referencing (or reference different) SVG resource michael@0: * elements. (_Not_ called for changes to referenced resource elements.) michael@0: * michael@0: * This function handles such changes by discarding _all_ the frame's SVG michael@0: * effects frame properties (causing those properties to stop watching their michael@0: * target element). It also synchronously (re)creates the filter and marker michael@0: * frame properties (XXX why not the other properties?), which makes it michael@0: * useful for initializing those properties during first reflow. michael@0: * michael@0: * XXX rename to something more meaningful like RefreshResourceReferences? michael@0: */ michael@0: static void UpdateEffects(nsIFrame *aFrame); michael@0: michael@0: /** michael@0: * @param aFrame should be the first continuation michael@0: */ michael@0: static nsSVGFilterProperty *GetFilterProperty(nsIFrame *aFrame); michael@0: michael@0: /** michael@0: * @param aFrame must be a first-continuation. michael@0: */ michael@0: static void AddRenderingObserver(Element *aElement, nsSVGRenderingObserver *aObserver); michael@0: /** michael@0: * @param aFrame must be a first-continuation. michael@0: */ michael@0: static void RemoveRenderingObserver(Element *aElement, nsSVGRenderingObserver *aObserver); michael@0: michael@0: /** michael@0: * Removes all rendering observers from aElement. michael@0: */ michael@0: static void RemoveAllRenderingObservers(Element *aElement); michael@0: michael@0: /** michael@0: * This can be called on any frame. We invalidate the observers of aFrame's michael@0: * element, if any, or else walk up to the nearest observable SVG parent michael@0: * frame with observers and invalidate them instead. michael@0: * michael@0: * Note that this method is very different to e.g. michael@0: * nsNodeUtils::AttributeChanged which walks up the content node tree all the michael@0: * way to the root node (not stopping if it encounters a non-container SVG michael@0: * node) invalidating all mutation observers (not just michael@0: * nsSVGRenderingObservers) on all nodes along the way (not just the first michael@0: * node it finds with observers). In other words, by doing all the michael@0: * things in parentheses in the preceding sentence, this method uses michael@0: * knowledge about our implementation and what can be affected by SVG effects michael@0: * to make invalidation relatively lightweight when an SVG effect changes. michael@0: */ michael@0: static void InvalidateRenderingObservers(nsIFrame *aFrame); michael@0: michael@0: enum { michael@0: INVALIDATE_REFLOW = 1 michael@0: }; michael@0: michael@0: /** michael@0: * This can be called on any element or frame. Only direct observers of this michael@0: * (frame's) element, if any, are invalidated. michael@0: */ michael@0: static void InvalidateDirectRenderingObservers(Element *aElement, uint32_t aFlags = 0); michael@0: static void InvalidateDirectRenderingObservers(nsIFrame *aFrame, uint32_t aFlags = 0); michael@0: michael@0: /** michael@0: * Get an nsSVGMarkerProperty for the frame, creating a fresh one if necessary michael@0: */ michael@0: static nsSVGMarkerProperty * michael@0: GetMarkerProperty(nsIURI *aURI, nsIFrame *aFrame, michael@0: const FramePropertyDescriptor *aProperty); michael@0: /** michael@0: * Get an nsSVGTextPathProperty for the frame, creating a fresh one if necessary michael@0: */ michael@0: static nsSVGTextPathProperty * michael@0: GetTextPathProperty(nsIURI *aURI, nsIFrame *aFrame, michael@0: const FramePropertyDescriptor *aProperty); michael@0: /** michael@0: * Get an nsSVGPaintingProperty for the frame, creating a fresh one if necessary michael@0: */ michael@0: static nsSVGPaintingProperty * michael@0: GetPaintingProperty(nsIURI *aURI, nsIFrame *aFrame, michael@0: const FramePropertyDescriptor *aProperty); michael@0: /** michael@0: * Get an nsSVGPaintingProperty for the frame for that URI, creating a fresh michael@0: * one if necessary michael@0: */ michael@0: static nsSVGPaintingProperty * michael@0: GetPaintingPropertyForURI(nsIURI *aURI, nsIFrame *aFrame, michael@0: const FramePropertyDescriptor *aProp); michael@0: }; michael@0: michael@0: #endif /*NSSVGEFFECTS_H_*/