diff -r 000000000000 -r 6474c204b198 content/base/src/nsDOMMutationObserver.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/content/base/src/nsDOMMutationObserver.h Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,606 @@ +/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*- */ +/* vim: set sw=4 ts=8 et 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/. */ + +#ifndef nsDOMMutationObserver_h +#define nsDOMMutationObserver_h + +#include "mozilla/Attributes.h" +#include "nsCycleCollectionParticipant.h" +#include "nsPIDOMWindow.h" +#include "nsIScriptContext.h" +#include "nsStubMutationObserver.h" +#include "nsCOMArray.h" +#include "nsTArray.h" +#include "nsAutoPtr.h" +#include "nsIVariant.h" +#include "nsContentList.h" +#include "mozilla/dom/Element.h" +#include "nsClassHashtable.h" +#include "nsNodeUtils.h" +#include "nsIDOMMutationEvent.h" +#include "nsWrapperCache.h" +#include "mozilla/dom/MutationObserverBinding.h" +#include "nsIDocument.h" + +class nsDOMMutationObserver; +using mozilla::dom::MutationObservingInfo; + +class nsDOMMutationRecord : public nsISupports, + public nsWrapperCache +{ +public: + nsDOMMutationRecord(nsIAtom* aType, nsISupports* aOwner) + : mType(aType), mAttrNamespace(NullString()), mPrevValue(NullString()), mOwner(aOwner) + { + SetIsDOMBinding(); + } + virtual ~nsDOMMutationRecord() {} + + nsISupports* GetParentObject() const + { + return mOwner; + } + + virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE + { + return mozilla::dom::MutationRecordBinding::Wrap(aCx, this); + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationRecord) + + void GetType(mozilla::dom::DOMString& aRetVal) const + { + aRetVal.SetOwnedAtom(mType, mozilla::dom::DOMString::eNullNotExpected); + } + + nsINode* GetTarget() const + { + return mTarget; + } + + nsINodeList* AddedNodes(); + + nsINodeList* RemovedNodes(); + + nsINode* GetPreviousSibling() const + { + return mPreviousSibling; + } + + nsINode* GetNextSibling() const + { + return mNextSibling; + } + + void GetAttributeName(mozilla::dom::DOMString& aRetVal) const + { + aRetVal.SetOwnedAtom(mAttrName, mozilla::dom::DOMString::eTreatNullAsNull); + } + + void GetAttributeNamespace(mozilla::dom::DOMString& aRetVal) const + { + aRetVal.SetOwnedString(mAttrNamespace); + } + + void GetOldValue(mozilla::dom::DOMString& aRetVal) const + { + aRetVal.SetOwnedString(mPrevValue); + } + + nsCOMPtr mTarget; + nsCOMPtr mType; + nsCOMPtr mAttrName; + nsString mAttrNamespace; + nsString mPrevValue; + nsRefPtr mAddedNodes; + nsRefPtr mRemovedNodes; + nsCOMPtr mPreviousSibling; + nsCOMPtr mNextSibling; + + nsRefPtr mNext; + nsCOMPtr mOwner; +}; + +// Base class just prevents direct access to +// members to make sure we go through getters/setters. +class nsMutationReceiverBase : public nsStubMutationObserver +{ +public: + virtual ~nsMutationReceiverBase() { } + + nsDOMMutationObserver* Observer(); + nsINode* Target() { return mParent ? mParent->Target() : mTarget; } + nsINode* RegisterTarget() { return mRegisterTarget; } + + bool Subtree() { return mParent ? mParent->Subtree() : mSubtree; } + void SetSubtree(bool aSubtree) + { + NS_ASSERTION(!mParent, "Shouldn't have parent"); + mSubtree = aSubtree; + } + + bool ChildList() { return mParent ? mParent->ChildList() : mChildList; } + void SetChildList(bool aChildList) + { + NS_ASSERTION(!mParent, "Shouldn't have parent"); + mChildList = aChildList; + } + + bool CharacterData() + { + return mParent ? mParent->CharacterData() : mCharacterData; + } + void SetCharacterData(bool aCharacterData) + { + NS_ASSERTION(!mParent, "Shouldn't have parent"); + mCharacterData = aCharacterData; + } + + bool CharacterDataOldValue() + { + return mParent ? mParent->CharacterDataOldValue() : mCharacterDataOldValue; + } + void SetCharacterDataOldValue(bool aOldValue) + { + NS_ASSERTION(!mParent, "Shouldn't have parent"); + mCharacterDataOldValue = aOldValue; + } + + bool Attributes() { return mParent ? mParent->Attributes() : mAttributes; } + void SetAttributes(bool aAttributes) + { + NS_ASSERTION(!mParent, "Shouldn't have parent"); + mAttributes = aAttributes; + } + + bool AllAttributes() + { + return mParent ? mParent->AllAttributes() + : mAllAttributes; + } + void SetAllAttributes(bool aAll) + { + NS_ASSERTION(!mParent, "Shouldn't have parent"); + mAllAttributes = aAll; + } + + bool AttributeOldValue() { + return mParent ? mParent->AttributeOldValue() + : mAttributeOldValue; + } + void SetAttributeOldValue(bool aOldValue) + { + NS_ASSERTION(!mParent, "Shouldn't have parent"); + mAttributeOldValue = aOldValue; + } + + nsCOMArray& AttributeFilter() { return mAttributeFilter; } + void SetAttributeFilter(nsCOMArray& aFilter) + { + NS_ASSERTION(!mParent, "Shouldn't have parent"); + mAttributeFilter.Clear(); + mAttributeFilter.AppendObjects(aFilter); + } + + void AddClone(nsMutationReceiverBase* aClone) + { + mTransientReceivers.AppendObject(aClone); + } + + void RemoveClone(nsMutationReceiverBase* aClone) + { + mTransientReceivers.RemoveObject(aClone); + } + +protected: + nsMutationReceiverBase(nsINode* aTarget, nsDOMMutationObserver* aObserver) + : mTarget(aTarget), mObserver(aObserver), mRegisterTarget(aTarget) + { + mRegisterTarget->AddMutationObserver(this); + mRegisterTarget->SetMayHaveDOMMutationObserver(); + mRegisterTarget->OwnerDoc()->SetMayHaveDOMMutationObservers(); + } + + nsMutationReceiverBase(nsINode* aRegisterTarget, + nsMutationReceiverBase* aParent) + : mTarget(nullptr), mObserver(nullptr), mParent(aParent), + mRegisterTarget(aRegisterTarget), mKungFuDeathGrip(aParent->Target()) + { + NS_ASSERTION(mParent->Subtree(), "Should clone a non-subtree observer!"); + mRegisterTarget->AddMutationObserver(this); + mRegisterTarget->SetMayHaveDOMMutationObserver(); + mRegisterTarget->OwnerDoc()->SetMayHaveDOMMutationObservers(); + } + + bool ObservesAttr(mozilla::dom::Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttr) + { + if (mParent) { + return mParent->ObservesAttr(aElement, aNameSpaceID, aAttr); + } + if (!Attributes() || (!Subtree() && aElement != Target())) { + return false; + } + if (AllAttributes()) { + return true; + } + + if (aNameSpaceID != kNameSpaceID_None) { + return false; + } + + nsCOMArray& filters = AttributeFilter(); + for (int32_t i = 0; i < filters.Count(); ++i) { + if (filters[i] == aAttr) { + return true; + } + } + return false; + } + + // The target for the MutationObserver.observe() method. + nsINode* mTarget; + nsDOMMutationObserver* mObserver; + nsRefPtr mParent; // Cleared after microtask. + // The node to which Gecko-internal nsIMutationObserver was registered to. + // This is different than mTarget when dealing with transient observers. + nsINode* mRegisterTarget; + nsCOMArray mTransientReceivers; + // While we have transient receivers, keep the original mutation receiver + // alive so it doesn't go away and disconnect all its transient receivers. + nsCOMPtr mKungFuDeathGrip; + +private: + bool mSubtree; + bool mChildList; + bool mCharacterData; + bool mCharacterDataOldValue; + bool mAttributes; + bool mAllAttributes; + bool mAttributeOldValue; + nsCOMArray mAttributeFilter; +}; + + +class nsMutationReceiver : public nsMutationReceiverBase +{ +public: + nsMutationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver); + + nsMutationReceiver(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent) + : nsMutationReceiverBase(aRegisterTarget, aParent) + { + NS_ASSERTION(!static_cast(aParent)->GetParent(), + "Shouldn't create deep observer hierarchies!"); + aParent->AddClone(this); + } + + virtual ~nsMutationReceiver() { Disconnect(false); } + + nsMutationReceiver* GetParent() + { + return static_cast(mParent.get()); + } + + void RemoveClones() + { + for (int32_t i = 0; i < mTransientReceivers.Count(); ++i) { + nsMutationReceiver* r = + static_cast(mTransientReceivers[i]); + r->DisconnectTransientReceiver(); + } + mTransientReceivers.Clear(); + } + + void DisconnectTransientReceiver() + { + if (mRegisterTarget) { + mRegisterTarget->RemoveMutationObserver(this); + mRegisterTarget = nullptr; + } + + mParent = nullptr; + NS_ASSERTION(!mTarget, "Should not have mTarget"); + NS_ASSERTION(!mObserver, "Should not have mObserver"); + } + + void Disconnect(bool aRemoveFromObserver); + + NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW + NS_DECL_ISUPPORTS + + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED + + virtual void AttributeSetToCurrentValue(nsIDocument* aDocument, + mozilla::dom::Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute) MOZ_OVERRIDE + { + // We can reuse AttributeWillChange implementation. + AttributeWillChange(aDocument, aElement, aNameSpaceID, aAttribute, + nsIDOMMutationEvent::MODIFICATION); + } +}; + +#define NS_DOM_MUTATION_OBSERVER_IID \ +{ 0x0c3b91f8, 0xcc3b, 0x4b08, \ + { 0x9e, 0xab, 0x07, 0x47, 0xa9, 0xe4, 0x65, 0xb4 } } + +class nsDOMMutationObserver : public nsISupports, + public nsWrapperCache +{ +public: + nsDOMMutationObserver(already_AddRefed&& aOwner, + mozilla::dom::MutationCallback& aCb) + : mOwner(aOwner), mLastPendingMutation(nullptr), mPendingMutationCount(0), + mCallback(&aCb), mWaitingForRun(false), mId(++sCount) + { + SetIsDOMBinding(); + } + virtual ~nsDOMMutationObserver(); + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationObserver) + NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_MUTATION_OBSERVER_IID) + + static already_AddRefed + Constructor(const mozilla::dom::GlobalObject& aGlobal, + mozilla::dom::MutationCallback& aCb, + mozilla::ErrorResult& aRv); + + virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE + { + return mozilla::dom::MutationObserverBinding::Wrap(aCx, this); + } + + nsISupports* GetParentObject() const + { + return mOwner; + } + + void Observe(nsINode& aTarget, + const mozilla::dom::MutationObserverInit& aOptions, + mozilla::ErrorResult& aRv); + + void Disconnect(); + + void TakeRecords(nsTArray >& aRetVal); + + void HandleMutation(); + + void GetObservingInfo(nsTArray >& aResult); + + mozilla::dom::MutationCallback* MutationCallback() { return mCallback; } + + void AppendMutationRecord(already_AddRefed aRecord) + { + nsRefPtr record = aRecord; + MOZ_ASSERT(record); + if (!mLastPendingMutation) { + MOZ_ASSERT(!mFirstPendingMutation); + mFirstPendingMutation = record.forget(); + mLastPendingMutation = mFirstPendingMutation; + } else { + MOZ_ASSERT(mFirstPendingMutation); + mLastPendingMutation->mNext = record.forget(); + mLastPendingMutation = mLastPendingMutation->mNext; + } + ++mPendingMutationCount; + } + + void ClearPendingRecords() + { + mFirstPendingMutation = nullptr; + mLastPendingMutation = nullptr; + mPendingMutationCount = 0; + } + + // static methods + static void HandleMutations() + { + if (sScheduledMutationObservers) { + HandleMutationsInternal(); + } + } + + static void EnterMutationHandling(); + static void LeaveMutationHandling(); + + static void Shutdown(); +protected: + friend class nsMutationReceiver; + friend class nsAutoMutationBatch; + nsMutationReceiver* GetReceiverFor(nsINode* aNode, bool aMayCreate); + void RemoveReceiver(nsMutationReceiver* aReceiver); + + already_AddRefed TakeRecords(); + + void GetAllSubtreeObserversFor(nsINode* aNode, + nsTArray& aObservers); + void ScheduleForRun(); + void RescheduleForRun(); + + nsDOMMutationRecord* CurrentRecord(nsIAtom* aType); + bool HasCurrentRecord(const nsAString& aType); + + bool Suppressed() + { + if (mOwner) { + nsCOMPtr d = mOwner->GetExtantDoc(); + return d && d->IsInSyncOperation(); + } + return false; + } + + static void HandleMutationsInternal(); + + static void AddCurrentlyHandlingObserver(nsDOMMutationObserver* aObserver); + + nsCOMPtr mOwner; + + nsCOMArray mReceivers; + nsClassHashtable > mTransientReceivers; + // MutationRecords which are being constructed. + nsAutoTArray mCurrentMutations; + // MutationRecords which will be handed to the callback at the end of + // the microtask. + nsRefPtr mFirstPendingMutation; + nsDOMMutationRecord* mLastPendingMutation; + uint32_t mPendingMutationCount; + + nsRefPtr mCallback; + + bool mWaitingForRun; + + uint64_t mId; + + static uint64_t sCount; + static nsAutoTArray, 4>* sScheduledMutationObservers; + static nsDOMMutationObserver* sCurrentObserver; + + static uint32_t sMutationLevel; + static nsAutoTArray, 4>, 4>* + sCurrentlyHandlingObservers; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsDOMMutationObserver, NS_DOM_MUTATION_OBSERVER_IID) + +class nsAutoMutationBatch +{ +public: + nsAutoMutationBatch() + : mPreviousBatch(nullptr), mBatchTarget(nullptr), mRemovalDone(false), + mFromFirstToLast(false), mAllowNestedBatches(false) + { + } + + nsAutoMutationBatch(nsINode* aTarget, bool aFromFirstToLast, + bool aAllowNestedBatches) + : mPreviousBatch(nullptr), mBatchTarget(nullptr), mRemovalDone(false), + mFromFirstToLast(false), mAllowNestedBatches(false) + { + Init(aTarget, aFromFirstToLast, aAllowNestedBatches); + } + + void Init(nsINode* aTarget, bool aFromFirstToLast, bool aAllowNestedBatches) + { + if (aTarget && aTarget->OwnerDoc()->MayHaveDOMMutationObservers()) { + if (sCurrentBatch && !sCurrentBatch->mAllowNestedBatches) { + return; + } + mBatchTarget = aTarget; + mFromFirstToLast = aFromFirstToLast; + mAllowNestedBatches = aAllowNestedBatches; + mPreviousBatch = sCurrentBatch; + sCurrentBatch = this; + nsDOMMutationObserver::EnterMutationHandling(); + } + } + + void RemovalDone() { mRemovalDone = true; } + static bool IsRemovalDone() { return sCurrentBatch->mRemovalDone; } + + void SetPrevSibling(nsINode* aNode) { mPrevSibling = aNode; } + void SetNextSibling(nsINode* aNode) { mNextSibling = aNode; } + + void Done(); + + ~nsAutoMutationBatch() { NodesAdded(); } + + static bool IsBatching() + { + return !!sCurrentBatch; + } + + static nsAutoMutationBatch* GetCurrentBatch() + { + return sCurrentBatch; + } + + static void UpdateObserver(nsDOMMutationObserver* aObserver, + bool aWantsChildList) + { + uint32_t l = sCurrentBatch->mObservers.Length(); + for (uint32_t i = 0; i < l; ++i) { + if (sCurrentBatch->mObservers[i].mObserver == aObserver) { + if (aWantsChildList) { + sCurrentBatch->mObservers[i].mWantsChildList = aWantsChildList; + } + return; + } + } + BatchObserver* bo = sCurrentBatch->mObservers.AppendElement(); + bo->mObserver = aObserver; + bo->mWantsChildList = aWantsChildList; + } + + + static nsINode* GetBatchTarget() { return sCurrentBatch->mBatchTarget; } + + // Mutation receivers notify the batch about removed child nodes. + static void NodeRemoved(nsIContent* aChild) + { + if (IsBatching() && !sCurrentBatch->mRemovalDone) { + uint32_t len = sCurrentBatch->mRemovedNodes.Length(); + if (!len || + sCurrentBatch->mRemovedNodes[len - 1] != aChild) { + sCurrentBatch->mRemovedNodes.AppendElement(aChild); + } + } + } + + // Called after new child nodes have been added to the batch target. + void NodesAdded() + { + if (sCurrentBatch != this) { + return; + } + + nsIContent* c = + mPrevSibling ? mPrevSibling->GetNextSibling() : + mBatchTarget->GetFirstChild(); + for (; c != mNextSibling; c = c->GetNextSibling()) { + mAddedNodes.AppendElement(c); + } + Done(); + } + +private: + struct BatchObserver + { + nsDOMMutationObserver* mObserver; + bool mWantsChildList; + }; + + static nsAutoMutationBatch* sCurrentBatch; + nsAutoMutationBatch* mPreviousBatch; + nsAutoTArray mObservers; + nsTArray > mRemovedNodes; + nsTArray > mAddedNodes; + nsINode* mBatchTarget; + bool mRemovalDone; + bool mFromFirstToLast; + bool mAllowNestedBatches; + nsCOMPtr mPrevSibling; + nsCOMPtr mNextSibling; +}; + +inline +nsDOMMutationObserver* +nsMutationReceiverBase::Observer() +{ + return mParent ? + mParent->Observer() : static_cast(mObserver); +} + +#endif