/* -*- 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/. */ #include "nsDOMMutationObserver.h" #include "mozilla/AnimationTarget.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/Maybe.h" #include "mozilla/OwningNonNull.h" #include "mozilla/dom/Animation.h" #include "mozilla/dom/KeyframeEffect.h" #include "mozilla/dom/DocGroup.h" #include "mozilla/BasePrincipal.h" #include "nsContentUtils.h" #include "nsCSSPseudoElements.h" #include "nsError.h" #include "nsIScriptGlobalObject.h" #include "nsNameSpaceManager.h" #include "nsServiceManagerUtils.h" #include "nsTextFragment.h" #include "nsThreadUtils.h" using namespace mozilla; using namespace mozilla::dom; using mozilla::dom::DocGroup; using mozilla::dom::HTMLSlotElement; AutoTArray, 4>* nsDOMMutationObserver::sScheduledMutationObservers = nullptr; uint32_t nsDOMMutationObserver::sMutationLevel = 0; uint64_t nsDOMMutationObserver::sCount = 0; AutoTArray, 4>, 4>* nsDOMMutationObserver::sCurrentlyHandlingObservers = nullptr; nsINodeList* nsDOMMutationRecord::AddedNodes() { if (!mAddedNodes) { mAddedNodes = new nsSimpleContentList(mTarget); } return mAddedNodes; } nsINodeList* nsDOMMutationRecord::RemovedNodes() { if (!mRemovedNodes) { mRemovedNodes = new nsSimpleContentList(mTarget); } return mRemovedNodes; } NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMMutationRecord) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMutationRecord) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMutationRecord) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMMutationRecord, mTarget, mPreviousSibling, mNextSibling, mAddedNodes, mRemovedNodes, mAddedAnimations, mRemovedAnimations, mChangedAnimations, mNext, mOwner) // Observer bool nsMutationReceiverBase::IsObservable(nsIContent* aContent) { return !aContent->ChromeOnlyAccess() && (Observer()->IsChrome() || !aContent->IsInNativeAnonymousSubtree()); } bool nsMutationReceiverBase::ObservesAttr(nsINode* aRegisterTarget, mozilla::dom::Element* aElement, int32_t aNameSpaceID, nsAtom* aAttr) { if (mParent) { return mParent->ObservesAttr(aRegisterTarget, aElement, aNameSpaceID, aAttr); } if (!Attributes() || (!Subtree() && aElement != Target()) || (Subtree() && aRegisterTarget->SubtreeRoot() != aElement->SubtreeRoot()) || !IsObservable(aElement)) { return false; } if (AllAttributes()) { return true; } if (aNameSpaceID != kNameSpaceID_None) { return false; } nsTArray>& filters = AttributeFilter(); for (size_t i = 0; i < filters.Length(); ++i) { if (filters[i] == aAttr) { return true; } } return false; } NS_IMPL_ADDREF(nsMutationReceiver) NS_IMPL_RELEASE(nsMutationReceiver) NS_INTERFACE_MAP_BEGIN(nsMutationReceiver) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) NS_INTERFACE_MAP_END nsMutationReceiver::nsMutationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver) : nsMutationReceiverBase(aTarget, aObserver) { mTarget->BindObject(aObserver); } void nsMutationReceiver::Disconnect(bool aRemoveFromObserver) { if (mRegisterTarget) { mRegisterTarget->RemoveMutationObserver(this); mRegisterTarget = nullptr; } mParent = nullptr; nsINode* target = mTarget; mTarget = nullptr; nsDOMMutationObserver* observer = mObserver; mObserver = nullptr; RemoveClones(); if (target && observer) { if (aRemoveFromObserver) { static_cast(observer)->RemoveReceiver(this); } // UnbindObject may delete 'this'! target->UnbindObject(observer); } } void nsMutationReceiver::NativeAnonymousChildListChange(nsIContent* aContent, bool aIsRemove) { if (!NativeAnonymousChildList()) { return; } nsINode* parent = aContent->GetParentNode(); if (!parent || (!Subtree() && Target() != parent) || (Subtree() && RegisterTarget()->SubtreeRoot() != parent->SubtreeRoot())) { return; } nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::nativeAnonymousChildList); if (m->mTarget) { return; } m->mTarget = parent; if (aIsRemove) { m->mRemovedNodes = new nsSimpleContentList(parent); m->mRemovedNodes->AppendElement(aContent); } else { m->mAddedNodes = new nsSimpleContentList(parent); m->mAddedNodes->AppendElement(aContent); } } void nsMutationReceiver::AttributeWillChange(mozilla::dom::Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType) { if (nsAutoMutationBatch::IsBatching() || !ObservesAttr(RegisterTarget(), aElement, aNameSpaceID, aAttribute)) { return; } nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::attributes); NS_ASSERTION(!m->mTarget || m->mTarget == aElement, "Wrong target!"); NS_ASSERTION(!m->mAttrName || m->mAttrName == aAttribute, "Wrong attribute!"); if (!m->mTarget) { m->mTarget = aElement; m->mAttrName = aAttribute; if (aNameSpaceID == kNameSpaceID_None) { m->mAttrNamespace.SetIsVoid(true); } else { nsNameSpaceManager::GetInstance()->GetNameSpaceURI(aNameSpaceID, m->mAttrNamespace); } } if (AttributeOldValue() && m->mPrevValue.IsVoid()) { if (!aElement->GetAttr(aNameSpaceID, aAttribute, m->mPrevValue)) { m->mPrevValue.SetIsVoid(true); } } } void nsMutationReceiver::CharacterDataWillChange( nsIContent* aContent, const CharacterDataChangeInfo&) { if (nsAutoMutationBatch::IsBatching() || !CharacterData() || (!Subtree() && aContent != Target()) || (Subtree() && RegisterTarget()->SubtreeRoot() != aContent->SubtreeRoot()) || !IsObservable(aContent)) { return; } nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::characterData); NS_ASSERTION(!m->mTarget || m->mTarget == aContent, "Wrong target!"); if (!m->mTarget) { m->mTarget = aContent; } if (CharacterDataOldValue() && m->mPrevValue.IsVoid()) { aContent->GetText()->AppendTo(m->mPrevValue); } } void nsMutationReceiver::ContentAppended(nsIContent* aFirstNewContent) { nsINode* parent = aFirstNewContent->GetParentNode(); bool wantsChildList = ChildList() && ((Subtree() && RegisterTarget()->SubtreeRoot() == parent->SubtreeRoot()) || parent == Target()); if (!wantsChildList || !IsObservable(aFirstNewContent)) { return; } if (nsAutoMutationBatch::IsBatching()) { if (parent == nsAutoMutationBatch::GetBatchTarget()) { nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList); } return; } nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::childList); NS_ASSERTION(!m->mTarget || m->mTarget == parent, "Wrong target!"); if (m->mTarget) { // Already handled case. return; } m->mTarget = parent; m->mAddedNodes = new nsSimpleContentList(parent); nsINode* n = aFirstNewContent; while (n) { m->mAddedNodes->AppendElement(static_cast(n)); n = n->GetNextSibling(); } m->mPreviousSibling = aFirstNewContent->GetPreviousSibling(); } void nsMutationReceiver::ContentInserted(nsIContent* aChild) { nsINode* parent = aChild->GetParentNode(); bool wantsChildList = ChildList() && ((Subtree() && RegisterTarget()->SubtreeRoot() == parent->SubtreeRoot()) || parent == Target()); if (!wantsChildList || !IsObservable(aChild)) { return; } if (nsAutoMutationBatch::IsBatching()) { if (parent == nsAutoMutationBatch::GetBatchTarget()) { nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList); } return; } nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::childList); if (m->mTarget) { // Already handled case. return; } m->mTarget = parent; m->mAddedNodes = new nsSimpleContentList(parent); m->mAddedNodes->AppendElement(aChild); m->mPreviousSibling = aChild->GetPreviousSibling(); m->mNextSibling = aChild->GetNextSibling(); } void nsMutationReceiver::ContentRemoved(nsIContent* aChild, nsIContent* aPreviousSibling) { if (!IsObservable(aChild)) { return; } nsINode* parent = aChild->GetParentNode(); if (Subtree() && parent->SubtreeRoot() != RegisterTarget()->SubtreeRoot()) { return; } if (nsAutoMutationBatch::IsBatching()) { if (nsAutoMutationBatch::IsRemovalDone()) { // This can happen for example if HTML parser parses to // context node, but needs to move elements around. return; } if (nsAutoMutationBatch::GetBatchTarget() != parent) { return; } bool wantsChildList = ChildList() && (Subtree() || parent == Target()); if (wantsChildList || Subtree()) { nsAutoMutationBatch::NodeRemoved(aChild); nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList); } return; } if (Subtree()) { // Try to avoid creating transient observer if the node // already has an observer observing the same set of nodes. nsMutationReceiver* orig = GetParent() ? GetParent() : this; if (Observer()->GetReceiverFor(aChild, false, false) != orig) { bool transientExists = false; bool isNewEntry = false; auto* const transientReceivers = Observer() ->mTransientReceivers .LookupOrInsertWith( aChild, [&isNewEntry] { isNewEntry = true; return MakeUnique>(); }) .get(); if (!isNewEntry) { for (int32_t i = 0; i < transientReceivers->Count(); ++i) { nsMutationReceiver* r = transientReceivers->ObjectAt(i); if (r->GetParent() == orig) { transientExists = true; } } } if (!transientExists) { // Make sure the elements which are removed from the // subtree are kept in the same observation set. nsMutationReceiver* tr; if (orig->Animations()) { tr = nsAnimationReceiver::Create(aChild, orig); } else { tr = nsMutationReceiver::Create(aChild, orig); } transientReceivers->AppendObject(tr); } } } if (ChildList() && (Subtree() || parent == Target())) { nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::childList); if (m->mTarget) { // Already handled case. return; } MOZ_ASSERT(parent); m->mTarget = parent; m->mRemovedNodes = new nsSimpleContentList(parent); m->mRemovedNodes->AppendElement(aChild); m->mPreviousSibling = aPreviousSibling; m->mNextSibling = aPreviousSibling ? aPreviousSibling->GetNextSibling() : parent->GetFirstChild(); } // We need to schedule always, so that after microtask mTransientReceivers // can be cleared correctly. Observer()->ScheduleForRun(); } void nsMutationReceiver::NodeWillBeDestroyed(nsINode* aNode) { NS_ASSERTION(!mParent, "Shouldn't have mParent here!"); Disconnect(true); } void nsAnimationReceiver::RecordAnimationMutation( Animation* aAnimation, AnimationMutation aMutationType) { mozilla::dom::AnimationEffect* effect = aAnimation->GetEffect(); if (!effect) { return; } mozilla::dom::KeyframeEffect* keyframeEffect = effect->AsKeyframeEffect(); if (!keyframeEffect) { return; } NonOwningAnimationTarget animationTarget = keyframeEffect->GetAnimationTarget(); if (!animationTarget) { return; } Element* elem = animationTarget.mElement; if (!Animations() || !(Subtree() || elem == Target()) || elem->ChromeOnlyAccess()) { return; } // Record animations targeting to a pseudo element only when subtree is true. if (animationTarget.mPseudoType != PseudoStyleType::NotPseudo && !Subtree()) { return; } if (nsAutoAnimationMutationBatch::IsBatching()) { switch (aMutationType) { case eAnimationMutation_Added: nsAutoAnimationMutationBatch::AnimationAdded(aAnimation, elem); break; case eAnimationMutation_Changed: nsAutoAnimationMutationBatch::AnimationChanged(aAnimation, elem); break; case eAnimationMutation_Removed: nsAutoAnimationMutationBatch::AnimationRemoved(aAnimation, elem); break; } nsAutoAnimationMutationBatch::AddObserver(Observer()); return; } nsDOMMutationRecord* m = Observer()->CurrentRecord(nsGkAtoms::animations); NS_ASSERTION(!m->mTarget, "Wrong target!"); m->mTarget = elem; switch (aMutationType) { case eAnimationMutation_Added: m->mAddedAnimations.AppendElement(aAnimation); break; case eAnimationMutation_Changed: m->mChangedAnimations.AppendElement(aAnimation); break; case eAnimationMutation_Removed: m->mRemovedAnimations.AppendElement(aAnimation); break; } } void nsAnimationReceiver::AnimationAdded(Animation* aAnimation) { RecordAnimationMutation(aAnimation, eAnimationMutation_Added); } void nsAnimationReceiver::AnimationChanged(Animation* aAnimation) { RecordAnimationMutation(aAnimation, eAnimationMutation_Changed); } void nsAnimationReceiver::AnimationRemoved(Animation* aAnimation) { RecordAnimationMutation(aAnimation, eAnimationMutation_Removed); } NS_IMPL_ISUPPORTS_INHERITED(nsAnimationReceiver, nsMutationReceiver, nsIAnimationObserver) // Observer NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMMutationObserver) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY(nsDOMMutationObserver) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMutationObserver) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMutationObserver) NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMMutationObserver) NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDOMMutationObserver) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMMutationObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) for (int32_t i = 0; i < tmp->mReceivers.Count(); ++i) { tmp->mReceivers[i]->Disconnect(false); } tmp->mReceivers.Clear(); tmp->ClearPendingRecords(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) // No need to handle mTransientReceivers NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMMutationObserver) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReceivers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFirstPendingMutation) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) // No need to handle mTransientReceivers NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END nsMutationReceiver* nsDOMMutationObserver::GetReceiverFor( nsINode* aNode, bool aMayCreate, bool aWantsAnimations) { MOZ_ASSERT(aMayCreate || !aWantsAnimations, "the value of aWantsAnimations doesn't matter when aMayCreate is " "false, so just pass in false for it"); if (!aMayCreate && !aNode->MayHaveDOMMutationObserver()) { return nullptr; } for (int32_t i = 0; i < mReceivers.Count(); ++i) { if (mReceivers[i]->Target() == aNode) { return mReceivers[i]; } } if (!aMayCreate) { return nullptr; } nsMutationReceiver* r; if (aWantsAnimations) { r = nsAnimationReceiver::Create(aNode, this); } else { r = nsMutationReceiver::Create(aNode, this); } mReceivers.AppendObject(r); return r; } void nsDOMMutationObserver::RemoveReceiver(nsMutationReceiver* aReceiver) { mReceivers.RemoveObject(aReceiver); } void nsDOMMutationObserver::GetAllSubtreeObserversFor( nsINode* aNode, nsTArray& aReceivers) { nsINode* n = aNode; while (n) { if (n->MayHaveDOMMutationObserver()) { nsMutationReceiver* r = GetReceiverFor(n, false, false); if (r && r->Subtree() && !aReceivers.Contains(r)) { aReceivers.AppendElement(r); // If we've found all the receivers the observer has, // no need to search for more. if (mReceivers.Count() == int32_t(aReceivers.Length())) { return; } } nsCOMArray* transientReceivers = nullptr; if (mTransientReceivers.Get(n, &transientReceivers) && transientReceivers) { for (int32_t i = 0; i < transientReceivers->Count(); ++i) { nsMutationReceiver* r = transientReceivers->ObjectAt(i); nsMutationReceiver* parent = r->GetParent(); if (r->Subtree() && parent && !aReceivers.Contains(parent)) { aReceivers.AppendElement(parent); } } if (mReceivers.Count() == int32_t(aReceivers.Length())) { return; } } } n = n->GetParentNode(); } } void nsDOMMutationObserver::ScheduleForRun() { nsDOMMutationObserver::AddCurrentlyHandlingObserver(this, sMutationLevel); if (mWaitingForRun) { return; } mWaitingForRun = true; RescheduleForRun(); } class MutationObserverMicroTask final : public MicroTaskRunnable { public: MOZ_CAN_RUN_SCRIPT virtual void Run(AutoSlowOperation& aAso) override { nsDOMMutationObserver::HandleMutations(aAso); } virtual bool Suppressed() override { return nsDOMMutationObserver::AllScheduledMutationObserversAreSuppressed(); } }; /* static */ void nsDOMMutationObserver::QueueMutationObserverMicroTask() { CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); if (!ccjs) { return; } RefPtr momt = new MutationObserverMicroTask(); ccjs->DispatchToMicroTask(momt.forget()); } void nsDOMMutationObserver::HandleMutations(mozilla::AutoSlowOperation& aAso) { if (sScheduledMutationObservers || mozilla::dom::DocGroup::sPendingDocGroups) { HandleMutationsInternal(aAso); } } void nsDOMMutationObserver::RescheduleForRun() { if (!sScheduledMutationObservers) { CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); if (!ccjs) { return; } RefPtr momt = new MutationObserverMicroTask(); ccjs->DispatchToMicroTask(momt.forget()); sScheduledMutationObservers = new AutoTArray, 4>; } bool didInsert = false; for (uint32_t i = 0; i < sScheduledMutationObservers->Length(); ++i) { if (static_cast((*sScheduledMutationObservers)[i]) ->mId > mId) { sScheduledMutationObservers->InsertElementAt(i, this); didInsert = true; break; } } if (!didInsert) { sScheduledMutationObservers->AppendElement(this); } } void nsDOMMutationObserver::Observe( nsINode& aTarget, const mozilla::dom::MutationObserverInit& aOptions, nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& aRv) { bool childList = aOptions.mChildList; bool attributes = aOptions.mAttributes.WasPassed() && aOptions.mAttributes.Value(); bool characterData = aOptions.mCharacterData.WasPassed() && aOptions.mCharacterData.Value(); bool subtree = aOptions.mSubtree; bool attributeOldValue = aOptions.mAttributeOldValue.WasPassed() && aOptions.mAttributeOldValue.Value(); bool nativeAnonymousChildList = aOptions.mNativeAnonymousChildList; bool characterDataOldValue = aOptions.mCharacterDataOldValue.WasPassed() && aOptions.mCharacterDataOldValue.Value(); bool animations = aOptions.mAnimations; if (!aOptions.mAttributes.WasPassed() && (aOptions.mAttributeOldValue.WasPassed() || aOptions.mAttributeFilter.WasPassed())) { attributes = true; } if (!aOptions.mCharacterData.WasPassed() && aOptions.mCharacterDataOldValue.WasPassed()) { characterData = true; } if (!(childList || attributes || characterData || animations || nativeAnonymousChildList)) { aRv.ThrowTypeError( "One of 'childList', 'attributes', 'characterData' must not be false."); return; } if (aOptions.mAttributeOldValue.WasPassed() && aOptions.mAttributeOldValue.Value() && !attributes) { aRv.ThrowTypeError( "If 'attributeOldValue' is true, 'attributes' must not be false."); return; } if (aOptions.mAttributeFilter.WasPassed() && !attributes) { aRv.ThrowTypeError( "If 'attributesFilter' is present, 'attributes' must not be false."); return; } if (aOptions.mCharacterDataOldValue.WasPassed() && aOptions.mCharacterDataOldValue.Value() && !characterData) { aRv.ThrowTypeError( "If 'characterDataOldValue' is true, 'characterData' must not be " "false."); return; } nsTArray> filters; bool allAttrs = true; if (aOptions.mAttributeFilter.WasPassed()) { allAttrs = false; const mozilla::dom::Sequence& filtersAsString = aOptions.mAttributeFilter.Value(); uint32_t len = filtersAsString.Length(); filters.SetCapacity(len); for (uint32_t i = 0; i < len; ++i) { filters.AppendElement(NS_Atomize(filtersAsString[i])); } } nsMutationReceiver* r = GetReceiverFor(&aTarget, true, animations); r->SetChildList(childList); r->SetAttributes(attributes); r->SetCharacterData(characterData); r->SetSubtree(subtree); r->SetAttributeOldValue(attributeOldValue); r->SetCharacterDataOldValue(characterDataOldValue); r->SetNativeAnonymousChildList(nativeAnonymousChildList); r->SetAttributeFilter(std::move(filters)); r->SetAllAttributes(allAttrs); r->SetAnimations(animations); r->RemoveClones(); if (!aSubjectPrincipal.IsSystemPrincipal() && !aSubjectPrincipal.GetIsAddonOrExpandedAddonPrincipal()) { if (nsPIDOMWindowInner* window = aTarget.OwnerDoc()->GetInnerWindow()) { window->SetMutationObserverHasObservedNodeForTelemetry(); } } #ifdef DEBUG for (int32_t i = 0; i < mReceivers.Count(); ++i) { NS_WARNING_ASSERTION(mReceivers[i]->Target(), "All the receivers should have a target!"); } #endif } void nsDOMMutationObserver::Disconnect() { for (int32_t i = 0; i < mReceivers.Count(); ++i) { mReceivers[i]->Disconnect(false); } mReceivers.Clear(); mCurrentMutations.Clear(); ClearPendingRecords(); } void nsDOMMutationObserver::TakeRecords( nsTArray>& aRetVal) { aRetVal.Clear(); aRetVal.SetCapacity(mPendingMutationCount); RefPtr current; current.swap(mFirstPendingMutation); for (uint32_t i = 0; i < mPendingMutationCount; ++i) { RefPtr next; current->mNext.swap(next); if (!mMergeAttributeRecords || !MergeableAttributeRecord(aRetVal.SafeLastElement(nullptr), current)) { *aRetVal.AppendElement() = std::move(current); } current.swap(next); } ClearPendingRecords(); } void nsDOMMutationObserver::GetObservingInfo( nsTArray>& aResult, mozilla::ErrorResult& aRv) { aResult.SetCapacity(mReceivers.Count()); for (int32_t i = 0; i < mReceivers.Count(); ++i) { MutationObservingInfo& info = aResult.AppendElement()->SetValue(); nsMutationReceiver* mr = mReceivers[i]; info.mChildList = mr->ChildList(); info.mAttributes.Construct(mr->Attributes()); info.mCharacterData.Construct(mr->CharacterData()); info.mSubtree = mr->Subtree(); info.mAttributeOldValue.Construct(mr->AttributeOldValue()); info.mCharacterDataOldValue.Construct(mr->CharacterDataOldValue()); info.mNativeAnonymousChildList = mr->NativeAnonymousChildList(); info.mAnimations = mr->Animations(); nsTArray>& filters = mr->AttributeFilter(); if (filters.Length()) { info.mAttributeFilter.Construct(); mozilla::dom::Sequence& filtersAsStrings = info.mAttributeFilter.Value(); nsString* strings = filtersAsStrings.AppendElements(filters.Length(), mozilla::fallible); if (!strings) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } for (size_t j = 0; j < filters.Length(); ++j) { filters[j]->ToString(strings[j]); } } info.mObservedNode = mr->Target(); } } // static already_AddRefed nsDOMMutationObserver::Constructor( const mozilla::dom::GlobalObject& aGlobal, mozilla::dom::MutationCallback& aCb, mozilla::ErrorResult& aRv) { nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); if (!window) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } bool isChrome = nsContentUtils::IsChromeDoc(window->GetExtantDoc()); RefPtr observer = new nsDOMMutationObserver(std::move(window), aCb, isChrome); return observer.forget(); } bool nsDOMMutationObserver::MergeableAttributeRecord( nsDOMMutationRecord* aOldRecord, nsDOMMutationRecord* aRecord) { MOZ_ASSERT(mMergeAttributeRecords); return aOldRecord && aOldRecord->mType == nsGkAtoms::attributes && aOldRecord->mType == aRecord->mType && aOldRecord->mTarget == aRecord->mTarget && aOldRecord->mAttrName == aRecord->mAttrName && aOldRecord->mAttrNamespace.Equals(aRecord->mAttrNamespace); } void nsDOMMutationObserver::HandleMutation() { NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "Whaat!"); NS_ASSERTION(mCurrentMutations.IsEmpty(), "Still generating MutationRecords?"); mWaitingForRun = false; for (int32_t i = 0; i < mReceivers.Count(); ++i) { mReceivers[i]->RemoveClones(); } mTransientReceivers.Clear(); nsPIDOMWindowOuter* outer = mOwner->GetOuterWindow(); if (!mPendingMutationCount || !outer || outer->GetCurrentInnerWindow() != mOwner) { ClearPendingRecords(); return; } mozilla::dom::Sequence> mutations; if (mutations.SetCapacity(mPendingMutationCount, mozilla::fallible)) { // We can't use TakeRecords easily here, because it deals with a // different type of array, and we want to optimize out any extra copying. RefPtr current; current.swap(mFirstPendingMutation); for (uint32_t i = 0; i < mPendingMutationCount; ++i) { RefPtr next; current->mNext.swap(next); if (!mMergeAttributeRecords || !MergeableAttributeRecord( mutations.Length() ? mutations.LastElement().get() : nullptr, current)) { *mutations.AppendElement(mozilla::fallible) = current; } current.swap(next); } } ClearPendingRecords(); RefPtr callback(mCallback); callback->Call(this, mutations, *this); } void nsDOMMutationObserver::HandleMutationsInternal(AutoSlowOperation& aAso) { nsTArray>* suppressedObservers = nullptr; // This loop implements: // * Let signalList be a copy of unit of related similar-origin browsing // contexts' signal slot list. // * Empty unit of related similar-origin browsing contexts' signal slot // list. nsTArray>> signalLists; if (DocGroup::sPendingDocGroups) { signalLists.SetCapacity(DocGroup::sPendingDocGroups->Length()); for (DocGroup* docGroup : *DocGroup::sPendingDocGroups) { signalLists.AppendElement(docGroup->MoveSignalSlotList()); } delete DocGroup::sPendingDocGroups; DocGroup::sPendingDocGroups = nullptr; } if (sScheduledMutationObservers) { AutoTArray, 4>* observers = sScheduledMutationObservers; sScheduledMutationObservers = nullptr; for (uint32_t i = 0; i < observers->Length(); ++i) { RefPtr currentObserver = static_cast((*observers)[i]); if (!currentObserver->Suppressed()) { currentObserver->HandleMutation(); } else { if (!suppressedObservers) { suppressedObservers = new nsTArray>; } if (!suppressedObservers->Contains(currentObserver)) { suppressedObservers->AppendElement(currentObserver); } } } delete observers; aAso.CheckForInterrupt(); } if (suppressedObservers) { for (uint32_t i = 0; i < suppressedObservers->Length(); ++i) { static_cast(suppressedObservers->ElementAt(i)) ->RescheduleForRun(); } delete suppressedObservers; suppressedObservers = nullptr; } // Fire slotchange event for each slot in signalLists. for (const nsTArray>& signalList : signalLists) { for (const RefPtr& signal : signalList) { signal->FireSlotChangeEvent(); } } } nsDOMMutationRecord* nsDOMMutationObserver::CurrentRecord(nsAtom* aType) { NS_ASSERTION(sMutationLevel > 0, "Unexpected mutation level!"); while (mCurrentMutations.Length() < sMutationLevel) { mCurrentMutations.AppendElement(static_cast(nullptr)); } uint32_t last = sMutationLevel - 1; if (!mCurrentMutations[last]) { RefPtr r = new nsDOMMutationRecord(aType, GetParentObject()); mCurrentMutations[last] = r; AppendMutationRecord(r.forget()); ScheduleForRun(); } #ifdef DEBUG MOZ_ASSERT(sCurrentlyHandlingObservers->Length() == sMutationLevel); for (size_t i = 0; i < sCurrentlyHandlingObservers->Length(); ++i) { MOZ_ASSERT(sCurrentlyHandlingObservers->ElementAt(i).Contains(this), "MutationObserver should be added as an observer of all the " "nested mutations!"); } #endif NS_ASSERTION(mCurrentMutations[last]->mType == aType, "Unexpected MutationRecord type!"); return mCurrentMutations[last]; } nsDOMMutationObserver::~nsDOMMutationObserver() { for (int32_t i = 0; i < mReceivers.Count(); ++i) { mReceivers[i]->RemoveClones(); } } void nsDOMMutationObserver::EnterMutationHandling() { ++sMutationLevel; } // Leave the current mutation level (there can be several levels if in case // of nested calls to the nsIMutationObserver methods). // The most recent mutation record is removed from mCurrentMutations, so // that is doesn't get modified anymore by receivers. void nsDOMMutationObserver::LeaveMutationHandling() { if (sCurrentlyHandlingObservers && sCurrentlyHandlingObservers->Length() == sMutationLevel) { nsTArray> obs = sCurrentlyHandlingObservers->PopLastElement(); for (uint32_t i = 0; i < obs.Length(); ++i) { nsDOMMutationObserver* o = static_cast(obs[i]); if (o->mCurrentMutations.Length() == sMutationLevel) { // It is already in pending mutations. o->mCurrentMutations.RemoveLastElement(); } } } --sMutationLevel; } void nsDOMMutationObserver::AddCurrentlyHandlingObserver( nsDOMMutationObserver* aObserver, uint32_t aMutationLevel) { NS_ASSERTION(aMutationLevel > 0, "Unexpected mutation level!"); if (aMutationLevel > 1) { // MutationObserver must be in the currently handling observer list // in all the nested levels. AddCurrentlyHandlingObserver(aObserver, aMutationLevel - 1); } if (!sCurrentlyHandlingObservers) { sCurrentlyHandlingObservers = new AutoTArray, 4>, 4>; } while (sCurrentlyHandlingObservers->Length() < aMutationLevel) { sCurrentlyHandlingObservers->InsertElementAt( sCurrentlyHandlingObservers->Length()); } uint32_t index = aMutationLevel - 1; if (!sCurrentlyHandlingObservers->ElementAt(index).Contains(aObserver)) { sCurrentlyHandlingObservers->ElementAt(index).AppendElement(aObserver); } } void nsDOMMutationObserver::Shutdown() { delete sCurrentlyHandlingObservers; sCurrentlyHandlingObservers = nullptr; delete sScheduledMutationObservers; sScheduledMutationObservers = nullptr; } nsAutoMutationBatch* nsAutoMutationBatch::sCurrentBatch = nullptr; void nsAutoMutationBatch::Done() { if (sCurrentBatch != this) { return; } sCurrentBatch = mPreviousBatch; if (mObservers.IsEmpty()) { nsDOMMutationObserver::LeaveMutationHandling(); // Nothing to do. return; } uint32_t len = mObservers.Length(); for (uint32_t i = 0; i < len; ++i) { nsDOMMutationObserver* ob = mObservers[i].mObserver; bool wantsChildList = mObservers[i].mWantsChildList; RefPtr removedList; if (wantsChildList) { removedList = new nsSimpleContentList(mBatchTarget); } nsTArray allObservers; ob->GetAllSubtreeObserversFor(mBatchTarget, allObservers); int32_t j = mFromFirstToLast ? 0 : mRemovedNodes.Length() - 1; int32_t end = mFromFirstToLast ? mRemovedNodes.Length() : -1; for (; j != end; mFromFirstToLast ? ++j : --j) { nsCOMPtr removed = mRemovedNodes[j]; if (removedList) { removedList->AppendElement(removed); } if (allObservers.Length()) { auto* const transientReceivers = ob->mTransientReceivers.GetOrInsertNew(removed); for (uint32_t k = 0; k < allObservers.Length(); ++k) { nsMutationReceiver* r = allObservers[k]; nsMutationReceiver* orig = r->GetParent() ? r->GetParent() : r; if (ob->GetReceiverFor(removed, false, false) != orig) { // Make sure the elements which are removed from the // subtree are kept in the same observation set. nsMutationReceiver* tr; if (orig->Animations()) { tr = nsAnimationReceiver::Create(removed, orig); } else { tr = nsMutationReceiver::Create(removed, orig); } transientReceivers->AppendObject(tr); } } } } if (wantsChildList && (mRemovedNodes.Length() || mAddedNodes.Length())) { RefPtr addedList = new nsSimpleContentList(mBatchTarget); for (uint32_t i = 0; i < mAddedNodes.Length(); ++i) { addedList->AppendElement(mAddedNodes[i]); } RefPtr m = new nsDOMMutationRecord(nsGkAtoms::childList, ob->GetParentObject()); m->mTarget = mBatchTarget; m->mRemovedNodes = removedList; m->mAddedNodes = addedList; m->mPreviousSibling = mPrevSibling; m->mNextSibling = mNextSibling; ob->AppendMutationRecord(m.forget()); } // Always schedule the observer so that transient receivers are // removed correctly. ob->ScheduleForRun(); } nsDOMMutationObserver::LeaveMutationHandling(); } nsAutoAnimationMutationBatch* nsAutoAnimationMutationBatch::sCurrentBatch = nullptr; void nsAutoAnimationMutationBatch::Done() { if (sCurrentBatch != this) { return; } sCurrentBatch = nullptr; if (mObservers.IsEmpty()) { nsDOMMutationObserver::LeaveMutationHandling(); // Nothing to do. return; } mBatchTargets.Sort(TreeOrderComparator()); for (nsDOMMutationObserver* ob : mObservers) { bool didAddRecords = false; for (nsINode* target : mBatchTargets) { EntryArray* entries = mEntryTable.Get(target); MOZ_ASSERT(entries, "Targets in entry table and targets list should match"); RefPtr m = new nsDOMMutationRecord(nsGkAtoms::animations, ob->GetParentObject()); m->mTarget = target; for (const Entry& e : *entries) { if (e.mState == eState_Added) { m->mAddedAnimations.AppendElement(e.mAnimation); } else if (e.mState == eState_Removed) { m->mRemovedAnimations.AppendElement(e.mAnimation); } else if (e.mState == eState_RemainedPresent && e.mChanged) { m->mChangedAnimations.AppendElement(e.mAnimation); } } if (!m->mAddedAnimations.IsEmpty() || !m->mChangedAnimations.IsEmpty() || !m->mRemovedAnimations.IsEmpty()) { ob->AppendMutationRecord(m.forget()); didAddRecords = true; } } if (didAddRecords) { ob->ScheduleForRun(); } } nsDOMMutationObserver::LeaveMutationHandling(); }