forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1876 lines
		
	
	
	
		
			64 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1876 lines
		
	
	
	
		
			64 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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/css/ImageLoader.h"
 | |
| #include "mozilla/dom/CanvasRenderingContext2D.h"
 | |
| #include "mozilla/dom/ReferrerInfo.h"
 | |
| #include "mozilla/dom/SVGGeometryElement.h"
 | |
| #include "mozilla/dom/SVGMPathElement.h"
 | |
| #include "mozilla/dom/SVGTextPathElement.h"
 | |
| #include "mozilla/dom/SVGUseElement.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/RestyleManager.h"
 | |
| #include "mozilla/SVGClipPathFrame.h"
 | |
| #include "mozilla/SVGGeometryFrame.h"
 | |
| #include "mozilla/SVGMaskFrame.h"
 | |
| #include "mozilla/SVGTextFrame.h"
 | |
| #include "mozilla/SVGUtils.h"
 | |
| #include "nsCSSFrameConstructor.h"
 | |
| #include "nsCycleCollectionParticipant.h"
 | |
| #include "nsHashKeys.h"
 | |
| #include "nsIContent.h"
 | |
| #include "nsIContentInlines.h"
 | |
| #include "nsInterfaceHashtable.h"
 | |
| #include "nsIReflowCallback.h"
 | |
| #include "nsISupportsImpl.h"
 | |
| #include "nsLayoutUtils.h"
 | |
| #include "nsNetUtil.h"
 | |
| #include "nsTHashtable.h"
 | |
| #include "nsURIHashKey.h"
 | |
| #include "SVGFilterFrame.h"
 | |
| #include "SVGMarkerFrame.h"
 | |
| #include "SVGPaintServerFrame.h"
 | |
| 
 | |
| using namespace mozilla::dom;
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| bool URLAndReferrerInfo::operator==(const URLAndReferrerInfo& aRHS) const {
 | |
|   bool uriEqual = false, referrerEqual = false;
 | |
|   this->mURI->Equals(aRHS.mURI, &uriEqual);
 | |
|   this->mReferrerInfo->Equals(aRHS.mReferrerInfo, &referrerEqual);
 | |
| 
 | |
|   return uriEqual && referrerEqual;
 | |
| }
 | |
| 
 | |
