michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ 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 NSREFERENCEDELEMENT_H_ michael@0: #define NSREFERENCEDELEMENT_H_ michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsAutoPtr.h" michael@0: michael@0: class nsIURI; michael@0: class nsCycleCollectionCallback; michael@0: michael@0: /** michael@0: * Class to track what element is referenced by a given ID. michael@0: * michael@0: * To use it, call Reset() to set it up to watch a given URI. Call get() michael@0: * anytime to determine the referenced element (which may be null if michael@0: * the element isn't found). When the element changes, ElementChanged michael@0: * will be called, so subclass this class if you want to receive that michael@0: * notification. ElementChanged runs at safe-for-script time, i.e. outside michael@0: * of the content update. Call Unlink() if you want to stop watching michael@0: * for changes (get() will then return null). michael@0: * michael@0: * By default this is a single-shot tracker --- i.e., when ElementChanged michael@0: * fires, we will automatically stop tracking. get() will continue to return michael@0: * the changed-to element. michael@0: * Override IsPersistent to return true if you want to keep tracking after michael@0: * the first change. michael@0: */ michael@0: class nsReferencedElement { michael@0: public: michael@0: typedef mozilla::dom::Element Element; michael@0: michael@0: nsReferencedElement() {} michael@0: ~nsReferencedElement() { michael@0: Unlink(); michael@0: } michael@0: michael@0: /** michael@0: * Find which element, if any, is referenced. michael@0: */ michael@0: Element* get() { return mElement; } michael@0: michael@0: /** michael@0: * Set up the reference. This can be called multiple times to michael@0: * change which reference is being tracked, but these changes michael@0: * do not trigger ElementChanged. michael@0: * @param aFrom the source element for context michael@0: * @param aURI the URI containing a hash-reference to the element michael@0: * @param aWatch if false, then we do not set up the notifications to track michael@0: * changes, so ElementChanged won't fire and get() will always return the same michael@0: * value, the current element for the ID. michael@0: * @param aReferenceImage whether the ID references image elements which are michael@0: * subject to the document's mozSetImageElement overriding mechanism. michael@0: */ michael@0: void Reset(nsIContent* aFrom, nsIURI* aURI, bool aWatch = true, michael@0: bool aReferenceImage = false); michael@0: michael@0: /** michael@0: * A variation on Reset() to set up a reference that consists of the ID of michael@0: * an element in the same document as aFrom. michael@0: * @param aFrom the source element for context michael@0: * @param aID the ID of the element michael@0: * @param aWatch if false, then we do not set up the notifications to track michael@0: * changes, so ElementChanged won't fire and get() will always return the same michael@0: * value, the current element for the ID. michael@0: */ michael@0: void ResetWithID(nsIContent* aFrom, const nsString& aID, michael@0: bool aWatch = true); michael@0: michael@0: /** michael@0: * Clears the reference. ElementChanged is not triggered. get() will return michael@0: * null. michael@0: */ michael@0: void Unlink(); michael@0: michael@0: void Traverse(nsCycleCollectionTraversalCallback* aCB); michael@0: michael@0: protected: michael@0: /** michael@0: * Override this to be notified of element changes. Don't forget michael@0: * to call this superclass method to change mElement. This is called michael@0: * at script-runnable time. michael@0: */ michael@0: virtual void ElementChanged(Element* aFrom, Element* aTo) { michael@0: mElement = aTo; michael@0: } michael@0: michael@0: /** michael@0: * Override this to convert from a single-shot notification to michael@0: * a persistent notification. michael@0: */ michael@0: virtual bool IsPersistent() { return false; } michael@0: michael@0: /** michael@0: * Set ourselves up with our new document. Note that aDocument might be michael@0: * null. Either aWatch must be false or aRef must be empty. michael@0: */ michael@0: void HaveNewDocument(nsIDocument* aDocument, bool aWatch, michael@0: const nsString& aRef); michael@0: michael@0: private: michael@0: static bool Observe(Element* aOldElement, michael@0: Element* aNewElement, void* aData); michael@0: michael@0: class Notification : public nsISupports { michael@0: public: michael@0: virtual void SetTo(Element* aTo) = 0; michael@0: virtual void Clear() { mTarget = nullptr; } michael@0: virtual ~Notification() {} michael@0: protected: michael@0: Notification(nsReferencedElement* aTarget) michael@0: : mTarget(aTarget) michael@0: { michael@0: NS_PRECONDITION(aTarget, "Must have a target"); michael@0: } michael@0: nsReferencedElement* mTarget; michael@0: }; michael@0: michael@0: class ChangeNotification : public nsRunnable, michael@0: public Notification michael@0: { michael@0: public: michael@0: ChangeNotification(nsReferencedElement* aTarget, michael@0: Element* aFrom, Element* aTo) michael@0: : Notification(aTarget), mFrom(aFrom), mTo(aTo) michael@0: {} michael@0: virtual ~ChangeNotification() {} michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: NS_IMETHOD Run() MOZ_OVERRIDE { michael@0: if (mTarget) { michael@0: mTarget->mPendingNotification = nullptr; michael@0: mTarget->ElementChanged(mFrom, mTo); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: virtual void SetTo(Element* aTo) { mTo = aTo; } michael@0: virtual void Clear() michael@0: { michael@0: Notification::Clear(); mFrom = nullptr; mTo = nullptr; michael@0: } michael@0: protected: michael@0: nsRefPtr mFrom; michael@0: nsRefPtr mTo; michael@0: }; michael@0: friend class ChangeNotification; michael@0: michael@0: class DocumentLoadNotification : public Notification, michael@0: public nsIObserver michael@0: { michael@0: public: michael@0: DocumentLoadNotification(nsReferencedElement* aTarget, michael@0: const nsString& aRef) : michael@0: Notification(aTarget) michael@0: { michael@0: if (!mTarget->IsPersistent()) { michael@0: mRef = aRef; michael@0: } michael@0: } michael@0: virtual ~DocumentLoadNotification() {} michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: private: michael@0: virtual void SetTo(Element* aTo) { } michael@0: michael@0: nsString mRef; michael@0: }; michael@0: friend class DocumentLoadNotification; michael@0: michael@0: nsCOMPtr mWatchID; michael@0: nsCOMPtr mWatchDocument; michael@0: nsRefPtr mElement; michael@0: nsRefPtr mPendingNotification; michael@0: bool mReferencingImage; michael@0: }; michael@0: michael@0: #endif /*NSREFERENCEDELEMENT_H_*/