IDTracker.h (7713B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_dom_IDTracker_h_ 8 #define mozilla_dom_IDTracker_h_ 9 10 #include "nsIObserver.h" 11 #include "nsThreadUtils.h" 12 13 class nsAtom; 14 class nsIContent; 15 class nsINode; 16 class nsIURI; 17 class nsIReferrerInfo; 18 19 namespace mozilla::dom { 20 21 class Document; 22 class DocumentOrShadowRoot; 23 class Element; 24 25 /** 26 * Class to track what element is referenced by a given ID. 27 * 28 * To use it, call one of the Reset methods to set it up to watch a given ID. 29 * Call get() anytime to determine the referenced element (which may be null if 30 * the element isn't found). When the element changes, ElementChanged 31 * will be called, so subclass this class if you want to receive that 32 * notification. ElementChanged runs at safe-for-script time, i.e. outside 33 * of the content update. Call Unlink() if you want to stop watching 34 * for changes (get() will then return null). 35 * 36 * By default this is a single-shot tracker --- i.e., when ElementChanged 37 * fires, we will automatically stop tracking. get() will continue to return 38 * the changed-to element. 39 * Override IsPersistent to return true if you want to keep tracking after 40 * the first change. 41 */ 42 class IDTracker { 43 public: 44 using Element = mozilla::dom::Element; 45 46 IDTracker(); 47 48 ~IDTracker(); 49 50 /** 51 * Find which element, if any, is referenced. 52 */ 53 Element* get() const { return mElement; } 54 55 /** 56 * Set up a reference to another element, identified by the fragment 57 * identifier in aURI. If aURI identifies an element in a document that is 58 * not aFrom's document, then an ExternalResourceLoad object will be created 59 * to load and store that document in the background as a resource document 60 * (until we, and any other observers, no longer observe it). 61 * 62 * This can be called multiple times with different URIs to change which 63 * element is being tracked, but these changes do not trigger ElementChanged. 64 * 65 * @param aFrom The source element that has made the reference to aURI. 66 * @param aURI A URI containing a fragment identifier that identifies the 67 * target element. 68 * @param aReferrerInfo The referrerInfo for the source element. Needed if 69 * the referenced element is in an external resource document. 70 * @param aReferenceImage Whether the reference comes from a -moz-element 71 * property (that is, we're creating a reference an "image element", which 72 * is subject to the document's mozSetImageElement overriding mechanism). 73 */ 74 void ResetToURIWithFragmentID(Element& aFrom, nsIURI* aURI, 75 nsIReferrerInfo* aReferrerInfo, 76 bool aReferenceImage = false); 77 78 /** 79 * A variation on ResetToURIWithFragmentID() to set up a reference that 80 * consists only of a fragment identifier, referencing an element in the same 81 * document as aFrom. 82 * 83 * @param aFrom The source element that is making the reference. 84 * @param aLocalRef The fragment identifier that identifies the target 85 * element. Must begin with "#". 86 * @param aBaseURI The URI this url was specified from. Only used to determine 87 * whether we need to reference the source resource document. 88 * @param aReferrerInfo The referrerInfo for the source element. Needed if 89 * the referenced element is in an external resource document. 90 * @param aReferenceImage See above. 91 */ 92 void ResetToLocalFragmentID(Element& aFrom, const nsAString& aLocalRef, 93 nsIURI* aBaseURI = nullptr, 94 nsIReferrerInfo* aReferrerInfo = nullptr, 95 bool aReferenceImage = false); 96 97 /** 98 * A variation on ResetToURIWithFragmentID() to set up a reference that 99 * consists of a pre-parsed ID, referencing an element in the same document 100 * as aFrom. 101 * 102 * @param aFrom The source element that is making the reference. 103 * @param aID The ID of the target element. 104 * @param aReferenceImage See above. 105 */ 106 void ResetToID(Element& aFrom, nsAtom* aID, bool aReferenceImage = false); 107 108 /** 109 * Clears the reference. ElementChanged is not triggered. get() will return 110 * null. 111 */ 112 void Unlink(); 113 114 void Traverse(nsCycleCollectionTraversalCallback* aCB); 115 116 protected: 117 /** Requests and maybe watches an external resource doc. */ 118 void ResetToExternalResource(nsIURI* aURI, nsIReferrerInfo* aReferrerInfo, 119 nsAtom* aRef, Element& aFrom, 120 bool aReferenceImage); 121 122 /** 123 * Override this to be notified of element changes. Don't forget 124 * to call this superclass method to change mElement. This is called 125 * at script-runnable time. 126 */ 127 virtual void ElementChanged(Element* aFrom, Element* aTo); 128 129 /** 130 * Override this to convert from a single-shot notification to 131 * a persistent notification. 132 */ 133 virtual bool IsPersistent() { return false; } 134 135 /** 136 * Set ourselves up with our new document. Note that aDocument might be 137 * null. Either aWatch must be false or aRef must be empty. 138 */ 139 void HaveNewDocumentOrShadowRoot(DocumentOrShadowRoot*, bool aWatch, 140 nsAtom* aID); 141 142 private: 143 static bool Observe(Element* aOldElement, Element* aNewElement, void* aData); 144 145 class Notification : public nsISupports { 146 public: 147 virtual void SetTo(Element* aTo) = 0; 148 virtual void Clear() { mTarget = nullptr; } 149 virtual ~Notification() = default; 150 151 protected: 152 explicit Notification(IDTracker* aTarget) : mTarget(aTarget) { 153 MOZ_ASSERT(aTarget, "Must have a target"); 154 } 155 IDTracker* mTarget; 156 }; 157 158 class ChangeNotification : public mozilla::Runnable, public Notification { 159 public: 160 ChangeNotification(IDTracker* aTarget, Element* aFrom, Element* aTo); 161 162 // We need to actually declare all of nsISupports, because 163 // Notification inherits from it but doesn't declare it. 164 NS_DECL_ISUPPORTS_INHERITED 165 NS_IMETHOD Run() override { 166 if (mTarget) { 167 mTarget->mPendingNotification = nullptr; 168 mTarget->ElementChanged(mFrom, mTo); 169 } 170 return NS_OK; 171 } 172 void SetTo(Element* aTo) override; 173 void Clear() override; 174 175 protected: 176 virtual ~ChangeNotification(); 177 178 RefPtr<Element> mFrom; 179 RefPtr<Element> mTo; 180 }; 181 friend class ChangeNotification; 182 183 class DocumentLoadNotification : public Notification, public nsIObserver { 184 public: 185 DocumentLoadNotification(IDTracker* aTarget, nsAtom* aRef) 186 : Notification(aTarget) { 187 if (!mTarget->IsPersistent()) { 188 mRef = aRef; 189 } 190 } 191 192 NS_DECL_ISUPPORTS 193 NS_DECL_NSIOBSERVER 194 private: 195 virtual ~DocumentLoadNotification() = default; 196 197 virtual void SetTo(Element* aTo) override {} 198 199 RefPtr<nsAtom> mRef; 200 }; 201 friend class DocumentLoadNotification; 202 203 DocumentOrShadowRoot* GetWatchDocOrShadowRoot() const; 204 205 RefPtr<nsAtom> mWatchID; 206 nsCOMPtr<nsINode> 207 mWatchDocumentOrShadowRoot; // Always a `DocumentOrShadowRoot`. 208 RefPtr<Element> mElement; 209 RefPtr<Notification> mPendingNotification; 210 bool mReferencingImage = false; 211 }; 212 213 inline void ImplCycleCollectionUnlink(IDTracker& aField) { aField.Unlink(); } 214 215 inline void ImplCycleCollectionTraverse( 216 nsCycleCollectionTraversalCallback& aCallback, IDTracker& aField, 217 const char* aName, uint32_t aFlags = 0) { 218 aField.Traverse(&aCallback); 219 } 220 221 } // namespace mozilla::dom 222 223 #endif /* mozilla_dom_IDTracker_h_ */