michael@0: /* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*- */ michael@0: /* vim: set sw=4 ts=8 et tw=80 : */ 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 file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsDOMMutationObserver.h" michael@0: michael@0: #include "mozilla/dom/OwningNonNull.h" michael@0: #include "nsError.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsIDOMMutationEvent.h" michael@0: #include "nsTextFragment.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: michael@0: nsAutoTArray, 4>* michael@0: nsDOMMutationObserver::sScheduledMutationObservers = nullptr; michael@0: michael@0: nsDOMMutationObserver* nsDOMMutationObserver::sCurrentObserver = nullptr; michael@0: michael@0: uint32_t nsDOMMutationObserver::sMutationLevel = 0; michael@0: uint64_t nsDOMMutationObserver::sCount = 0; michael@0: michael@0: nsAutoTArray, 4>, 4>* michael@0: nsDOMMutationObserver::sCurrentlyHandlingObservers = nullptr; michael@0: michael@0: nsINodeList* michael@0: nsDOMMutationRecord::AddedNodes() michael@0: { michael@0: if (!mAddedNodes) { michael@0: mAddedNodes = new nsSimpleContentList(mTarget); michael@0: } michael@0: return mAddedNodes; michael@0: } michael@0: michael@0: nsINodeList* michael@0: nsDOMMutationRecord::RemovedNodes() michael@0: { michael@0: if (!mRemovedNodes) { michael@0: mRemovedNodes = new nsSimpleContentList(mTarget); michael@0: } michael@0: return mRemovedNodes; michael@0: } michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMMutationRecord) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMutationRecord) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMutationRecord) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_7(nsDOMMutationRecord, michael@0: mTarget, michael@0: mPreviousSibling, mNextSibling, michael@0: mAddedNodes, mRemovedNodes, michael@0: mNext, mOwner) michael@0: michael@0: // Observer michael@0: michael@0: NS_IMPL_ADDREF(nsMutationReceiver) michael@0: NS_IMPL_RELEASE(nsMutationReceiver) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsMutationReceiver) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: nsMutationReceiver::nsMutationReceiver(nsINode* aTarget, michael@0: nsDOMMutationObserver* aObserver) michael@0: : nsMutationReceiverBase(aTarget, aObserver) michael@0: { michael@0: mTarget->BindObject(aObserver); michael@0: } michael@0: michael@0: void michael@0: nsMutationReceiver::Disconnect(bool aRemoveFromObserver) michael@0: { michael@0: if (mRegisterTarget) { michael@0: mRegisterTarget->RemoveMutationObserver(this); michael@0: mRegisterTarget = nullptr; michael@0: } michael@0: michael@0: mParent = nullptr; michael@0: nsINode* target = mTarget; michael@0: mTarget = nullptr; michael@0: nsDOMMutationObserver* observer = mObserver; michael@0: mObserver = nullptr; michael@0: RemoveClones(); michael@0: michael@0: if (target && observer) { michael@0: if (aRemoveFromObserver) { michael@0: static_cast(observer)->RemoveReceiver(this); michael@0: } michael@0: // UnbindObject may delete 'this'! michael@0: target->UnbindObject(observer); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMutationReceiver::AttributeWillChange(nsIDocument* aDocument, michael@0: mozilla::dom::Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: if (nsAutoMutationBatch::IsBatching() || michael@0: !ObservesAttr(aElement, aNameSpaceID, aAttribute) || michael@0: aElement->ChromeOnlyAccess()) { michael@0: return; michael@0: } michael@0: michael@0: nsDOMMutationRecord* m = michael@0: Observer()->CurrentRecord(nsGkAtoms::attributes); michael@0: michael@0: NS_ASSERTION(!m->mTarget || m->mTarget == aElement, michael@0: "Wrong target!"); michael@0: NS_ASSERTION(!m->mAttrName || m->mAttrName == aAttribute, michael@0: "Wrong attribute!"); michael@0: if (!m->mTarget) { michael@0: m->mTarget = aElement; michael@0: m->mAttrName = aAttribute; michael@0: if (aNameSpaceID == kNameSpaceID_None) { michael@0: m->mAttrNamespace.SetIsVoid(true); michael@0: } else { michael@0: nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, michael@0: m->mAttrNamespace); michael@0: } michael@0: } michael@0: michael@0: if (AttributeOldValue() && m->mPrevValue.IsVoid()) { michael@0: if (!aElement->GetAttr(aNameSpaceID, aAttribute, m->mPrevValue)) { michael@0: m->mPrevValue.SetIsVoid(true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMutationReceiver::CharacterDataWillChange(nsIDocument *aDocument, michael@0: nsIContent* aContent, michael@0: CharacterDataChangeInfo* aInfo) michael@0: { michael@0: if (nsAutoMutationBatch::IsBatching() || michael@0: !CharacterData() || !(Subtree() || aContent == Target()) || michael@0: aContent->ChromeOnlyAccess()) { michael@0: return; michael@0: } michael@0: michael@0: nsDOMMutationRecord* m = michael@0: Observer()->CurrentRecord(nsGkAtoms::characterData); michael@0: michael@0: NS_ASSERTION(!m->mTarget || m->mTarget == aContent, michael@0: "Wrong target!"); michael@0: michael@0: if (!m->mTarget) { michael@0: m->mTarget = aContent; michael@0: } michael@0: if (CharacterDataOldValue() && m->mPrevValue.IsVoid()) { michael@0: aContent->GetText()->AppendTo(m->mPrevValue); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMutationReceiver::ContentAppended(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aFirstNewContent, michael@0: int32_t aNewIndexInContainer) michael@0: { michael@0: nsINode* parent = NODE_FROM(aContainer, aDocument); michael@0: bool wantsChildList = ChildList() && (Subtree() || parent == Target()); michael@0: if (!wantsChildList || aFirstNewContent->ChromeOnlyAccess()) { michael@0: return; michael@0: } michael@0: michael@0: if (nsAutoMutationBatch::IsBatching()) { michael@0: if (parent == nsAutoMutationBatch::GetBatchTarget()) { michael@0: nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: nsDOMMutationRecord* m = michael@0: Observer()->CurrentRecord(nsGkAtoms::childList); michael@0: NS_ASSERTION(!m->mTarget || m->mTarget == parent, michael@0: "Wrong target!"); michael@0: if (m->mTarget) { michael@0: // Already handled case. michael@0: return; michael@0: } michael@0: m->mTarget = parent; michael@0: m->mAddedNodes = new nsSimpleContentList(parent); michael@0: michael@0: nsINode* n = aFirstNewContent; michael@0: while (n) { michael@0: m->mAddedNodes->AppendElement(static_cast(n)); michael@0: n = n->GetNextSibling(); michael@0: } michael@0: m->mPreviousSibling = aFirstNewContent->GetPreviousSibling(); michael@0: } michael@0: michael@0: void michael@0: nsMutationReceiver::ContentInserted(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer) michael@0: { michael@0: nsINode* parent = NODE_FROM(aContainer, aDocument); michael@0: bool wantsChildList = ChildList() && (Subtree() || parent == Target()); michael@0: if (!wantsChildList || aChild->ChromeOnlyAccess()) { michael@0: return; michael@0: } michael@0: michael@0: if (nsAutoMutationBatch::IsBatching()) { michael@0: if (parent == nsAutoMutationBatch::GetBatchTarget()) { michael@0: nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: nsDOMMutationRecord* m = michael@0: Observer()->CurrentRecord(nsGkAtoms::childList); michael@0: if (m->mTarget) { michael@0: // Already handled case. michael@0: return; michael@0: } michael@0: m->mTarget = parent; michael@0: m->mAddedNodes = new nsSimpleContentList(parent); michael@0: m->mAddedNodes->AppendElement(aChild); michael@0: m->mPreviousSibling = aChild->GetPreviousSibling(); michael@0: m->mNextSibling = aChild->GetNextSibling(); michael@0: } michael@0: michael@0: void michael@0: nsMutationReceiver::ContentRemoved(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: if (aChild->ChromeOnlyAccess()) { michael@0: return; michael@0: } michael@0: michael@0: nsINode* parent = NODE_FROM(aContainer, aDocument); michael@0: if (nsAutoMutationBatch::IsBatching()) { michael@0: if (nsAutoMutationBatch::IsRemovalDone()) { michael@0: // This can happen for example if HTML parser parses to michael@0: // context node, but needs to move elements around. michael@0: return; michael@0: } michael@0: if (nsAutoMutationBatch::GetBatchTarget() != parent) { michael@0: return; michael@0: } michael@0: michael@0: bool wantsChildList = ChildList() && (Subtree() || parent == Target()); michael@0: if (wantsChildList || Subtree()) { michael@0: nsAutoMutationBatch::NodeRemoved(aChild); michael@0: nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: if (Subtree()) { michael@0: // Try to avoid creating transient observer if the node michael@0: // already has an observer observing the same set of nodes. michael@0: nsMutationReceiver* orig = GetParent() ? GetParent() : this; michael@0: if (Observer()->GetReceiverFor(aChild, false) != orig) { michael@0: bool transientExists = false; michael@0: nsCOMArray* transientReceivers = nullptr; michael@0: Observer()->mTransientReceivers.Get(aChild, &transientReceivers); michael@0: if (!transientReceivers) { michael@0: transientReceivers = new nsCOMArray(); michael@0: Observer()->mTransientReceivers.Put(aChild, transientReceivers); michael@0: } else { michael@0: for (int32_t i = 0; i < transientReceivers->Count(); ++i) { michael@0: nsMutationReceiver* r = transientReceivers->ObjectAt(i); michael@0: if (r->GetParent() == orig) { michael@0: transientExists = true; michael@0: } michael@0: } michael@0: } michael@0: if (!transientExists) { michael@0: // Make sure the elements which are removed from the michael@0: // subtree are kept in the same observation set. michael@0: transientReceivers->AppendObject(new nsMutationReceiver(aChild, orig)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (ChildList() && (Subtree() || parent == Target())) { michael@0: nsDOMMutationRecord* m = michael@0: Observer()->CurrentRecord(nsGkAtoms::childList); michael@0: if (m->mTarget) { michael@0: // Already handled case. michael@0: return; michael@0: } michael@0: m->mTarget = parent; michael@0: m->mRemovedNodes = new nsSimpleContentList(parent); michael@0: m->mRemovedNodes->AppendElement(aChild); michael@0: m->mPreviousSibling = aPreviousSibling; michael@0: m->mNextSibling = parent->GetChildAt(aIndexInContainer); michael@0: } michael@0: // We need to schedule always, so that after microtask mTransientReceivers michael@0: // can be cleared correctly. michael@0: Observer()->ScheduleForRun(); michael@0: } michael@0: michael@0: void nsMutationReceiver::NodeWillBeDestroyed(const nsINode *aNode) michael@0: { michael@0: NS_ASSERTION(!mParent, "Shouldn't have mParent here!"); michael@0: Disconnect(true); michael@0: } michael@0: michael@0: // Observer michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMMutationObserver) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_ENTRY(nsDOMMutationObserver) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMutationObserver) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMutationObserver) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMMutationObserver) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDOMMutationObserver) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMMutationObserver) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) michael@0: for (int32_t i = 0; i < tmp->mReceivers.Count(); ++i) { michael@0: tmp->mReceivers[i]->Disconnect(false); michael@0: } michael@0: tmp->mReceivers.Clear(); michael@0: tmp->ClearPendingRecords(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) michael@0: // No need to handle mTransientReceivers michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMMutationObserver) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReceivers) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFirstPendingMutation) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) michael@0: // No need to handle mTransientReceivers michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: nsMutationReceiver* michael@0: nsDOMMutationObserver::GetReceiverFor(nsINode* aNode, bool aMayCreate) michael@0: { michael@0: if (!aMayCreate && !aNode->MayHaveDOMMutationObserver()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: for (int32_t i = 0; i < mReceivers.Count(); ++i) { michael@0: if (mReceivers[i]->Target() == aNode) { michael@0: return mReceivers[i]; michael@0: } michael@0: } michael@0: if (!aMayCreate) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsMutationReceiver* r = new nsMutationReceiver(aNode, this); michael@0: mReceivers.AppendObject(r); michael@0: return r; michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::RemoveReceiver(nsMutationReceiver* aReceiver) michael@0: { michael@0: mReceivers.RemoveObject(aReceiver); michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::GetAllSubtreeObserversFor(nsINode* aNode, michael@0: nsTArray& michael@0: aReceivers) michael@0: { michael@0: nsINode* n = aNode; michael@0: while (n) { michael@0: if (n->MayHaveDOMMutationObserver()) { michael@0: nsMutationReceiver* r = GetReceiverFor(n, false); michael@0: if (r && r->Subtree() && !aReceivers.Contains(r)) { michael@0: aReceivers.AppendElement(r); michael@0: // If we've found all the receivers the observer has, michael@0: // no need to search for more. michael@0: if (mReceivers.Count() == int32_t(aReceivers.Length())) { michael@0: return; michael@0: } michael@0: } michael@0: nsCOMArray* transientReceivers = nullptr; michael@0: if (mTransientReceivers.Get(n, &transientReceivers) && transientReceivers) { michael@0: for (int32_t i = 0; i < transientReceivers->Count(); ++i) { michael@0: nsMutationReceiver* r = transientReceivers->ObjectAt(i); michael@0: nsMutationReceiver* parent = r->GetParent(); michael@0: if (r->Subtree() && parent && !aReceivers.Contains(parent)) { michael@0: aReceivers.AppendElement(parent); michael@0: } michael@0: } michael@0: if (mReceivers.Count() == int32_t(aReceivers.Length())) { michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: n = n->GetParentNode(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::ScheduleForRun() michael@0: { michael@0: nsDOMMutationObserver::AddCurrentlyHandlingObserver(this); michael@0: michael@0: if (mWaitingForRun) { michael@0: return; michael@0: } michael@0: mWaitingForRun = true; michael@0: RescheduleForRun(); michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::RescheduleForRun() michael@0: { michael@0: if (!sScheduledMutationObservers) { michael@0: sScheduledMutationObservers = new nsAutoTArray, 4>; michael@0: } michael@0: michael@0: bool didInsert = false; michael@0: for (uint32_t i = 0; i < sScheduledMutationObservers->Length(); ++i) { michael@0: if (static_cast((*sScheduledMutationObservers)[i]) michael@0: ->mId > mId) { michael@0: sScheduledMutationObservers->InsertElementAt(i, this); michael@0: didInsert = true; michael@0: break; michael@0: } michael@0: } michael@0: if (!didInsert) { michael@0: sScheduledMutationObservers->AppendElement(this); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::Observe(nsINode& aTarget, michael@0: const mozilla::dom::MutationObserverInit& aOptions, michael@0: mozilla::ErrorResult& aRv) michael@0: { michael@0: michael@0: if (!(aOptions.mChildList || aOptions.mAttributes || aOptions.mCharacterData)) { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: if (aOptions.mAttributeOldValue && !aOptions.mAttributes) { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: if (aOptions.mCharacterDataOldValue && !aOptions.mCharacterData) { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsCOMArray filters; michael@0: bool allAttrs = true; michael@0: if (aOptions.mAttributeFilter.WasPassed()) { michael@0: allAttrs = false; michael@0: const mozilla::dom::Sequence& filtersAsString = michael@0: aOptions.mAttributeFilter.Value(); michael@0: uint32_t len = filtersAsString.Length(); michael@0: michael@0: if (len != 0 && !aOptions.mAttributes) { michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: filters.SetCapacity(len); michael@0: michael@0: for (uint32_t i = 0; i < len; ++i) { michael@0: nsCOMPtr a = do_GetAtom(filtersAsString[i]); michael@0: filters.AppendObject(a); michael@0: } michael@0: } michael@0: michael@0: nsMutationReceiver* r = GetReceiverFor(&aTarget, true); michael@0: r->SetChildList(aOptions.mChildList); michael@0: r->SetAttributes(aOptions.mAttributes); michael@0: r->SetCharacterData(aOptions.mCharacterData); michael@0: r->SetSubtree(aOptions.mSubtree); michael@0: r->SetAttributeOldValue(aOptions.mAttributeOldValue); michael@0: r->SetCharacterDataOldValue(aOptions.mCharacterDataOldValue); michael@0: r->SetAttributeFilter(filters); michael@0: r->SetAllAttributes(allAttrs); michael@0: r->RemoveClones(); michael@0: michael@0: #ifdef DEBUG michael@0: for (int32_t i = 0; i < mReceivers.Count(); ++i) { michael@0: NS_WARN_IF_FALSE(mReceivers[i]->Target(), michael@0: "All the receivers should have a target!"); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::Disconnect() michael@0: { michael@0: for (int32_t i = 0; i < mReceivers.Count(); ++i) { michael@0: mReceivers[i]->Disconnect(false); michael@0: } michael@0: mReceivers.Clear(); michael@0: mCurrentMutations.Clear(); michael@0: ClearPendingRecords(); michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::TakeRecords( michael@0: nsTArray >& aRetVal) michael@0: { michael@0: aRetVal.Clear(); michael@0: aRetVal.SetCapacity(mPendingMutationCount); michael@0: nsRefPtr current; michael@0: current.swap(mFirstPendingMutation); michael@0: for (uint32_t i = 0; i < mPendingMutationCount; ++i) { michael@0: nsRefPtr next; michael@0: current->mNext.swap(next); michael@0: *aRetVal.AppendElement() = current.forget(); michael@0: current.swap(next); michael@0: } michael@0: ClearPendingRecords(); michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::GetObservingInfo(nsTArray >& aResult) michael@0: { michael@0: aResult.SetCapacity(mReceivers.Count()); michael@0: for (int32_t i = 0; i < mReceivers.Count(); ++i) { michael@0: MutationObservingInfo& info = aResult.AppendElement()->SetValue(); michael@0: nsMutationReceiver* mr = mReceivers[i]; michael@0: info.mChildList = mr->ChildList(); michael@0: info.mAttributes = mr->Attributes(); michael@0: info.mCharacterData = mr->CharacterData(); michael@0: info.mSubtree = mr->Subtree(); michael@0: info.mAttributeOldValue = mr->AttributeOldValue(); michael@0: info.mCharacterDataOldValue = mr->CharacterDataOldValue(); michael@0: nsCOMArray& filters = mr->AttributeFilter(); michael@0: if (filters.Count()) { michael@0: info.mAttributeFilter.Construct(); michael@0: mozilla::dom::Sequence& filtersAsStrings = michael@0: info.mAttributeFilter.Value(); michael@0: for (int32_t j = 0; j < filters.Count(); ++j) { michael@0: filtersAsStrings.AppendElement(nsDependentAtomString(filters[j])); michael@0: } michael@0: } michael@0: info.mObservedNode = mr->Target(); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: nsDOMMutationObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal, michael@0: mozilla::dom::MutationCallback& aCb, michael@0: mozilla::ErrorResult& aRv) michael@0: { michael@0: nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); michael@0: if (!window) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: return nullptr; michael@0: } michael@0: MOZ_ASSERT(window->IsInnerWindow()); michael@0: nsRefPtr observer = michael@0: new nsDOMMutationObserver(window.forget(), aCb); michael@0: return observer.forget(); michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::HandleMutation() michael@0: { michael@0: NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "Whaat!"); michael@0: NS_ASSERTION(mCurrentMutations.IsEmpty(), michael@0: "Still generating MutationRecords?"); michael@0: michael@0: mWaitingForRun = false; michael@0: michael@0: for (int32_t i = 0; i < mReceivers.Count(); ++i) { michael@0: mReceivers[i]->RemoveClones(); michael@0: } michael@0: mTransientReceivers.Clear(); michael@0: michael@0: nsPIDOMWindow* outer = mOwner->GetOuterWindow(); michael@0: if (!mPendingMutationCount || !outer || michael@0: outer->GetCurrentInnerWindow() != mOwner) { michael@0: ClearPendingRecords(); michael@0: return; michael@0: } michael@0: michael@0: mozilla::dom::Sequence > michael@0: mutations; michael@0: if (mutations.SetCapacity(mPendingMutationCount)) { michael@0: // We can't use TakeRecords easily here, because it deals with a michael@0: // different type of array, and we want to optimize out any extra copying. michael@0: nsRefPtr current; michael@0: current.swap(mFirstPendingMutation); michael@0: for (uint32_t i = 0; i < mPendingMutationCount; ++i) { michael@0: nsRefPtr next; michael@0: current->mNext.swap(next); michael@0: *mutations.AppendElement() = current; michael@0: current.swap(next); michael@0: } michael@0: } michael@0: ClearPendingRecords(); michael@0: michael@0: mozilla::ErrorResult rv; michael@0: mCallback->Call(this, mutations, *this, rv); michael@0: } michael@0: michael@0: class AsyncMutationHandler : public nsRunnable michael@0: { michael@0: public: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsDOMMutationObserver::HandleMutations(); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: void michael@0: nsDOMMutationObserver::HandleMutationsInternal() michael@0: { michael@0: if (!nsContentUtils::IsSafeToRunScript()) { michael@0: nsContentUtils::AddScriptRunner(new AsyncMutationHandler()); michael@0: return; michael@0: } michael@0: static nsRefPtr sCurrentObserver; michael@0: if (sCurrentObserver && !sCurrentObserver->Suppressed()) { michael@0: // In normal cases sScheduledMutationObservers will be handled michael@0: // after previous mutations are handled. But in case some michael@0: // callback calls a sync API, which spins the eventloop, we need to still michael@0: // process other mutations happening during that sync call. michael@0: // This does *not* catch all cases, but should work for stuff running michael@0: // in separate tabs. michael@0: return; michael@0: } michael@0: michael@0: nsTArray >* suppressedObservers = nullptr; michael@0: michael@0: while (sScheduledMutationObservers) { michael@0: nsAutoTArray, 4>* observers = michael@0: sScheduledMutationObservers; michael@0: sScheduledMutationObservers = nullptr; michael@0: for (uint32_t i = 0; i < observers->Length(); ++i) { michael@0: sCurrentObserver = static_cast((*observers)[i]); michael@0: if (!sCurrentObserver->Suppressed()) { michael@0: sCurrentObserver->HandleMutation(); michael@0: } else { michael@0: if (!suppressedObservers) { michael@0: suppressedObservers = new nsTArray >; michael@0: } michael@0: if (!suppressedObservers->Contains(sCurrentObserver)) { michael@0: suppressedObservers->AppendElement(sCurrentObserver); michael@0: } michael@0: } michael@0: } michael@0: delete observers; michael@0: } michael@0: michael@0: if (suppressedObservers) { michael@0: for (uint32_t i = 0; i < suppressedObservers->Length(); ++i) { michael@0: static_cast(suppressedObservers->ElementAt(i))-> michael@0: RescheduleForRun(); michael@0: } michael@0: delete suppressedObservers; michael@0: suppressedObservers = nullptr; michael@0: } michael@0: sCurrentObserver = nullptr; michael@0: } michael@0: michael@0: nsDOMMutationRecord* michael@0: nsDOMMutationObserver::CurrentRecord(nsIAtom* aType) michael@0: { michael@0: NS_ASSERTION(sMutationLevel > 0, "Unexpected mutation level!"); michael@0: michael@0: while (mCurrentMutations.Length() < sMutationLevel) { michael@0: mCurrentMutations.AppendElement(static_cast(nullptr)); michael@0: } michael@0: michael@0: uint32_t last = sMutationLevel - 1; michael@0: if (!mCurrentMutations[last]) { michael@0: nsRefPtr r = new nsDOMMutationRecord(aType, GetParentObject()); michael@0: mCurrentMutations[last] = r; michael@0: AppendMutationRecord(r.forget()); michael@0: ScheduleForRun(); michael@0: } michael@0: michael@0: NS_ASSERTION(mCurrentMutations[last]->mType == aType, michael@0: "Unexpected MutationRecord type!"); michael@0: michael@0: return mCurrentMutations[last]; michael@0: } michael@0: michael@0: nsDOMMutationObserver::~nsDOMMutationObserver() michael@0: { michael@0: for (int32_t i = 0; i < mReceivers.Count(); ++i) { michael@0: mReceivers[i]->RemoveClones(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::EnterMutationHandling() michael@0: { michael@0: ++sMutationLevel; michael@0: } michael@0: michael@0: // Leave the current mutation level (there can be several levels if in case michael@0: // of nested calls to the nsIMutationObserver methods). michael@0: // The most recent mutation record is removed from mCurrentMutations, so michael@0: // that is doesn't get modified anymore by receivers. michael@0: void michael@0: nsDOMMutationObserver::LeaveMutationHandling() michael@0: { michael@0: if (sCurrentlyHandlingObservers && michael@0: sCurrentlyHandlingObservers->Length() == sMutationLevel) { michael@0: nsTArray >& obs = michael@0: sCurrentlyHandlingObservers->ElementAt(sMutationLevel - 1); michael@0: for (uint32_t i = 0; i < obs.Length(); ++i) { michael@0: nsDOMMutationObserver* o = michael@0: static_cast(obs[i]); michael@0: if (o->mCurrentMutations.Length() == sMutationLevel) { michael@0: // It is already in pending mutations. michael@0: o->mCurrentMutations.RemoveElementAt(sMutationLevel - 1); michael@0: } michael@0: } michael@0: sCurrentlyHandlingObservers->RemoveElementAt(sMutationLevel - 1); michael@0: } michael@0: --sMutationLevel; michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::AddCurrentlyHandlingObserver(nsDOMMutationObserver* aObserver) michael@0: { michael@0: NS_ASSERTION(sMutationLevel > 0, "Unexpected mutation level!"); michael@0: michael@0: if (!sCurrentlyHandlingObservers) { michael@0: sCurrentlyHandlingObservers = michael@0: new nsAutoTArray, 4>, 4>; michael@0: } michael@0: michael@0: while (sCurrentlyHandlingObservers->Length() < sMutationLevel) { michael@0: sCurrentlyHandlingObservers->InsertElementAt( michael@0: sCurrentlyHandlingObservers->Length()); michael@0: } michael@0: michael@0: uint32_t last = sMutationLevel - 1; michael@0: if (!sCurrentlyHandlingObservers->ElementAt(last).Contains(aObserver)) { michael@0: sCurrentlyHandlingObservers->ElementAt(last).AppendElement(aObserver); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsDOMMutationObserver::Shutdown() michael@0: { michael@0: delete sCurrentlyHandlingObservers; michael@0: sCurrentlyHandlingObservers = nullptr; michael@0: delete sScheduledMutationObservers; michael@0: sScheduledMutationObservers = nullptr; michael@0: } michael@0: michael@0: nsAutoMutationBatch* michael@0: nsAutoMutationBatch::sCurrentBatch = nullptr; michael@0: michael@0: void michael@0: nsAutoMutationBatch::Done() michael@0: { michael@0: if (sCurrentBatch != this) { michael@0: return; michael@0: } michael@0: michael@0: sCurrentBatch = mPreviousBatch; michael@0: if (mObservers.IsEmpty()) { michael@0: nsDOMMutationObserver::LeaveMutationHandling(); michael@0: // Nothing to do. michael@0: return; michael@0: } michael@0: michael@0: uint32_t len = mObservers.Length(); michael@0: for (uint32_t i = 0; i < len; ++i) { michael@0: nsDOMMutationObserver* ob = mObservers[i].mObserver; michael@0: bool wantsChildList = mObservers[i].mWantsChildList; michael@0: michael@0: nsRefPtr removedList; michael@0: if (wantsChildList) { michael@0: removedList = new nsSimpleContentList(mBatchTarget); michael@0: } michael@0: michael@0: nsTArray allObservers; michael@0: ob->GetAllSubtreeObserversFor(mBatchTarget, allObservers); michael@0: michael@0: int32_t j = mFromFirstToLast ? 0 : mRemovedNodes.Length() - 1; michael@0: int32_t end = mFromFirstToLast ? mRemovedNodes.Length() : -1; michael@0: for (; j != end; mFromFirstToLast ? ++j : --j) { michael@0: nsCOMPtr removed = mRemovedNodes[j]; michael@0: if (removedList) { michael@0: removedList->AppendElement(removed); michael@0: } michael@0: michael@0: if (allObservers.Length()) { michael@0: nsCOMArray* transientReceivers = nullptr; michael@0: ob->mTransientReceivers.Get(removed, &transientReceivers); michael@0: if (!transientReceivers) { michael@0: transientReceivers = new nsCOMArray(); michael@0: ob->mTransientReceivers.Put(removed, transientReceivers); michael@0: } michael@0: for (uint32_t k = 0; k < allObservers.Length(); ++k) { michael@0: nsMutationReceiver* r = allObservers[k]; michael@0: nsMutationReceiver* orig = r->GetParent() ? r->GetParent() : r; michael@0: if (ob->GetReceiverFor(removed, false) != orig) { michael@0: // Make sure the elements which are removed from the michael@0: // subtree are kept in the same observation set. michael@0: transientReceivers->AppendObject(new nsMutationReceiver(removed, orig)); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (wantsChildList && (mRemovedNodes.Length() || mAddedNodes.Length())) { michael@0: nsRefPtr addedList = michael@0: new nsSimpleContentList(mBatchTarget); michael@0: for (uint32_t i = 0; i < mAddedNodes.Length(); ++i) { michael@0: addedList->AppendElement(mAddedNodes[i]); michael@0: } michael@0: nsRefPtr m = michael@0: new nsDOMMutationRecord(nsGkAtoms::childList, michael@0: ob->GetParentObject()); michael@0: m->mTarget = mBatchTarget; michael@0: m->mRemovedNodes = removedList; michael@0: m->mAddedNodes = addedList; michael@0: m->mPreviousSibling = mPrevSibling; michael@0: m->mNextSibling = mNextSibling; michael@0: ob->AppendMutationRecord(m.forget()); michael@0: } michael@0: // Always schedule the observer so that transient receivers are michael@0: // removed correctly. michael@0: ob->ScheduleForRun(); michael@0: } michael@0: nsDOMMutationObserver::LeaveMutationHandling(); michael@0: }