| class URLAndReferrerInfoHashKey : public PLDHashEntryHdr {
 | |
|  public:
 | |
|   using KeyType = const URLAndReferrerInfo*;
 | |
|   using KeyTypePointer = const URLAndReferrerInfo*;
 | |
| 
 | |
|   explicit URLAndReferrerInfoHashKey(const URLAndReferrerInfo* aKey) noexcept
 | |
|       : mKey(aKey) {
 | |
|     MOZ_COUNT_CTOR(URLAndReferrerInfoHashKey);
 | |
|   }
 | |
|   URLAndReferrerInfoHashKey(URLAndReferrerInfoHashKey&& aToMove) noexcept
 | |
|       : PLDHashEntryHdr(std::move(aToMove)), mKey(std::move(aToMove.mKey)) {
 | |
|     MOZ_COUNT_CTOR(URLAndReferrerInfoHashKey);
 | |
|   }
 | |
|   MOZ_COUNTED_DTOR(URLAndReferrerInfoHashKey)
 | |
| 
 | |
|   const URLAndReferrerInfo* GetKey() const { return mKey; }
 | |
| 
 | |
|   bool KeyEquals(const URLAndReferrerInfo* aKey) const {
 | |
|     if (!mKey) {
 | |
|       return !aKey;
 | |
|     }
 | |
|     return *mKey == *aKey;
 | |
|   }
 | |
| 
 | |
|   static const URLAndReferrerInfo* KeyToPointer(
 | |
|       const URLAndReferrerInfo* aKey) {
 | |
|     return aKey;
 | |
|   }
 | |
| 
 | |
|   static PLDHashNumber HashKey(const URLAndReferrerInfo* aKey) {
 | |
|     if (!aKey) {
 | |
|       // If the key is null, return hash for empty string.
 | |
|       return HashString(""_ns);
 | |
|     }
 | |
|     nsAutoCString urlSpec, referrerSpec;
 | |
|     // nsURIHashKey ignores GetSpec() failures, so we do too:
 | |
|     Unused << aKey->GetURI()->GetSpec(urlSpec);
 | |
|     return AddToHash(
 | |
|         HashString(urlSpec),
 | |
|         static_cast<ReferrerInfo*>(aKey->GetReferrerInfo())->Hash());
 | |
|   }
 | |
| 
 | |
|   enum { ALLOW_MEMMOVE = true };
 | |
| 
 | |
|  protected:
 | |
|   RefPtr<const URLAndReferrerInfo> mKey;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * 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* content,
 | |
|                                                       nsIURI* aURI) {
 | |
|   MOZ_ASSERT(content);
 | |
| 
 | |
|   // Content is in a shadow tree.  If this URL was specified in the subtree
 | |
|   // referenced by the <use>, 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 (SVGUseElement* use = content->GetContainingSVGUseShadowHost()) {
 | |
|     if (nsIURI* originalURI = use->GetSourceDocURI()) {
 | |
|       bool isEqualsExceptRef = false;
 | |
|       aURI->EqualsExceptRef(originalURI, &isEqualsExceptRef);
 | |
|       if (isEqualsExceptRef) {
 | |
|         return do_AddRef(originalURI);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // For a local-reference URL, resolve that fragment against the current
 | |
|   // document that relative URLs are resolved against.
 | |
|   return do_AddRef(content->OwnerDoc()->GetDocumentURI());
 | |
| }
 | |
| 
 | |
| static already_AddRefed<URLAndReferrerInfo> ResolveURLUsingLocalRef(
 | |
|     nsIFrame* aFrame, const StyleComputedImageUrl& aURL) {
 | |
|   MOZ_ASSERT(aFrame);
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri = aURL.GetURI();
 | |
| 
 | |
|   if (aURL.IsLocalRef()) {
 | |
|     uri = GetBaseURLForLocalRef(aFrame->GetContent(), uri);
 | |
|     uri = aURL.ResolveLocalRef(uri);
 | |
|   }
 | |
| 
 | |
|   if (!uri) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return do_AddRef(new URLAndReferrerInfo(uri, aURL.ExtraData()));
 | |
| }
 | |
| 
 | |
| static already_AddRefed<URLAndReferrerInfo> ResolveURLUsingLocalRef(
 | |
|     nsIContent* aContent, const nsAString& aURL) {
 | |
|   // Like GetBaseURLForLocalRef, we want to resolve the
 | |
|   // URL against any <use> element shadow tree's source document.
 | |
|   //
 | |
|   // Unlike GetBaseURLForLocalRef, we are assuming that the URL was specified
 | |
|   // directly on mFrame's content (because this ResolveURLUsingLocalRef
 | |
|   // overload is used for href="" attributes and not CSS URL values), so there
 | |
|   // is no need to check whether the URL was specified / inherited from
 | |
|   // outside the shadow tree.
 | |
|   nsIURI* base = nullptr;
 | |
|   const Encoding* encoding = nullptr;
 | |
|   if (SVGUseElement* use = aContent->GetContainingSVGUseShadowHost()) {
 | |
|     base = use->GetSourceDocURI();
 | |
|     encoding = use->GetSourceDocCharacterSet();
 | |
|   }
 | |
| 
 | |
|   if (!base) {
 | |
|     base = aContent->OwnerDoc()->GetDocumentURI();
 | |
|     encoding = aContent->OwnerDoc()->GetDocumentCharacterSet();
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   Unused << NS_NewURI(getter_AddRefs(uri), aURL, WrapNotNull(encoding), base);
 | |
| 
 | |
|   if (!uri) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // There's no clear refererer policy spec about non-CSS SVG resource
 | |
|   // references Bug 1415044 to investigate which referrer we should use
 | |
|   nsIReferrerInfo* referrerInfo =
 | |
|       aContent->OwnerDoc()->ReferrerInfoForInternalCSSAndSVGResources();
 | |
| 
 | |
|   return do_AddRef(new URLAndReferrerInfo(uri, referrerInfo));
 | |
| }
 | |
| 
 | |
| class SVGFilterObserverList;
 | |
| 
 | |
| /**
 | |
|  * A class used as a member of the "observer" classes below to help them
 | |
|  * avoid dereferencing their frame during presshell teardown when their frame
 | |
|  * may have been destroyed (leaving their pointer to their frame dangling).
 | |
|  *
 | |
|  * When a presshell is torn down, the properties for each frame may not be
 | |
|  * deleted until after the frames are destroyed.  "Observer" objects (attached
 | |
|  * as frame properties) must therefore check whether the presshell is being
 | |
|  * torn down before using their pointer to their frame.
 | |
|  *
 | |
|  * mFramePresShell may be null, but when mFrame is non-null, mFramePresShell
 | |
|  * is guaranteed to be non-null, too.
 | |
|  */
 | |
| struct SVGFrameReferenceFromProperty {
 | |
|   explicit SVGFrameReferenceFromProperty(nsIFrame* aFrame)
 | |
|       : mFrame(aFrame), mFramePresShell(aFrame->PresShell()) {}
 | |
| 
 | |
|   // Clear our reference to the frame.
 | |
|   void Detach() {
 | |
|     mFrame = nullptr;
 | |
|     mFramePresShell = nullptr;
 | |
|   }
 | |
| 
 | |
|   // null if the frame has become invalid
 | |
|   nsIFrame* Get() {
 | |
|     if (mFramePresShell && mFramePresShell->IsDestroying()) {
 | |
|       Detach();  // mFrame is no longer valid.
 | |
|     }
 | |
|     return mFrame;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   // The frame that our property is attached to (may be null).
 | |
|   nsIFrame* mFrame;
 | |
|   PresShell* mFramePresShell;
 | |
| };
 | |
| 
 | |
| void SVGRenderingObserver::StartObserving() {
 | |
|   Element* target = GetReferencedElementWithoutObserving();
 | |
|   if (target) {
 | |
|     target->AddMutationObserver(this);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SVGRenderingObserver::StopObserving() {
 | |
|   Element* target = GetReferencedElementWithoutObserving();
 | |
| 
 | |
|   if (target) {
 | |
|     target->RemoveMutationObserver(this);
 | |
|     if (mInObserverSet) {
 | |
|       SVGObserverUtils::RemoveRenderingObserver(target, this);
 | |
|       mInObserverSet = false;
 | |
|     }
 | |
|   }
 | |
|   NS_ASSERTION(!mInObserverSet, "still in an observer set?");
 | |
| }
 | |
| 
 | |
| Element* SVGRenderingObserver::GetAndObserveReferencedElement() {
 | |
| #ifdef DEBUG
 | |
|   DebugObserverSet();
 | |
| #endif
 | |
|   Element* referencedElement = GetReferencedElementWithoutObserving();
 | |
|   if (referencedElement && !mInObserverSet) {
 | |
|     SVGObserverUtils::AddRenderingObserver(referencedElement, this);
 | |
|     mInObserverSet = true;
 | |
|   }
 | |
|   return referencedElement;
 | |
| }
 | |
| 
 | |
| nsIFrame* SVGRenderingObserver::GetAndObserveReferencedFrame() {
 | |
|   Element* referencedElement = GetAndObserveReferencedElement();
 | |
|   return referencedElement ? referencedElement->GetPrimaryFrame() : nullptr;
 | |
| }
 | |
| 
 | |
| nsIFrame* SVGRenderingObserver::GetAndObserveReferencedFrame(
 | |
|     LayoutFrameType aFrameType, bool* aOK) {
 | |
|   nsIFrame* frame = GetAndObserveReferencedFrame();
 | |
|   if (frame) {
 | |
|     if (frame->Type() == aFrameType) {
 | |
|       return frame;
 | |
|     }
 | |
|     if (aOK) {
 | |
|       *aOK = false;
 | |
|     }
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void SVGRenderingObserver::OnNonDOMMutationRenderingChange() {
 | |
|   OnRenderingChange();
 | |
| }
 | |
| 
 | |
| void SVGRenderingObserver::NotifyEvictedFromRenderingObserverSet() {
 | |
|   mInObserverSet = false;  // We've been removed from rendering-obs. set.
 | |
|   StopObserving();         // Stop observing mutations too.
 | |
| }
 | |
| 
 | |
| void SVGRenderingObserver::AttributeChanged(dom::Element* aElement,
 | |
|                                             int32_t aNameSpaceID,
 | |
|                                             nsAtom* aAttribute,
 | |
|                                             int32_t aModType,
 | |
|                                             const nsAttrValue* aOldValue) {
 | |
|   if (aElement->IsInNativeAnonymousSubtree()) {
 | |
|     // Don't observe attribute changes in native-anonymous subtrees like
 | |
|     // scrollbars.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // 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 SVGRenderingObserver::ContentAppended(nsIContent* aFirstNewContent) {
 | |
|   OnRenderingChange();
 | |
| }
 | |
| 
 | |
| void SVGRenderingObserver::ContentInserted(nsIContent* aChild) {
 | |
|   OnRenderingChange();
 | |
| }
 | |
| 
 | |
| void SVGRenderingObserver::ContentRemoved(nsIContent* aChild,
 | |
|                                           nsIContent* aPreviousSibling) {
 | |
|   OnRenderingChange();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * SVG elements reference supporting resources by element ID. We need to
 | |
|  * track when those resources change and when the document 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 SVGIDRenderingObserver to manage the relationship. The
 | |
|  * property object is attached to the referencing frame.
 | |
|  */
 | |
| class SVGIDRenderingObserver : public SVGRenderingObserver {
 | |
|  public:
 | |
|   // Callback for checking if the element being observed is valid for this
 | |
|   // observer. Note that this may be called during construction, before the
 | |
|   // deriving class is fully constructed.
 | |
|   using TargetIsValidCallback = bool (*)(const Element&);
 | |
|   SVGIDRenderingObserver(
 | |
|       URLAndReferrerInfo* aURI, nsIContent* aObservingContent,
 | |
|       bool aReferenceImage,
 | |
|       uint32_t aCallbacks = kAttributeChanged | kContentAppended |
 | |
|                             kContentInserted | kContentRemoved,
 | |
|       TargetIsValidCallback aTargetIsValidCallback = nullptr);
 | |
| 
 | |
|   void Traverse(nsCycleCollectionTraversalCallback* aCB);
 | |
| 
 | |
|  protected:
 | |
|   virtual ~SVGIDRenderingObserver() {
 | |
|     // This needs to call our GetReferencedElementWithoutObserving override,
 | |
|     // so must be called here rather than in our base class's dtor.
 | |
|     StopObserving();
 | |
|   }
 | |
| 
 | |
|   void TargetChanged() {
 | |
|     mTargetIsValid = ([this] {
 | |
|       Element* observed = mObservedElementTracker.get();
 | |
|       if (!observed) {
 | |
|         return false;
 | |
|       }
 | |
|       // If the content is observing an ancestor, then return the target is not
 | |
|       // valid.
 | |
|       //
 | |
|       // TODO(emilio): Should we allow content observing its own descendants?
 | |
|       // That seems potentially-bad as well.
 | |
|       if (observed->OwnerDoc() == mObservingContent->OwnerDoc() &&
 | |
|           nsContentUtils::ContentIsHostIncludingDescendantOf(mObservingContent,
 | |
|                                                              observed)) {
 | |
|         return false;
 | |
|       }
 | |
|       if (mTargetIsValidCallback) {
 | |
|         return mTargetIsValidCallback(*observed);
 | |
|       }
 | |
|       return true;
 | |
|     }());
 | |
|   }
 | |
| 
 | |
|   Element* GetReferencedElementWithoutObserving() final {
 | |
|     return mTargetIsValid ? mObservedElementTracker.get() : nullptr;
 | |
|   }
 | |
| 
 | |
|   void OnRenderingChange() override;
 | |
| 
 | |
|   /**
 | |
|    * Helper that provides a reference to the element with the ID that our
 | |
|    * observer wants to observe, and that will invalidate our observer if the
 | |
|    * element that that ID identifies changes to a different element (or none).
 | |
|    */
 | |
|   class ElementTracker final : public IDTracker {
 | |
|    public:
 | |
|     explicit ElementTracker(SVGIDRenderingObserver* aOwningObserver)
 | |
|         : mOwningObserver(aOwningObserver) {}
 | |
| 
 | |
|    protected:
 | |
|     void ElementChanged(Element* aFrom, Element* aTo) override {
 | |
|       // Call OnRenderingChange() before the target changes, so that
 | |
|       // mIsTargetValid reflects the right state.
 | |
|       mOwningObserver->OnRenderingChange();
 | |
|       mOwningObserver->StopObserving();
 | |
|       IDTracker::ElementChanged(aFrom, aTo);
 | |
|       mOwningObserver->TargetChanged();
 | |
|       mOwningObserver->StartObserving();
 | |
|       // And same after the target changes, for the same reason.
 | |
|       mOwningObserver->OnRenderingChange();
 | |
|     }
 | |
|     /**
 | |
|      * Override IsPersistent because we want to keep tracking the element
 | |
|      * for the ID even when it changes.
 | |
|      */
 | |
|     bool IsPersistent() override { return true; }
 | |
| 
 | |
|    private:
 | |
|     SVGIDRenderingObserver* mOwningObserver;
 | |
|   };
 | |
| 
 | |
|   ElementTracker mObservedElementTracker;
 | |
|   RefPtr<Element> mObservingContent;
 | |
|   bool mTargetIsValid = false;
 | |
|   TargetIsValidCallback mTargetIsValidCallback;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Note that in the current setup there are two separate observer lists.
 | |
|  *
 | |
|  * In SVGIDRenderingObserver's ctor, the new object adds itself to the
 | |
|  * mutation observer list maintained by the referenced element. In this way the
 | |
|  * SVGIDRenderingObserver is notified if there are any attribute or content
 | |
|  * tree changes to the element or any of its *descendants*.
 | |
|  *
 | |
|  * In SVGIDRenderingObserver::GetAndObserveReferencedElement() the
 | |
|  * SVGIDRenderingObserver object also adds itself to an
 | |
|  * SVGRenderingObserverSet 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.
 | |
|  */
 | |
| SVGIDRenderingObserver::SVGIDRenderingObserver(
 | |
|     URLAndReferrerInfo* aURI, nsIContent* aObservingContent,
 | |
|     bool aReferenceImage, uint32_t aCallbacks,
 | |
|     TargetIsValidCallback aTargetIsValidCallback)
 | |
|     : SVGRenderingObserver(aCallbacks),
 | |
|       mObservedElementTracker(this),
 | |
|       mObservingContent(aObservingContent->AsElement()),
 | |
|       mTargetIsValidCallback(aTargetIsValidCallback) {
 | |
|   // Start watching the target element
 | |
|   nsIURI* uri = nullptr;
 | |
|   nsIReferrerInfo* referrerInfo = nullptr;
 | |
|   if (aURI) {
 | |
|     uri = aURI->GetURI();
 | |
|     referrerInfo = aURI->GetReferrerInfo();
 | |
|   }
 | |
| 
 | |
|   mObservedElementTracker.ResetToURIFragmentID(
 | |
|       aObservingContent, uri, referrerInfo, true, aReferenceImage);
 | |
|   TargetChanged();
 | |
|   StartObserving();
 | |
| }
 | |
| 
 | |
| void SVGIDRenderingObserver::Traverse(nsCycleCollectionTraversalCallback* aCB) {
 | |
|   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*aCB, "mObservingContent");
 | |
|   aCB->NoteXPCOMChild(mObservingContent);
 | |
|   mObservedElementTracker.Traverse(aCB);
 | |
| }
 | |
| 
 | |
| void SVGIDRenderingObserver::OnRenderingChange() {
 | |
|   if (mObservedElementTracker.get() && mInObserverSet) {
 | |
|     SVGObserverUtils::RemoveRenderingObserver(mObservedElementTracker.get(),
 | |
|                                               this);
 | |
|     mInObserverSet = false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class SVGRenderingObserverProperty : public SVGIDRenderingObserver {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS
 | |
| 
 | |
|   SVGRenderingObserverProperty(
 | |
|       URLAndReferrerInfo* aURI, nsIFrame* aFrame, bool aReferenceImage,
 | |
|       uint32_t aCallbacks = kAttributeChanged | kContentAppended |
 | |
|                             kContentInserted | kContentRemoved,
 | |
|       TargetIsValidCallback aTargetIsValidCallback = nullptr)
 | |
|       : SVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage,
 | |
|                                aCallbacks, aTargetIsValidCallback),
 | |
|         mFrameReference(aFrame) {}
 | |
| 
 | |
|  protected:
 | |
|   virtual ~SVGRenderingObserverProperty() = default;  // non-public
 | |
| 
 | |
|   void OnRenderingChange() override;
 | |
| 
 | |
|   SVGFrameReferenceFromProperty mFrameReference;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(SVGRenderingObserverProperty, nsIMutationObserver)
 | |
| 
 | |
| void SVGRenderingObserverProperty::OnRenderingChange() {
 | |
|   SVGIDRenderingObserver::OnRenderingChange();
 | |
| 
 | |
|   if (!mTargetIsValid) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsIFrame* frame = mFrameReference.Get();
 | |
| 
 | |
|   if (frame && frame->HasAllStateBits(NS_FRAME_SVG_LAYOUT)) {
 | |
|     // We need to notify anything that is observing the referencing frame or
 | |
|     // any of its ancestors that the referencing frame has been invalidated.
 | |
|     // Since walking the parent chain checking for observers is expensive we
 | |
|     // do that using a change hint (multiple change hints of the same type are
 | |
|     // coalesced).
 | |
|     nsLayoutUtils::PostRestyleEvent(frame->GetContent()->AsElement(),
 | |
|                                     RestyleHint{0},
 | |
|                                     nsChangeHint_InvalidateRenderingObservers);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool IsSVGGeometryElement(const Element& aObserved) {
 | |
|   return aObserved.IsSVGGeometryElement();
 | |
| }
 | |
| 
 | |
| class SVGTextPathObserver final : public SVGRenderingObserverProperty {
 | |
|  public:
 | |
|   SVGTextPathObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
 | |
|                       bool aReferenceImage)
 | |
|       : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage,
 | |
|                                      kAttributeChanged, IsSVGGeometryElement) {}
 | |
| 
 | |
|  protected:
 | |
|   void OnRenderingChange() override;
 | |
| };
 | |
| 
 | |
| void SVGTextPathObserver::OnRenderingChange() {
 | |
|   SVGRenderingObserverProperty::OnRenderingChange();
 | |
| 
 | |
|   if (!mTargetIsValid) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsIFrame* frame = mFrameReference.Get();
 | |
|   if (!frame) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(frame->IsSVGFrame() || frame->IsInSVGTextSubtree(),
 | |
|              "SVG frame expected");
 | |
| 
 | |
|   MOZ_ASSERT(frame->GetContent()->IsSVGElement(nsGkAtoms::textPath),
 | |
|              "expected frame for a <textPath> element");
 | |
| 
 | |
|   auto* text = static_cast<SVGTextFrame*>(
 | |
|       nsLayoutUtils::GetClosestFrameOfType(frame, LayoutFrameType::SVGText));
 | |
|   MOZ_ASSERT(text, "expected to find an ancestor SVGTextFrame");
 | |
|   if (text) {
 | |
|     text->AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY);
 | |
| 
 | |
|     if (SVGUtils::AnyOuterSVGIsCallingReflowSVG(text)) {
 | |
|       text->AddStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
 | |
|       if (text->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
 | |
|         text->ReflowSVGNonDisplayText();
 | |
|       } else {
 | |
|         text->ReflowSVG();
 | |
|       }
 | |
|     } else {
 | |
|       text->ScheduleReflowSVG();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| class SVGMPathObserver final : public SVGIDRenderingObserver {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS
 | |
| 
 | |
|   SVGMPathObserver(URLAndReferrerInfo* aURI, SVGMPathElement* aElement)
 | |
|       : SVGIDRenderingObserver(aURI, aElement, /* aReferenceImage = */ false,
 | |
|                                kAttributeChanged, IsSVGGeometryElement) {}
 | |
| 
 | |
|  protected:
 | |
|   virtual ~SVGMPathObserver() = default;  // non-public
 | |
| 
 | |
|   void OnRenderingChange() override;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(SVGMPathObserver, nsIMutationObserver)
 | |
| 
 | |
| void SVGMPathObserver::OnRenderingChange() {
 | |
|   SVGIDRenderingObserver::OnRenderingChange();
 | |
| 
 | |
|   if (!mTargetIsValid) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   auto* element = static_cast<SVGMPathElement*>(mObservingContent.get());
 | |
|   element->NotifyParentOfMpathChange();
 | |
| }
 | |
| 
 | |
| class SVGMarkerObserver final : public SVGRenderingObserverProperty {
 | |
|  public:
 | |
|   SVGMarkerObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
 | |
|                     bool aReferenceImage)
 | |
|       : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage,
 | |
|                                      kAttributeChanged | kContentAppended |
 | |
|                                          kContentInserted | kContentRemoved) {}
 | |
| 
 | |
|  protected:
 | |
|   void OnRenderingChange() override;
 | |
| };
 | |
| 
 | |
| void SVGMarkerObserver::OnRenderingChange() {
 | |
|   SVGRenderingObserverProperty::OnRenderingChange();
 | |
| 
 | |
|   nsIFrame* frame = mFrameReference.Get();
 | |
|   if (!frame) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(frame->IsSVGFrame(), "SVG frame expected");
 | |
| 
 | |
|   // Don't need to request ReflowFrame if we're being reflowed.
 | |
|   // Because mRect for SVG frames includes the bounds of any markers
 | |
|   // (see the comment for nsIFrame::GetRect), the referencing frame must be
 | |
|   // reflowed for any marker changes.
 | |
|   if (!frame->HasAnyStateBits(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!!!
 | |
|     SVGUtils::ScheduleReflowSVG(frame);
 | |
|   }
 | |
|   frame->PresContext()->RestyleManager()->PostRestyleEvent(
 | |
|       frame->GetContent()->AsElement(), RestyleHint{0},
 | |
|       nsChangeHint_RepaintFrame);
 | |
| }
 | |
| 
 | |
| class SVGPaintingProperty : public SVGRenderingObserverProperty {
 | |
|  public:
 | |
|   SVGPaintingProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
 | |
|                       bool aReferenceImage)
 | |
|       : SVGRenderingObserverProperty(aURI, aFrame, aReferenceImage) {}
 | |
| 
 | |
|  protected:
 | |
|   void OnRenderingChange() override;
 | |
| };
 | |
| 
 | |
| void SVGPaintingProperty::OnRenderingChange() {
 | |
|   SVGRenderingObserverProperty::OnRenderingChange();
 | |
| 
 | |
|   nsIFrame* frame = mFrameReference.Get();
 | |
|   if (!frame) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
 | |
|     frame->InvalidateFrameSubtree();
 | |
|   } else {
 | |
|     for (nsIFrame* f = frame; f;
 | |
|          f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
 | |
|       f->InvalidateFrame();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Observer for -moz-element(#element). Note that the observed element does not
 | |
| // have to be an SVG element.
 | |
| class SVGMozElementObserver final : public SVGPaintingProperty {
 | |
|  public:
 | |
|   SVGMozElementObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame)
 | |
|       : SVGPaintingProperty(aURI, aFrame, /* aReferenceImage = */ true) {}
 | |
| 
 | |
|   // We only return true here because GetAndObserveBackgroundImage uses us
 | |
|   // to implement observing of arbitrary elements (including HTML elements)
 | |
|   // that may require us to repaint if the referenced element is reflowed.
 | |
|   // Bug 1496065 has been filed to remove that support though.
 | |
|   bool ObservesReflow() override { return true; }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * For content with `background-clip: text`.
 | |
|  *
 | |
|  * This observer is unusual in that the observing frame and observed frame are
 | |
|  * the same frame.  This is because the observing frame is observing for reflow
 | |
|  * of its descendant text nodes, since such reflows may not result in the
 | |
|  * frame's nsDisplayBackground changing.  In other words, Display List Based
 | |
|  * Invalidation may not invalidate the frame's background, so we need this
 | |
|  * observer to make sure that happens.
 | |
|  *
 | |
|  * XXX: It's questionable whether we should even be [ab]using the SVG observer
 | |
|  * mechanism for `background-clip:text`.  Since we know that the observed frame
 | |
|  * is the frame we need to invalidate, we could just check the computed style
 | |
|  * in the (one) place where we pass INVALIDATE_REFLOW and invalidate there...
 | |
|  */
 | |
| class BackgroundClipRenderingObserver : public SVGRenderingObserver {
 | |
|  public:
 | |
|   explicit BackgroundClipRenderingObserver(nsIFrame* aFrame) : mFrame(aFrame) {}
 | |
| 
 | |
|   NS_DECL_ISUPPORTS
 | |
| 
 | |
|  private:
 | |
|   // We do not call StopObserving() since the observing and observed element
 | |
|   // are the same element (and because we could crash - see bug 1556441).
 | |
|   virtual ~BackgroundClipRenderingObserver() = default;
 | |
| 
 | |
|   Element* GetReferencedElementWithoutObserving() final {
 | |
|     return mFrame->GetContent()->AsElement();
 | |
|   }
 | |
| 
 | |
|   void OnRenderingChange() final;
 | |
| 
 | |
|   /**
 | |
|    * Observing for mutations is not enough.  A new font loading and applying
 | |
|    * to the text content could cause it to reflow, and we need to invalidate
 | |
|    * for that.
 | |
|    */
 | |
|   bool ObservesReflow() final { return true; }
 | |
| 
 | |
|   // The observer and observee!
 | |
|   nsIFrame* mFrame;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(BackgroundClipRenderingObserver, nsIMutationObserver)
 | |
| 
 | |
| void BackgroundClipRenderingObserver::OnRenderingChange() {
 | |
|   for (nsIFrame* f = mFrame; f;
 | |
|        f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
 | |
|     f->InvalidateFrame();
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool IsSVGFilterElement(const Element& aObserved) {
 | |
|   return aObserved.IsSVGElement(nsGkAtoms::filter);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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 SVGFilterObserverList class manages a list of SVGFilterObservers.
 | |
|  */
 | |
| class SVGFilterObserver final : public SVGIDRenderingObserver {
 | |
|  public:
 | |
|   SVGFilterObserver(URLAndReferrerInfo* aURI, nsIContent* aObservingContent,
 | |
|                     SVGFilterObserverList* aFilterChainObserver)
 | |
|       : SVGIDRenderingObserver(aURI, aObservingContent, false,
 | |
|                                kAttributeChanged | kContentAppended |
 | |
|                                    kContentInserted | kContentRemoved,
 | |
|                                IsSVGFilterElement),
 | |
|         mFilterObserverList(aFilterChainObserver) {}
 | |
| 
 | |
|   void DetachFromChainObserver() { mFilterObserverList = nullptr; }
 | |
| 
 | |
|   /**
 | |
|    * @return the filter frame, or null if there is no filter frame
 | |
|    */
 | |
|   SVGFilterFrame* GetAndObserveFilterFrame();
 | |
| 
 | |
|   // nsISupports
 | |
|   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 | |
|   NS_DECL_CYCLE_COLLECTION_CLASS(SVGFilterObserver)
 | |
| 
 | |
|   // SVGIDRenderingObserver
 | |
|   void OnRenderingChange() override;
 | |
| 
 | |
|  protected:
 | |
|   virtual ~SVGFilterObserver() = default;  // non-public
 | |
| 
 | |
|   SVGFilterObserverList* mFilterObserverList;
 | |
| };
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTING_ADDREF(SVGFilterObserver)
 | |
| NS_IMPL_CYCLE_COLLECTING_RELEASE(SVGFilterObserver)
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGFilterObserver)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsISupports)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_CLASS(SVGFilterObserver)
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGFilterObserver)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservedElementTracker)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservingContent)
 | |
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGFilterObserver)
 | |
|   tmp->StopObserving();
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservedElementTracker);
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservingContent)
 | |
| NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 | |
| 
 | |
| SVGFilterFrame* SVGFilterObserver::GetAndObserveFilterFrame() {
 | |
|   return static_cast<SVGFilterFrame*>(
 | |
|       GetAndObserveReferencedFrame(LayoutFrameType::SVGFilter, nullptr));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * This class manages a list of SVGFilterObservers, which correspond to
 | |
|  * reference to SVG filters in a list of filters in a given 'filter' property.
 | |
|  * e.g. filter: url(#svg-filter-1) blur(10px) url(#svg-filter-2);
 | |
|  *
 | |
|  * In the above example, the SVGFilterObserverList will manage two
 | |
|  * SVGFilterObservers, one for each of the references to SVG filters.  CSS
 | |
|  * filters like "blur(10px)" don't reference filter elements, so they don't
 | |
|  * need an SVGFilterObserver.  The style system invalidates changes to CSS
 | |
|  * filters.
 | |
|  *
 | |
|  * FIXME(emilio): Why do we need this as opposed to the individual observers we
 | |
|  * create in the constructor?
 | |
|  */
 | |
| class SVGFilterObserverList : public nsISupports {
 | |
|  public:
 | |
|   SVGFilterObserverList(Span<const StyleFilter> aFilters,
 | |
|                         nsIContent* aFilteredElement,
 | |
|                         nsIFrame* aFilteredFrame = nullptr);
 | |
| 
 | |
|   const nsTArray<RefPtr<SVGFilterObserver>>& GetObservers() const {
 | |
|     return mObservers;
 | |
|   }
 | |
| 
 | |
|   // nsISupports
 | |
|   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 | |
|   NS_DECL_CYCLE_COLLECTION_CLASS(SVGFilterObserverList)
 | |
| 
 | |
|   virtual void OnRenderingChange() = 0;
 | |
| 
 | |
|  protected:
 | |
|   virtual ~SVGFilterObserverList();
 | |
| 
 | |
|   void DetachObservers() {
 | |
|     for (auto& observer : mObservers) {
 | |
|       observer->DetachFromChainObserver();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsTArray<RefPtr<SVGFilterObserver>> mObservers;
 | |
| };
 | |
| 
 | |
| void SVGFilterObserver::OnRenderingChange() {
 | |
|   SVGIDRenderingObserver::OnRenderingChange();
 | |
| 
 | |
|   if (mFilterObserverList) {
 | |
|     mFilterObserverList->OnRenderingChange();
 | |
|   }
 | |
| 
 | |
|   if (!mTargetIsValid) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsIFrame* frame = mObservingContent->GetPrimaryFrame();
 | |
|   if (!frame) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Repaint asynchronously in case the filter frame is being torn down
 | |
|   nsChangeHint changeHint = nsChangeHint(nsChangeHint_RepaintFrame);
 | |
| 
 | |
|   // Since we don't call SVGRenderingObserverProperty::
 | |
|   // OnRenderingChange, we have to add this bit ourselves.
 | |
|   if (frame->HasAnyStateBits(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->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
 | |
|     changeHint |= nsChangeHint_UpdateOverflow;
 | |
|   }
 | |
|   frame->PresContext()->RestyleManager()->PostRestyleEvent(
 | |
|       mObservingContent, RestyleHint{0}, changeHint);
 | |
| }
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTING_ADDREF(SVGFilterObserverList)
 | |
| NS_IMPL_CYCLE_COLLECTING_RELEASE(SVGFilterObserverList)
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_CLASS(SVGFilterObserverList)
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGFilterObserverList)
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers)
 | |
| NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGFilterObserverList)
 | |
|   tmp->DetachObservers();
 | |
|   NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers);
 | |
| NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SVGFilterObserverList)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsISupports)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| SVGFilterObserverList::SVGFilterObserverList(Span<const StyleFilter> aFilters,
 | |
|                                              nsIContent* aFilteredElement,
 | |
|                                              nsIFrame* aFilteredFrame) {
 | |
|   for (const auto& filter : aFilters) {
 | |
|     if (!filter.IsUrl()) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     const auto& url = filter.AsUrl();
 | |
| 
 | |
|     // aFilteredFrame can be null if this filter belongs to a
 | |
|     // CanvasRenderingContext2D.
 | |
|     RefPtr<URLAndReferrerInfo> filterURL;
 | |
|     if (aFilteredFrame) {
 | |
|       filterURL = ResolveURLUsingLocalRef(aFilteredFrame, url);
 | |
|     } else {
 | |
|       nsCOMPtr<nsIURI> resolvedURI = url.ResolveLocalRef(aFilteredElement);
 | |
|       if (resolvedURI) {
 | |
|         filterURL = new URLAndReferrerInfo(resolvedURI, url.ExtraData());
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     RefPtr<SVGFilterObserver> observer =
 | |
|         new SVGFilterObserver(filterURL, aFilteredElement, this);
 | |
|     mObservers.AppendElement(observer);
 | |
|   }
 | |
| }
 | |
| 
 | |
| SVGFilterObserverList::~SVGFilterObserverList() { DetachObservers(); }
 | |
| 
 | |
| class SVGFilterObserverListForCSSProp final : public SVGFilterObserverList {
 | |
|  public:
 | |
|   SVGFilterObserverListForCSSProp(Span<const StyleFilter> aFilters,
 | |
|                                   nsIFrame* aFilteredFrame)
 | |
|       : SVGFilterObserverList(aFilters, aFilteredFrame->GetContent(),
 | |
|                               aFilteredFrame) {}
 | |
| 
 | |
|  protected:
 | |
|   void OnRenderingChange() override;
 | |
|   bool mInvalidating = false;
 | |
| };
 | |
| 
 | |
| void SVGFilterObserverListForCSSProp::OnRenderingChange() {
 | |
|   if (mInvalidating) {
 | |
|     return;
 | |
|   }
 | |
|   AutoRestore<bool> guard(mInvalidating);
 | |
|   mInvalidating = true;
 | |
|   for (auto& observer : mObservers) {
 | |
|     observer->OnRenderingChange();
 | |
|   }
 | |
| }
 | |
| 
 | |
| class SVGFilterObserverListForCanvasContext final
 | |
|     : public SVGFilterObserverList {
 | |
|  public:
 | |
|   SVGFilterObserverListForCanvasContext(CanvasRenderingContext2D* aContext,
 | |
|                                         Element* aCanvasElement,
 | |
|                                         Span<const StyleFilter> aFilters)
 | |
|       : SVGFilterObserverList(aFilters, aCanvasElement), mContext(aContext) {}
 | |
| 
 | |
|   void OnRenderingChange() override;
 | |
|   void DetachFromContext() { mContext = nullptr; }
 | |
| 
 | |
|  private:
 | |
|   CanvasRenderingContext2D* mContext;
 | |
| };
 | |
| 
 | |
| void SVGFilterObserverListForCanvasContext::OnRenderingChange() {
 | |
|   if (!mContext) {
 | |
|     NS_WARNING(
 | |
|         "GFX: This should never be called without a context, except during "
 | |
|         "cycle collection (when DetachFromContext has been called)");
 | |
|     return;
 | |
|   }
 | |
|   // Refresh the cached FilterDescription in mContext->CurrentState().filter.
 | |
|   // If this filter is not at the top of the state stack, we'll refresh the
 | |
|   // wrong filter, but that's ok, because we'll refresh the right filter
 | |
|   // when we pop the state stack in CanvasRenderingContext2D::Restore().
 | |
|   //
 | |
|   // We don't need to flush, we're called by layout.
 | |
|   RefPtr<CanvasRenderingContext2D> kungFuDeathGrip(mContext);
 | |
|   kungFuDeathGrip->UpdateFilter(/* aFlushIfNeeded = */ false);
 | |
| }
 | |
| 
 | |
| class SVGMaskObserverList final : public nsISupports {
 | |
|  public:
 | |
|   explicit SVGMaskObserverList(nsIFrame* aFrame);
 | |
| 
 | |
|   // nsISupports
 | |
|   NS_DECL_ISUPPORTS
 | |
| 
 | |
|   const nsTArray<RefPtr<SVGPaintingProperty>>& GetObservers() const {
 | |
|     return mProperties;
 | |
|   }
 | |
| 
 | |
|   void ResolveImage(uint32_t aIndex);
 | |
| 
 | |
|  private:
 | |
|   virtual ~SVGMaskObserverList() = default;  // non-public
 | |
|   nsTArray<RefPtr<SVGPaintingProperty>> mProperties;
 | |
|   nsIFrame* mFrame;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(SVGMaskObserverList, nsISupports)
 | |
| 
 | |
| SVGMaskObserverList::SVGMaskObserverList(nsIFrame* aFrame) : mFrame(aFrame) {
 | |
|   const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
 | |
| 
 | |
|   for (uint32_t i = 0; i < svgReset->mMask.mImageCount; i++) {
 | |
|     const StyleComputedImageUrl* data =
 | |
|         svgReset->mMask.mLayers[i].mImage.GetImageRequestURLValue();
 | |
|     RefPtr<URLAndReferrerInfo> maskUri;
 | |
|     if (data) {
 | |
|       maskUri = ResolveURLUsingLocalRef(aFrame, *data);
 | |
|     }
 | |
| 
 | |
|     bool hasRef = false;
 | |
|     if (maskUri) {
 | |
|       maskUri->GetURI()->GetHasRef(&hasRef);
 | |
|     }
 | |
| 
 | |
|     // Accrording to maskUri, SVGPaintingProperty'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.
 | |
|     SVGPaintingProperty* prop = new SVGPaintingProperty(
 | |
|         hasRef ? maskUri.get() : nullptr, aFrame, false);
 | |
|     mProperties.AppendElement(prop);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SVGMaskObserverList::ResolveImage(uint32_t aIndex) {
 | |
|   const nsStyleSVGReset* svgReset = mFrame->StyleSVGReset();
 | |
|   MOZ_ASSERT(aIndex < svgReset->mMask.mImageCount);
 | |
| 
 | |
|   const auto& image = svgReset->mMask.mLayers[aIndex].mImage;
 | |
|   if (image.IsResolved()) {
 | |
|     return;
 | |
|   }
 | |
|   MOZ_ASSERT(image.IsImageRequestType());
 | |
|   Document* doc = mFrame->PresContext()->Document();
 | |
|   const_cast<StyleImage&>(image).ResolveImage(*doc, nullptr);
 | |
|   if (imgRequestProxy* req = image.GetImageRequest()) {
 | |
|     // FIXME(emilio): What disassociates this request?
 | |
|     doc->StyleImageLoader()->AssociateRequestToFrame(req, mFrame);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Used for gradient-to-gradient, pattern-to-pattern and filter-to-filter
 | |
|  * references to "template" elements (specified via the 'href' attributes).
 | |
|  */
 | |
| class SVGTemplateElementObserver : public SVGIDRenderingObserver {
 | |
|  public:
 | |
|   NS_DECL_ISUPPORTS
 | |
| 
 | |
|   SVGTemplateElementObserver(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
 | |
|                              bool aReferenceImage)
 | |
|       : SVGIDRenderingObserver(aURI, aFrame->GetContent(), aReferenceImage,
 | |
|                                kAttributeChanged | kContentAppended |
 | |
|                                    kContentInserted | kContentRemoved),
 | |
|         mFrameReference(aFrame) {}
 | |
| 
 | |
|  protected:
 | |
|   virtual ~SVGTemplateElementObserver() = default;  // non-public
 | |
| 
 | |
|   void OnRenderingChange() override;
 | |
| 
 | |
|   SVGFrameReferenceFromProperty mFrameReference;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(SVGTemplateElementObserver, nsIMutationObserver)
 | |
| 
 | |
| void SVGTemplateElementObserver::OnRenderingChange() {
 | |
|   SVGIDRenderingObserver::OnRenderingChange();
 | |
| 
 | |
|   if (nsIFrame* frame = mFrameReference.Get()) {
 | |
|     SVGObserverUtils::InvalidateRenderingObservers(frame);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * An instance of this class is stored on an observed frame (as a frame
 | |
|  * property) whenever the frame has active rendering observers.  It is used to
 | |
|  * store pointers to the SVGRenderingObserver instances belonging to any
 | |
|  * observing frames, allowing invalidations from the observed frame to be sent
 | |
|  * to all observing frames.
 | |
|  *
 | |
|  * SVGRenderingObserver instances that are added are not strongly referenced,
 | |
|  * so they must remove themselves before they die.
 | |
|  *
 | |
|  * This class is "single-shot", which is to say that when something about the
 | |
|  * observed element changes, InvalidateAll() clears our hashtable of
 | |
|  * SVGRenderingObservers.  SVGRenderingObserver objects will be added back
 | |
|  * again if/when the observing frame looks up our observed frame to use it.
 | |
|  *
 | |
|  * XXXjwatt: is this the best thing to do nowadays?  Back when that mechanism
 | |
|  * landed in bug 330498 we had two pass, recursive invalidation up the frame
 | |
|  * tree, and I think reference loops were a problem.  Nowadays maybe a flag
 | |
|  * on the SVGRenderingObserver objects to coalesce invalidations may work
 | |
|  * better?
 | |
|  *
 | |
|  * InvalidateAll must be called before this object is destroyed, i.e.
 | |
|  * before the referenced frame is destroyed. This should normally happen
 | |
|  * via SVGContainerFrame::RemoveFrame, since only frames in the frame
 | |
|  * tree should be referenced.
 | |
|  */
 | |
| class SVGRenderingObserverSet {
 | |
|  public:
 | |
|   SVGRenderingObserverSet() : mObservers(4) {
 | |
|     MOZ_COUNT_CTOR(SVGRenderingObserverSet);
 | |
|   }
 | |
| 
 | |
|   ~SVGRenderingObserverSet() { MOZ_COUNT_DTOR(SVGRenderingObserverSet); }
 | |
| 
 | |
|   void Add(SVGRenderingObserver* aObserver) { mObservers.Insert(aObserver); }
 | |
|   void Remove(SVGRenderingObserver* aObserver) { mObservers.Remove(aObserver); }
 | |
| #ifdef DEBUG
 | |
|   bool Contains(SVGRenderingObserver* aObserver) {
 | |
|     return mObservers.Contains(aObserver);
 | |
|   }
 | |
| #endif
 | |
|   bool IsEmpty() { return mObservers.IsEmpty(); }
 | |
| 
 | |
|   /**
 | |
|    * 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:
 | |
|   nsTHashSet<SVGRenderingObserver*> mObservers;
 | |
| };
 | |
| 
 | |
| void SVGRenderingObserverSet::InvalidateAll() {
 | |
|   if (mObservers.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const auto observers = std::move(mObservers);
 | |
| 
 | |
|   // We've moved all the observers from mObservers, effectively
 | |
|   // evicting them so we need to notify all observers of eviction
 | |
|   // before we process any rendering changes. In short, don't
 | |
|   // try to merge these loops.
 | |
|   for (const auto& observer : observers) {
 | |
|     observer->NotifyEvictedFromRenderingObserverSet();
 | |
|   }
 | |
|   for (const auto& observer : observers) {
 | |
|     observer->OnNonDOMMutationRenderingChange();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SVGRenderingObserverSet::InvalidateAllForReflow() {
 | |
|   if (mObservers.IsEmpty()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   AutoTArray<SVGRenderingObserver*, 10> observers;
 | |
| 
 | |
|   for (auto it = mObservers.cbegin(), end = mObservers.cend(); it != end;
 | |
|        ++it) {
 | |
|     SVGRenderingObserver* obs = *it;
 | |
|     if (obs->ObservesReflow()) {
 | |
|       observers.AppendElement(obs);
 | |
|       mObservers.Remove(it);
 | |
|       obs->NotifyEvictedFromRenderingObserverSet();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (const auto& observer : observers) {
 | |
|     observer->OnNonDOMMutationRenderingChange();
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SVGRenderingObserverSet::RemoveAll() {
 | |
|   const auto observers = std::move(mObservers);
 | |
| 
 | |
|   // 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 (const auto& observer : observers) {
 | |
|     observer->NotifyEvictedFromRenderingObserverSet();
 | |
|   }
 | |
| }
 | |
| 
 | |
| static SVGRenderingObserverSet* GetObserverSet(Element* aElement) {
 | |
|   return static_cast<SVGRenderingObserverSet*>(
 | |
|       aElement->GetProperty(nsGkAtoms::renderingobserverset));
 | |
| }
 | |
| 
 | |
| #ifdef DEBUG
 | |
| // Defined down here because we need SVGRenderingObserverSet's definition.
 | |
| void SVGRenderingObserver::DebugObserverSet() {
 | |
|   Element* referencedElement = GetReferencedElementWithoutObserving();
 | |
|   if (referencedElement) {
 | |
|     SVGRenderingObserverSet* observers = GetObserverSet(referencedElement);
 | |
|     bool inObserverSet = observers && observers->Contains(this);
 | |
|     MOZ_ASSERT(inObserverSet == mInObserverSet,
 | |
|                "failed to track whether we're in our referenced element's "
 | |
|                "observer set!");
 | |
|   } else {
 | |
|     MOZ_ASSERT(!mInObserverSet, "In whose observer set are we, then?");
 | |
|   }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| using URIObserverHashtable =
 | |
|     nsInterfaceHashtable<URLAndReferrerInfoHashKey, nsIMutationObserver>;
 | |
| 
 | |
| using PaintingPropertyDescriptor =
 | |
|     const FramePropertyDescriptor<SVGPaintingProperty>*;
 | |
| 
 | |
| static void DestroyFilterProperty(SVGFilterObserverListForCSSProp* aProp) {
 | |
|   aProp->Release();
 | |
| }
 | |
| 
 | |
| NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefToTemplateProperty,
 | |
|                                      SVGTemplateElementObserver)
 | |
| NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(BackdropFilterProperty,
 | |
|                                     SVGFilterObserverListForCSSProp,
 | |
|                                     DestroyFilterProperty)
 | |
| NS_DECLARE_FRAME_PROPERTY_WITH_DTOR(FilterProperty,
 | |
|                                     SVGFilterObserverListForCSSProp,
 | |
|                                     DestroyFilterProperty)
 | |
| NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MaskProperty, SVGMaskObserverList)
 | |
| NS_DECLARE_FRAME_PROPERTY_RELEASABLE(ClipPathProperty, SVGPaintingProperty)
 | |
| NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerStartProperty, SVGMarkerObserver)
 | |
| NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerMidProperty, SVGMarkerObserver)
 | |
| NS_DECLARE_FRAME_PROPERTY_RELEASABLE(MarkerEndProperty, SVGMarkerObserver)
 | |
| NS_DECLARE_FRAME_PROPERTY_RELEASABLE(FillProperty, SVGPaintingProperty)
 | |
| NS_DECLARE_FRAME_PROPERTY_RELEASABLE(StrokeProperty, SVGPaintingProperty)
 | |
| NS_DECLARE_FRAME_PROPERTY_RELEASABLE(HrefAsTextPathProperty,
 | |
|                                      SVGTextPathObserver)
 | |
| NS_DECLARE_FRAME_PROPERTY_DELETABLE(BackgroundImageProperty,
 | |
|                                     URIObserverHashtable)
 | |
| NS_DECLARE_FRAME_PROPERTY_RELEASABLE(BackgroundClipObserverProperty,
 | |
|                                      BackgroundClipRenderingObserver)
 | |
| NS_DECLARE_FRAME_PROPERTY_RELEASABLE(OffsetPathProperty,
 | |
|                                      SVGRenderingObserverProperty)
 | |
| 
 | |
| template <class T>
 | |
| static T* GetEffectProperty(URLAndReferrerInfo* aURI, nsIFrame* aFrame,
 | |
|                             const FramePropertyDescriptor<T>* aProperty) {
 | |
|   if (!aURI) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   bool found;
 | |
|   T* prop = aFrame->GetProperty(aProperty, &found);
 | |
|   if (found) {
 | |
|     MOZ_ASSERT(prop, "this property should only store non-null values");
 | |
|     return prop;
 | |
|   }
 | |
|   prop = new T(aURI, aFrame, false);
 | |
|   NS_ADDREF(prop);
 | |
|   aFrame->AddProperty(aProperty, prop);
 | |
|   return prop;
 | |
| }
 | |
| 
 | |
| static SVGPaintingProperty* GetPaintingProperty(
 | |
|     URLAndReferrerInfo* aURI, nsIFrame* aFrame,
 | |
|     const FramePropertyDescriptor<SVGPaintingProperty>* aProperty) {
 | |
|   return GetEffectProperty(aURI, aFrame, aProperty);
 | |
| }
 | |
| 
 | |
| static already_AddRefed<URLAndReferrerInfo> GetMarkerURI(
 | |
|     nsIFrame* aFrame, const StyleUrlOrNone nsStyleSVG::*aMarker) {
 | |
|   const StyleUrlOrNone& url = aFrame->StyleSVG()->*aMarker;
 | |
|   if (url.IsNone()) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return ResolveURLUsingLocalRef(aFrame, url.AsUrl());
 | |
| }
 | |
| 
 | |
| bool SVGObserverUtils::GetAndObserveMarkers(nsIFrame* aMarkedFrame,
 | |
|                                             SVGMarkerFrame* (*aFrames)[3]) {
 | |
|   MOZ_ASSERT(!aMarkedFrame->GetPrevContinuation() &&
 | |
|                  aMarkedFrame->IsSVGGeometryFrame() &&
 | |
|                  static_cast<SVGGeometryElement*>(aMarkedFrame->GetContent())
 | |
|                      ->IsMarkable(),
 | |
|              "Bad frame");
 | |
| 
 | |
|   bool foundMarker = false;
 | |
|   RefPtr<URLAndReferrerInfo> markerURL;
 | |
|   SVGMarkerObserver* observer;
 | |
|   nsIFrame* marker;
 | |
| 
 | |
| #define GET_MARKER(type)                                                    \
 | |
|   markerURL = GetMarkerURI(aMarkedFrame, &nsStyleSVG::mMarker##type);       \
 | |
|   observer =                                                                \
 | |
|       GetEffectProperty(markerURL, aMarkedFrame, Marker##type##Property()); \
 | |
|   marker = observer ? observer->GetAndObserveReferencedFrame(               \
 | |
|                           LayoutFrameType::SVGMarker, nullptr)              \
 | |
|                     : nullptr;                                              \
 | |
|   foundMarker = foundMarker || bool(marker);                                \
 | |
|   (*aFrames)[SVGMark::e##type] = static_cast<SVGMarkerFrame*>(marker);
 | |
| 
 | |
|   GET_MARKER(Start)
 | |
|   GET_MARKER(Mid)
 | |
|   GET_MARKER(End)
 | |
| 
 | |
| #undef GET_MARKER
 | |
| 
 | |
|   return foundMarker;
 | |
| }
 | |
| 
 | |
| // Note that the returned list will be empty in the case of a 'filter' property
 | |
| // that only specifies CSS filter functions (no url()'s to SVG filters).
 | |
| template <typename P>
 | |
| static SVGFilterObserverListForCSSProp* GetOrCreateFilterObserverListForCSS(
 | |
|     nsIFrame* aFrame, bool aHasFilters,
 | |
|     FrameProperties::Descriptor<P> aProperty,
 | |
|     Span<const StyleFilter> aFilters) {
 | |
|   if (!aHasFilters) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   bool found;
 | |
|   SVGFilterObserverListForCSSProp* observers =
 | |
|       aFrame->GetProperty(aProperty, &found);
 | |
|   if (found) {
 | |
|     MOZ_ASSERT(observers, "this property should only store non-null values");
 | |
|     return observers;
 | |
|   }
 | |
|   observers = new SVGFilterObserverListForCSSProp(aFilters, aFrame);
 | |
|   NS_ADDREF(observers);
 | |
|   aFrame->AddProperty(aProperty, observers);
 | |
|   return observers;
 | |
| }
 | |
| 
 | |
| static SVGFilterObserverListForCSSProp* GetOrCreateFilterObserverListForCSS(
 | |
|     nsIFrame* aFrame, StyleFilterType aStyleFilterType) {
 | |
|   MOZ_ASSERT(!aFrame->GetPrevContinuation(), "Require first continuation");
 | |
| 
 | |
|   const nsStyleEffects* effects = aFrame->StyleEffects();
 | |
| 
 | |
|   return aStyleFilterType == StyleFilterType::BackdropFilter
 | |
|              ? GetOrCreateFilterObserverListForCSS(
 | |
|                    aFrame, effects->HasBackdropFilters(),
 | |
|                    BackdropFilterProperty(), effects->mBackdropFilters.AsSpan())
 | |
|              : GetOrCreateFilterObserverListForCSS(
 | |
|                    aFrame, effects->HasFilters(), FilterProperty(),
 | |
|                    effects->mFilters.AsSpan());
 | |
| }
 | |
| 
 | |
| static SVGObserverUtils::ReferenceState GetAndObserveFilters(
 | |
|     SVGFilterObserverList* aObserverList,
 | |
|     nsTArray<SVGFilterFrame*>* aFilterFrames) {
 | |
|   if (!aObserverList) {
 | |
|     return SVGObserverUtils::eHasNoRefs;
 | |
|   }
 | |
| 
 | |
|   const nsTArray<RefPtr<SVGFilterObserver>>& observers =
 | |
|       aObserverList->GetObservers();
 | |
|   if (observers.IsEmpty()) {
 | |
|     return SVGObserverUtils::eHasNoRefs;
 | |
|   }
 | |
| 
 | |
|   for (const auto& observer : observers) {
 | |
|     SVGFilterFrame* filter = observer->GetAndObserveFilterFrame();
 | |
|     if (!filter) {
 | |
|       if (aFilterFrames) {
 | |
|         aFilterFrames->Clear();
 | |
|       }
 | |
|       return SVGObserverUtils::eHasRefsSomeInvalid;
 | |
|     }
 | |
|     if (aFilterFrames) {
 | |
|       aFilterFrames->AppendElement(filter);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return SVGObserverUtils::eHasRefsAllValid;
 | |
| }
 | |
| 
 | |
| SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveFilters(
 | |
|     nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames,
 | |
|     StyleFilterType aStyleFilterType) {
 | |
|   SVGFilterObserverListForCSSProp* observerList =
 | |
|       GetOrCreateFilterObserverListForCSS(aFilteredFrame, aStyleFilterType);
 | |
|   return mozilla::GetAndObserveFilters(observerList, aFilterFrames);
 | |
| }
 | |
| 
 | |
| SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveFilters(
 | |
|     nsISupports* aObserverList, nsTArray<SVGFilterFrame*>* aFilterFrames) {
 | |
|   return mozilla::GetAndObserveFilters(
 | |
|       static_cast<SVGFilterObserverListForCanvasContext*>(aObserverList),
 | |
|       aFilterFrames);
 | |
| }
 | |
| 
 | |
| SVGObserverUtils::ReferenceState SVGObserverUtils::GetFiltersIfObserving(
 | |
|     nsIFrame* aFilteredFrame, nsTArray<SVGFilterFrame*>* aFilterFrames) {
 | |
|   SVGFilterObserverListForCSSProp* observerList =
 | |
|       aFilteredFrame->GetProperty(FilterProperty());
 | |
|   return mozilla::GetAndObserveFilters(observerList, aFilterFrames);
 | |
| }
 | |
| 
 | |
| already_AddRefed<nsISupports> SVGObserverUtils::ObserveFiltersForCanvasContext(
 | |
|     CanvasRenderingContext2D* aContext, Element* aCanvasElement,
 | |
|     const Span<const StyleFilter> aFilters) {
 | |
|   return do_AddRef(new SVGFilterObserverListForCanvasContext(
 | |
|       aContext, aCanvasElement, aFilters));
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::DetachFromCanvasContext(nsISupports* aAutoObserver) {
 | |
|   static_cast<SVGFilterObserverListForCanvasContext*>(aAutoObserver)
 | |
|       ->DetachFromContext();
 | |
| }
 | |
| 
 | |
| static SVGPaintingProperty* GetOrCreateClipPathObserver(
 | |
|     nsIFrame* aClippedFrame) {
 | |
|   MOZ_ASSERT(!aClippedFrame->GetPrevContinuation(),
 | |
|              "Require first continuation");
 | |
| 
 | |
|   const nsStyleSVGReset* svgStyleReset = aClippedFrame->StyleSVGReset();
 | |
|   if (!svgStyleReset->mClipPath.IsUrl()) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   const auto& url = svgStyleReset->mClipPath.AsUrl();
 | |
|   RefPtr<URLAndReferrerInfo> pathURI =
 | |
|       ResolveURLUsingLocalRef(aClippedFrame, url);
 | |
|   return GetPaintingProperty(pathURI, aClippedFrame, ClipPathProperty());
 | |
| }
 | |
| 
 | |
| SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveClipPath(
 | |
|     nsIFrame* aClippedFrame, SVGClipPathFrame** aClipPathFrame) {
 | |
|   if (aClipPathFrame) {
 | |
|     *aClipPathFrame = nullptr;
 | |
|   }
 | |
|   SVGPaintingProperty* observers = GetOrCreateClipPathObserver(aClippedFrame);
 | |
|   if (!observers) {
 | |
|     return eHasNoRefs;
 | |
|   }
 | |
|   bool frameTypeOK = true;
 | |
|   SVGClipPathFrame* frame =
 | |
|       static_cast<SVGClipPathFrame*>(observers->GetAndObserveReferencedFrame(
 | |
|           LayoutFrameType::SVGClipPath, &frameTypeOK));
 | |
|   // Note that, unlike for filters, a reference to an ID that doesn't exist
 | |
|   // is not invalid for clip-path or mask.
 | |
|   if (!frameTypeOK) {
 | |
|     return eHasRefsSomeInvalid;
 | |
|   }
 | |
|   if (aClipPathFrame) {
 | |
|     *aClipPathFrame = frame;
 | |
|   }
 | |
|   return frame ? eHasRefsAllValid : eHasNoRefs;
 | |
| }
 | |
| 
 | |
| static SVGRenderingObserverProperty* GetOrCreateGeometryObserver(
 | |
|     nsIFrame* aFrame) {
 | |
|   // Now only offset-path property uses this. See MotionPathUtils.cpp.
 | |
|   const nsStyleDisplay* disp = aFrame->StyleDisplay();
 | |
|   if (!disp->mOffsetPath.IsUrl()) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   const auto& url = disp->mOffsetPath.AsUrl();
 | |
|   RefPtr<URLAndReferrerInfo> pathURI = ResolveURLUsingLocalRef(aFrame, url);
 | |
|   return GetEffectProperty(pathURI, aFrame, OffsetPathProperty());
 | |
| }
 | |
| 
 | |
| SVGGeometryElement* SVGObserverUtils::GetAndObserveGeometry(nsIFrame* aFrame) {
 | |
|   SVGRenderingObserverProperty* observers = GetOrCreateGeometryObserver(aFrame);
 | |
|   if (!observers) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   bool frameTypeOK = true;
 | |
|   SVGGeometryFrame* frame =
 | |
|       do_QueryFrame(observers->GetAndObserveReferencedFrame(
 | |
|           LayoutFrameType::SVGGeometry, &frameTypeOK));
 | |
|   if (!frameTypeOK || !frame) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return static_cast<dom::SVGGeometryElement*>(frame->GetContent());
 | |
| }
 | |
| 
 | |
| static SVGMaskObserverList* GetOrCreateMaskObserverList(
 | |
|     nsIFrame* aMaskedFrame) {
 | |
|   MOZ_ASSERT(!aMaskedFrame->GetPrevContinuation(),
 | |
|              "Require first continuation");
 | |
| 
 | |
|   const nsStyleSVGReset* style = aMaskedFrame->StyleSVGReset();
 | |
|   if (!style->HasMask()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(style->mMask.mImageCount > 0);
 | |
| 
 | |
|   bool found;
 | |
|   SVGMaskObserverList* prop = aMaskedFrame->GetProperty(MaskProperty(), &found);
 | |
|   if (found) {
 | |
|     MOZ_ASSERT(prop, "this property should only store non-null values");
 | |
|     return prop;
 | |
|   }
 | |
|   prop = new SVGMaskObserverList(aMaskedFrame);
 | |
|   NS_ADDREF(prop);
 | |
|   aMaskedFrame->AddProperty(MaskProperty(), prop);
 | |
|   return prop;
 | |
| }
 | |
| 
 | |
| SVGObserverUtils::ReferenceState SVGObserverUtils::GetAndObserveMasks(
 | |
|     nsIFrame* aMaskedFrame, nsTArray<SVGMaskFrame*>* aMaskFrames) {
 | |
|   SVGMaskObserverList* observerList = GetOrCreateMaskObserverList(aMaskedFrame);
 | |
|   if (!observerList) {
 | |
|     return eHasNoRefs;
 | |
|   }
 | |
| 
 | |
|   const nsTArray<RefPtr<SVGPaintingProperty>>& observers =
 | |
|       observerList->GetObservers();
 | |
|   if (observers.IsEmpty()) {
 | |
|     return eHasNoRefs;
 | |
|   }
 | |
| 
 | |
|   ReferenceState state = eHasRefsAllValid;
 | |
| 
 | |
|   for (size_t i = 0; i < observers.Length(); i++) {
 | |
|     bool frameTypeOK = true;
 | |
|     SVGMaskFrame* maskFrame =
 | |
|         static_cast<SVGMaskFrame*>(observers[i]->GetAndObserveReferencedFrame(
 | |
|             LayoutFrameType::SVGMask, &frameTypeOK));
 | |
|     MOZ_ASSERT(!maskFrame || frameTypeOK);
 | |
|     // XXXjwatt: this looks fishy
 | |
|     if (!frameTypeOK) {
 | |
|       // 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.
 | |
|       observerList->ResolveImage(i);
 | |
|       state = eHasRefsSomeInvalid;
 | |
|     }
 | |
|     if (aMaskFrames) {
 | |
|       aMaskFrames->AppendElement(maskFrame);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return state;
 | |
| }
 | |
| 
 | |
| SVGGeometryElement* SVGObserverUtils::GetAndObserveTextPathsPath(
 | |
|     nsIFrame* aTextPathFrame) {
 | |
|   // Continuations can come and go during reflow, and we don't need to observe
 | |
|   // the referenced element more than once for a given node.
 | |
|   aTextPathFrame = aTextPathFrame->FirstContinuation();
 | |
| 
 | |
|   SVGTextPathObserver* property =
 | |
|       aTextPathFrame->GetProperty(HrefAsTextPathProperty());
 | |
| 
 | |
|   if (!property) {
 | |
|     nsIContent* content = aTextPathFrame->GetContent();
 | |
|     nsAutoString href;
 | |
|     static_cast<SVGTextPathElement*>(content)->HrefAsString(href);
 | |
|     if (href.IsEmpty()) {
 | |
|       return nullptr;  // no URL
 | |
|     }
 | |
| 
 | |
|     RefPtr<URLAndReferrerInfo> target = ResolveURLUsingLocalRef(content, href);
 | |
| 
 | |
|     property =
 | |
|         GetEffectProperty(target, aTextPathFrame, HrefAsTextPathProperty());
 | |
|     if (!property) {
 | |
|       return nullptr;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return SVGGeometryElement::FromNodeOrNull(
 | |
|       property->GetAndObserveReferencedElement());
 | |
| }
 | |
| 
 | |
| SVGGeometryElement* SVGObserverUtils::GetAndObserveMPathsPath(
 | |
|     SVGMPathElement* aSVGMPathElement) {
 | |
|   if (!aSVGMPathElement->mMPathObserver) {
 | |
|     nsAutoString href;
 | |
|     aSVGMPathElement->HrefAsString(href);
 | |
|     if (href.IsEmpty()) {
 | |
|       return nullptr;  // no URL
 | |
|     }
 | |
| 
 | |
|     RefPtr<URLAndReferrerInfo> target =
 | |
|         ResolveURLUsingLocalRef(aSVGMPathElement, href);
 | |
| 
 | |
|     aSVGMPathElement->mMPathObserver =
 | |
|         new SVGMPathObserver(target, aSVGMPathElement);
 | |
|   }
 | |
| 
 | |
|   return SVGGeometryElement::FromNodeOrNull(
 | |
|       static_cast<SVGMPathObserver*>(aSVGMPathElement->mMPathObserver.get())
 | |
|           ->GetAndObserveReferencedElement());
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::TraverseMPathObserver(
 | |
|     SVGMPathElement* aSVGMPathElement,
 | |
|     nsCycleCollectionTraversalCallback* aCB) {
 | |
|   if (aSVGMPathElement->mMPathObserver) {
 | |
|     static_cast<SVGMPathObserver*>(aSVGMPathElement->mMPathObserver.get())
 | |
|         ->Traverse(aCB);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::InitiateResourceDocLoads(nsIFrame* aFrame) {
 | |
|   // We create observer objects and attach them to aFrame, but we do not
 | |
|   // make aFrame start observing the referenced frames.
 | |
|   Unused << GetOrCreateFilterObserverListForCSS(
 | |
|       aFrame, StyleFilterType::BackdropFilter);
 | |
|   Unused << GetOrCreateFilterObserverListForCSS(aFrame,
 | |
|                                                 StyleFilterType::Filter);
 | |
|   Unused << GetOrCreateClipPathObserver(aFrame);
 | |
|   Unused << GetOrCreateGeometryObserver(aFrame);
 | |
|   Unused << GetOrCreateMaskObserverList(aFrame);
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::RemoveTextPathObserver(nsIFrame* aTextPathFrame) {
 | |
|   aTextPathFrame->RemoveProperty(HrefAsTextPathProperty());
 | |
| }
 | |
| 
 | |
| nsIFrame* SVGObserverUtils::GetAndObserveTemplate(
 | |
|     nsIFrame* aFrame, HrefToTemplateCallback aGetHref) {
 | |
|   SVGTemplateElementObserver* observer =
 | |
|       aFrame->GetProperty(HrefToTemplateProperty());
 | |
| 
 | |
|   if (!observer) {
 | |
|     nsAutoString href;
 | |
|     aGetHref(href);
 | |
|     if (href.IsEmpty()) {
 | |
|       return nullptr;  // no URL
 | |
|     }
 | |
| 
 | |
|     RefPtr<URLAndReferrerInfo> info =
 | |
|         ResolveURLUsingLocalRef(aFrame->GetContent(), href);
 | |
| 
 | |
|     observer = GetEffectProperty(info, aFrame, HrefToTemplateProperty());
 | |
|   }
 | |
| 
 | |
|   return observer ? observer->GetAndObserveReferencedFrame() : nullptr;
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::RemoveTemplateObserver(nsIFrame* aFrame) {
 | |
|   aFrame->RemoveProperty(HrefToTemplateProperty());
 | |
| }
 | |
| 
 | |
| Element* SVGObserverUtils::GetAndObserveBackgroundImage(nsIFrame* aFrame,
 | |
|                                                         const nsAtom* aHref) {
 | |
|   bool found;
 | |
|   URIObserverHashtable* hashtable =
 | |
|       aFrame->GetProperty(BackgroundImageProperty(), &found);
 | |
|   if (!found) {
 | |
|     hashtable = new URIObserverHashtable();
 | |
|     aFrame->AddProperty(BackgroundImageProperty(), hashtable);
 | |
|   } else {
 | |
|     MOZ_ASSERT(hashtable, "this property should only store non-null values");
 | |
|   }
 | |
| 
 | |
|   nsAutoString elementId = u"#"_ns + nsDependentAtomString(aHref);
 | |
|   nsCOMPtr<nsIURI> targetURI;
 | |
|   nsContentUtils::NewURIWithDocumentCharset(
 | |
|       getter_AddRefs(targetURI), elementId,
 | |
|       aFrame->GetContent()->GetUncomposedDoc(),
 | |
|       aFrame->GetContent()->GetBaseURI());
 | |
|   nsIReferrerInfo* referrerInfo =
 | |
|       aFrame->GetContent()
 | |
|           ->OwnerDoc()
 | |
|           ->ReferrerInfoForInternalCSSAndSVGResources();
 | |
|   RefPtr<URLAndReferrerInfo> url =
 | |
|       new URLAndReferrerInfo(targetURI, referrerInfo);
 | |
| 
 | |
|   return static_cast<SVGMozElementObserver*>(
 | |
|              hashtable
 | |
|                  ->LookupOrInsertWith(
 | |
|                      url,
 | |
|                      [&] {
 | |
|                        return MakeRefPtr<SVGMozElementObserver>(url, aFrame);
 | |
|                      })
 | |
|                  .get())
 | |
|       ->GetAndObserveReferencedElement();
 | |
| }
 | |
| 
 | |
| Element* SVGObserverUtils::GetAndObserveBackgroundClip(nsIFrame* aFrame) {
 | |
|   bool found;
 | |
|   BackgroundClipRenderingObserver* obs =
 | |
|       aFrame->GetProperty(BackgroundClipObserverProperty(), &found);
 | |
|   if (!found) {
 | |
|     obs = new BackgroundClipRenderingObserver(aFrame);
 | |
|     NS_ADDREF(obs);
 | |
|     aFrame->AddProperty(BackgroundClipObserverProperty(), obs);
 | |
|   }
 | |
| 
 | |
|   return obs->GetAndObserveReferencedElement();
 | |
| }
 | |
| 
 | |
| SVGPaintServerFrame* SVGObserverUtils::GetAndObservePaintServer(
 | |
|     nsIFrame* aPaintedFrame, StyleSVGPaint nsStyleSVG::*aPaint) {
 | |
|   // 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* paintedFrame = aPaintedFrame;
 | |
|   if (paintedFrame->IsInSVGTextSubtree()) {
 | |
|     paintedFrame = paintedFrame->GetParent();
 | |
|     nsIFrame* grandparent = paintedFrame->GetParent();
 | |
|     if (grandparent && grandparent->IsSVGTextFrame()) {
 | |
|       paintedFrame = grandparent;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const nsStyleSVG* svgStyle = paintedFrame->StyleSVG();
 | |
|   if (!(svgStyle->*aPaint).kind.IsPaintServer()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   RefPtr<URLAndReferrerInfo> paintServerURL = ResolveURLUsingLocalRef(
 | |
|       paintedFrame, (svgStyle->*aPaint).kind.AsPaintServer());
 | |
| 
 | |
|   MOZ_ASSERT(aPaint == &nsStyleSVG::mFill || aPaint == &nsStyleSVG::mStroke);
 | |
|   PaintingPropertyDescriptor propDesc =
 | |
|       (aPaint == &nsStyleSVG::mFill) ? FillProperty() : StrokeProperty();
 | |
|   if (auto* property =
 | |
|           GetPaintingProperty(paintServerURL, paintedFrame, propDesc)) {
 | |
|     return do_QueryFrame(property->GetAndObserveReferencedFrame());
 | |
|   }
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::UpdateEffects(nsIFrame* aFrame) {
 | |
|   NS_ASSERTION(aFrame->GetContent()->IsElement(),
 | |
|                "aFrame's content should be an element");
 | |
| 
 | |
|   aFrame->RemoveProperty(BackdropFilterProperty());
 | |
|   aFrame->RemoveProperty(FilterProperty());
 | |
|   aFrame->RemoveProperty(MaskProperty());
 | |
|   aFrame->RemoveProperty(ClipPathProperty());
 | |
|   aFrame->RemoveProperty(MarkerStartProperty());
 | |
|   aFrame->RemoveProperty(MarkerMidProperty());
 | |
|   aFrame->RemoveProperty(MarkerEndProperty());
 | |
|   aFrame->RemoveProperty(FillProperty());
 | |
|   aFrame->RemoveProperty(StrokeProperty());
 | |
|   aFrame->RemoveProperty(BackgroundImageProperty());
 | |
| 
 | |
|   // Ensure that the filter is repainted correctly
 | |
|   // We can't do that in OnRenderingChange as the referenced frame may
 | |
|   // not be valid
 | |
|   GetOrCreateFilterObserverListForCSS(aFrame, StyleFilterType::BackdropFilter);
 | |
|   GetOrCreateFilterObserverListForCSS(aFrame, StyleFilterType::Filter);
 | |
| 
 | |
|   if (aFrame->IsSVGGeometryFrame() &&
 | |
|       static_cast<SVGGeometryElement*>(aFrame->GetContent())->IsMarkable()) {
 | |
|     // Set marker properties here to avoid reference loops
 | |
|     RefPtr<URLAndReferrerInfo> markerURL =
 | |
|         GetMarkerURI(aFrame, &nsStyleSVG::mMarkerStart);
 | |
|     GetEffectProperty(markerURL, aFrame, MarkerStartProperty());
 | |
|     markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerMid);
 | |
|     GetEffectProperty(markerURL, aFrame, MarkerMidProperty());
 | |
|     markerURL = GetMarkerURI(aFrame, &nsStyleSVG::mMarkerEnd);
 | |
|     GetEffectProperty(markerURL, aFrame, MarkerEndProperty());
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::AddRenderingObserver(Element* aElement,
 | |
|                                             SVGRenderingObserver* aObserver) {
 | |
|   SVGRenderingObserverSet* observers = GetObserverSet(aElement);
 | |
|   if (!observers) {
 | |
|     observers = new SVGRenderingObserverSet();
 | |
|     // When we call cloneAndAdopt we keep the property. If the referenced
 | |
|     // element doesn't exist in the new document then the observer set and
 | |
|     // observers will be removed by ElementTracker::ElementChanged when we
 | |
|     // get the ChangeNotification.
 | |
|     aElement->SetProperty(nsGkAtoms::renderingobserverset, observers,
 | |
|                           nsINode::DeleteProperty<SVGRenderingObserverSet>,
 | |
|                           /* aTransfer = */ true);
 | |
|   }
 | |
|   aElement->SetHasRenderingObservers(true);
 | |
|   observers->Add(aObserver);
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::RemoveRenderingObserver(
 | |
|     Element* aElement, SVGRenderingObserver* aObserver) {
 | |
|   SVGRenderingObserverSet* observers = GetObserverSet(aElement);
 | |
|   if (observers) {
 | |
|     NS_ASSERTION(observers->Contains(aObserver),
 | |
|                  "removing observer from an element we're not observing?");
 | |
|     observers->Remove(aObserver);
 | |
|     if (observers->IsEmpty()) {
 | |
|       aElement->RemoveProperty(nsGkAtoms::renderingobserverset);
 | |
|       aElement->SetHasRenderingObservers(false);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::RemoveAllRenderingObservers(Element* aElement) {
 | |
|   SVGRenderingObserverSet* observers = GetObserverSet(aElement);
 | |
|   if (observers) {
 | |
|     observers->RemoveAll();
 | |
|     aElement->RemoveProperty(nsGkAtoms::renderingobserverset);
 | |
|     aElement->SetHasRenderingObservers(false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::InvalidateRenderingObservers(nsIFrame* aFrame) {
 | |
|   NS_ASSERTION(!aFrame->GetPrevContinuation(),
 | |
|                "aFrame must be first continuation");
 | |
| 
 | |
|   auto* element = Element::FromNodeOrNull(aFrame->GetContent());
 | |
|   if (!element) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If the rendering has changed, the bounds may well have changed too:
 | |
|   aFrame->RemoveProperty(SVGUtils::ObjectBoundingBoxProperty());
 | |
| 
 | |
|   if (auto* observers = GetObserverSet(element)) {
 | |
|     observers->InvalidateAll();
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aFrame->IsRenderingObserverContainer()) {
 | |
|     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->IsSVGContainerFrame();
 | |
|        f = f->GetParent()) {
 | |
|     if (auto* element = Element::FromNode(f->GetContent())) {
 | |
|       if (auto* observers = GetObserverSet(element)) {
 | |
|         observers->InvalidateAll();
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|     if (f->IsRenderingObserverContainer()) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::InvalidateDirectRenderingObservers(
 | |
|     Element* aElement, uint32_t aFlags /* = 0 */) {
 | |
|   if (nsIFrame* frame = aElement->GetPrimaryFrame()) {
 | |
|     // If the rendering has changed, the bounds may well have changed too:
 | |
|     frame->RemoveProperty(SVGUtils::ObjectBoundingBoxProperty());
 | |
|   }
 | |
| 
 | |
|   if (aElement->HasRenderingObservers()) {
 | |
|     SVGRenderingObserverSet* observers = GetObserverSet(aElement);
 | |
|     if (observers) {
 | |
|       if (aFlags & INVALIDATE_REFLOW) {
 | |
|         observers->InvalidateAllForReflow();
 | |
|       } else {
 | |
|         observers->InvalidateAll();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void SVGObserverUtils::InvalidateDirectRenderingObservers(
 | |
|     nsIFrame* aFrame, uint32_t aFlags /* = 0 */) {
 | |
|   if (auto* element = Element::FromNodeOrNull(aFrame->GetContent())) {
 | |
|     InvalidateDirectRenderingObservers(element, aFlags);
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 | 
