mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-01 00:32:11 +00:00
e1b7b99daf
Differential Revision: https://phabricator.services.mozilla.com/D141845
872 lines
27 KiB
C++
872 lines
27 KiB
C++
/* -*- 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/. */
|
|
|
|
#ifndef nsDOMMutationObserver_h
|
|
#define nsDOMMutationObserver_h
|
|
|
|
#include <utility>
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/dom/Animation.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/dom/MutationEventBinding.h"
|
|
#include "mozilla/dom/MutationObserverBinding.h"
|
|
#include "mozilla/dom/Nullable.h"
|
|
#include "nsCOMArray.h"
|
|
#include "nsClassHashtable.h"
|
|
#include "nsContentList.h"
|
|
#include "nsCycleCollectionParticipant.h"
|
|
#include "nsGlobalWindow.h"
|
|
#include "nsIAnimationObserver.h"
|
|
#include "nsIScriptContext.h"
|
|
#include "nsIVariant.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsStubAnimationObserver.h"
|
|
#include "nsTArray.h"
|
|
#include "nsWrapperCache.h"
|
|
|
|
class nsIPrincipal;
|
|
|
|
class nsDOMMutationObserver;
|
|
using mozilla::dom::MutationObservingInfo;
|
|
|
|
namespace mozilla::dom {
|
|
class Element;
|
|
}
|
|
|
|
class nsDOMMutationRecord final : public nsISupports, public nsWrapperCache {
|
|
virtual ~nsDOMMutationRecord() = default;
|
|
|
|
public:
|
|
using AnimationArray = nsTArray<RefPtr<mozilla::dom::Animation>>;
|
|
|
|
nsDOMMutationRecord(nsAtom* aType, nsISupports* aOwner)
|
|
: mType(aType),
|
|
mAttrNamespace(VoidString()),
|
|
mPrevValue(VoidString()),
|
|
mOwner(aOwner) {}
|
|
|
|
nsISupports* GetParentObject() const { return mOwner; }
|
|
|
|
virtual JSObject* WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) override {
|
|
return mozilla::dom::MutationRecord_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
|
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationRecord)
|
|
|
|
void GetType(mozilla::dom::DOMString& aRetVal) const {
|
|
aRetVal.SetKnownLiveAtom(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.SetKnownLiveAtom(mAttrName,
|
|
mozilla::dom::DOMString::eTreatNullAsNull);
|
|
}
|
|
|
|
void GetAttributeNamespace(mozilla::dom::DOMString& aRetVal) const {
|
|
aRetVal.SetKnownLiveString(mAttrNamespace);
|
|
}
|
|
|
|
void GetOldValue(mozilla::dom::DOMString& aRetVal) const {
|
|
aRetVal.SetKnownLiveString(mPrevValue);
|
|
}
|
|
|
|
void GetAddedAnimations(AnimationArray& aRetVal) const {
|
|
aRetVal = mAddedAnimations.Clone();
|
|
}
|
|
|
|
void GetRemovedAnimations(AnimationArray& aRetVal) const {
|
|
aRetVal = mRemovedAnimations.Clone();
|
|
}
|
|
|
|
void GetChangedAnimations(AnimationArray& aRetVal) const {
|
|
aRetVal = mChangedAnimations.Clone();
|
|
}
|
|
|
|
nsCOMPtr<nsINode> mTarget;
|
|
RefPtr<nsAtom> mType;
|
|
RefPtr<nsAtom> mAttrName;
|
|
nsString mAttrNamespace;
|
|
nsString mPrevValue;
|
|
RefPtr<nsSimpleContentList> mAddedNodes;
|
|
RefPtr<nsSimpleContentList> mRemovedNodes;
|
|
nsCOMPtr<nsINode> mPreviousSibling;
|
|
nsCOMPtr<nsINode> mNextSibling;
|
|
AnimationArray mAddedAnimations;
|
|
AnimationArray mRemovedAnimations;
|
|
AnimationArray mChangedAnimations;
|
|
|
|
RefPtr<nsDOMMutationRecord> mNext;
|
|
nsCOMPtr<nsISupports> mOwner;
|
|
};
|
|
|
|
// Base class just prevents direct access to
|
|
// members to make sure we go through getters/setters.
|
|
class nsMutationReceiverBase : public nsStubAnimationObserver {
|
|
public:
|
|
virtual ~nsMutationReceiverBase() = default;
|
|
|
|
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 NativeAnonymousChildList() {
|
|
return mParent ? mParent->NativeAnonymousChildList()
|
|
: mNativeAnonymousChildList;
|
|
}
|
|
void SetNativeAnonymousChildList(bool aOldValue) {
|
|
NS_ASSERTION(!mParent, "Shouldn't have parent");
|
|
mNativeAnonymousChildList = 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 Animations() { return mParent ? mParent->Animations() : mAnimations; }
|
|
void SetAnimations(bool aAnimations) {
|
|
NS_ASSERTION(!mParent, "Shouldn't have parent");
|
|
mAnimations = aAnimations;
|
|
}
|
|
|
|
bool AttributeOldValue() {
|
|
return mParent ? mParent->AttributeOldValue() : mAttributeOldValue;
|
|
}
|
|
void SetAttributeOldValue(bool aOldValue) {
|
|
NS_ASSERTION(!mParent, "Shouldn't have parent");
|
|
mAttributeOldValue = aOldValue;
|
|
}
|
|
|
|
nsTArray<RefPtr<nsAtom>>& AttributeFilter() { return mAttributeFilter; }
|
|
void SetAttributeFilter(nsTArray<RefPtr<nsAtom>>&& aFilter) {
|
|
NS_ASSERTION(!mParent, "Shouldn't have parent");
|
|
mAttributeFilter.Clear();
|
|
mAttributeFilter = std::move(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),
|
|
mSubtree(false),
|
|
mChildList(false),
|
|
mCharacterData(false),
|
|
mCharacterDataOldValue(false),
|
|
mNativeAnonymousChildList(false),
|
|
mAttributes(false),
|
|
mAllAttributes(false),
|
|
mAttributeOldValue(false),
|
|
mAnimations(false) {}
|
|
|
|
nsMutationReceiverBase(nsINode* aRegisterTarget,
|
|
nsMutationReceiverBase* aParent)
|
|
: mTarget(nullptr),
|
|
mObserver(nullptr),
|
|
mParent(aParent),
|
|
mRegisterTarget(aRegisterTarget),
|
|
mKungFuDeathGrip(aParent->Target()),
|
|
mSubtree(false),
|
|
mChildList(false),
|
|
mCharacterData(false),
|
|
mCharacterDataOldValue(false),
|
|
mNativeAnonymousChildList(false),
|
|
mAttributes(false),
|
|
mAllAttributes(false),
|
|
mAttributeOldValue(false),
|
|
mAnimations(false) {
|
|
NS_ASSERTION(mParent->Subtree(), "Should clone a non-subtree observer!");
|
|
}
|
|
|
|
virtual void AddMutationObserver() = 0;
|
|
|
|
void AddObserver() {
|
|
AddMutationObserver();
|
|
mRegisterTarget->SetMayHaveDOMMutationObserver();
|
|
mRegisterTarget->OwnerDoc()->SetMayHaveDOMMutationObservers();
|
|
}
|
|
|
|
bool IsObservable(nsIContent* aContent);
|
|
|
|
bool ObservesAttr(nsINode* aRegisterTarget, mozilla::dom::Element* aElement,
|
|
int32_t aNameSpaceID, nsAtom* aAttr);
|
|
|
|
// The target for the MutationObserver.observe() method.
|
|
nsINode* mTarget;
|
|
nsDOMMutationObserver* mObserver;
|
|
RefPtr<nsMutationReceiverBase> 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<nsMutationReceiverBase> 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<nsINode> mKungFuDeathGrip;
|
|
|
|
private:
|
|
bool mSubtree;
|
|
bool mChildList;
|
|
bool mCharacterData;
|
|
bool mCharacterDataOldValue;
|
|
bool mNativeAnonymousChildList;
|
|
bool mAttributes;
|
|
bool mAllAttributes;
|
|
bool mAttributeOldValue;
|
|
bool mAnimations;
|
|
nsTArray<RefPtr<nsAtom>> mAttributeFilter;
|
|
};
|
|
|
|
class nsMutationReceiver : public nsMutationReceiverBase {
|
|
protected:
|
|
virtual ~nsMutationReceiver() { Disconnect(false); }
|
|
|
|
public:
|
|
static nsMutationReceiver* Create(nsINode* aTarget,
|
|
nsDOMMutationObserver* aObserver) {
|
|
nsMutationReceiver* r = new nsMutationReceiver(aTarget, aObserver);
|
|
r->AddObserver();
|
|
return r;
|
|
}
|
|
|
|
static nsMutationReceiver* Create(nsINode* aRegisterTarget,
|
|
nsMutationReceiverBase* aParent) {
|
|
nsMutationReceiver* r = new nsMutationReceiver(aRegisterTarget, aParent);
|
|
aParent->AddClone(r);
|
|
r->AddObserver();
|
|
return r;
|
|
}
|
|
|
|
nsMutationReceiver* GetParent() {
|
|
return static_cast<nsMutationReceiver*>(mParent.get());
|
|
}
|
|
|
|
void RemoveClones() {
|
|
for (int32_t i = 0; i < mTransientReceivers.Count(); ++i) {
|
|
nsMutationReceiver* r =
|
|
static_cast<nsMutationReceiver*>(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_ISUPPORTS
|
|
|
|
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
|
|
NS_DECL_NSIMUTATIONOBSERVER_NATIVEANONYMOUSCHILDLISTCHANGE
|
|
NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
|
|
NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
|
|
NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
|
|
NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
|
|
NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
|
|
|
|
virtual void AttributeSetToCurrentValue(mozilla::dom::Element* aElement,
|
|
int32_t aNameSpaceID,
|
|
nsAtom* aAttribute) override {
|
|
// We can reuse AttributeWillChange implementation.
|
|
AttributeWillChange(aElement, aNameSpaceID, aAttribute,
|
|
mozilla::dom::MutationEvent_Binding::MODIFICATION);
|
|
}
|
|
|
|
protected:
|
|
nsMutationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver);
|
|
|
|
nsMutationReceiver(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent)
|
|
: nsMutationReceiverBase(aRegisterTarget, aParent) {
|
|
NS_ASSERTION(!static_cast<nsMutationReceiver*>(aParent)->GetParent(),
|
|
"Shouldn't create deep observer hierarchies!");
|
|
}
|
|
|
|
virtual void AddMutationObserver() override {
|
|
mRegisterTarget->AddMutationObserver(this);
|
|
}
|
|
};
|
|
|
|
class nsAnimationReceiver : public nsMutationReceiver {
|
|
public:
|
|
static nsAnimationReceiver* Create(nsINode* aTarget,
|
|
nsDOMMutationObserver* aObserver) {
|
|
nsAnimationReceiver* r = new nsAnimationReceiver(aTarget, aObserver);
|
|
r->AddObserver();
|
|
return r;
|
|
}
|
|
|
|
static nsAnimationReceiver* Create(nsINode* aRegisterTarget,
|
|
nsMutationReceiverBase* aParent) {
|
|
nsAnimationReceiver* r = new nsAnimationReceiver(aRegisterTarget, aParent);
|
|
aParent->AddClone(r);
|
|
r->AddObserver();
|
|
return r;
|
|
}
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONADDED
|
|
NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONCHANGED
|
|
NS_DECL_NSIANIMATIONOBSERVER_ANIMATIONREMOVED
|
|
|
|
protected:
|
|
virtual ~nsAnimationReceiver() = default;
|
|
|
|
nsAnimationReceiver(nsINode* aTarget, nsDOMMutationObserver* aObserver)
|
|
: nsMutationReceiver(aTarget, aObserver) {}
|
|
|
|
nsAnimationReceiver(nsINode* aRegisterTarget, nsMutationReceiverBase* aParent)
|
|
: nsMutationReceiver(aRegisterTarget, aParent) {}
|
|
|
|
virtual void AddMutationObserver() override {
|
|
mRegisterTarget->AddAnimationObserver(this);
|
|
}
|
|
|
|
private:
|
|
enum AnimationMutation {
|
|
eAnimationMutation_Added,
|
|
eAnimationMutation_Changed,
|
|
eAnimationMutation_Removed
|
|
};
|
|
|
|
void RecordAnimationMutation(mozilla::dom::Animation* aAnimation,
|
|
AnimationMutation aMutationType);
|
|
};
|
|
|
|
#define NS_DOM_MUTATION_OBSERVER_IID \
|
|
{ \
|
|
0x0c3b91f8, 0xcc3b, 0x4b08, { \
|
|
0x9e, 0xab, 0x07, 0x47, 0xa9, 0xe4, 0x65, 0xb4 \
|
|
} \
|
|
}
|
|
|
|
class nsDOMMutationObserver final : public nsISupports, public nsWrapperCache {
|
|
public:
|
|
nsDOMMutationObserver(nsCOMPtr<nsPIDOMWindowInner>&& aOwner,
|
|
mozilla::dom::MutationCallback& aCb, bool aChrome)
|
|
: mOwner(std::move(aOwner)),
|
|
mLastPendingMutation(nullptr),
|
|
mPendingMutationCount(0),
|
|
mCallback(&aCb),
|
|
mWaitingForRun(false),
|
|
mIsChrome(aChrome),
|
|
mMergeAttributeRecords(false),
|
|
mId(++sCount) {}
|
|
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<nsDOMMutationObserver> Constructor(
|
|
const mozilla::dom::GlobalObject& aGlobal,
|
|
mozilla::dom::MutationCallback& aCb, mozilla::ErrorResult& aRv);
|
|
|
|
virtual JSObject* WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) override {
|
|
return mozilla::dom::MutationObserver_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
nsISupports* GetParentObject() const { return mOwner; }
|
|
|
|
bool IsChrome() { return mIsChrome; }
|
|
|
|
void Observe(nsINode& aTarget,
|
|
const mozilla::dom::MutationObserverInit& aOptions,
|
|
nsIPrincipal& aSubjectPrincipal, mozilla::ErrorResult& aRv);
|
|
|
|
void Disconnect();
|
|
|
|
void TakeRecords(nsTArray<RefPtr<nsDOMMutationRecord>>& aRetVal);
|
|
|
|
MOZ_CAN_RUN_SCRIPT void HandleMutation();
|
|
|
|
void GetObservingInfo(
|
|
nsTArray<mozilla::dom::Nullable<MutationObservingInfo>>& aResult,
|
|
mozilla::ErrorResult& aRv);
|
|
|
|
mozilla::dom::MutationCallback* MutationCallback() { return mCallback; }
|
|
|
|
bool MergeAttributeRecords() { return mMergeAttributeRecords; }
|
|
|
|
void SetMergeAttributeRecords(bool aVal) { mMergeAttributeRecords = aVal; }
|
|
|
|
// If both records are for 'attributes' type and for the same target and
|
|
// attribute name and namespace are the same, we can skip the newer record.
|
|
// aOldRecord->mPrevValue holds the original value, if observed.
|
|
bool MergeableAttributeRecord(nsDOMMutationRecord* aOldRecord,
|
|
nsDOMMutationRecord* aRecord);
|
|
|
|
void AppendMutationRecord(already_AddRefed<nsDOMMutationRecord> aRecord) {
|
|
RefPtr<nsDOMMutationRecord> record = aRecord;
|
|
MOZ_ASSERT(record);
|
|
if (!mLastPendingMutation) {
|
|
MOZ_ASSERT(!mFirstPendingMutation);
|
|
mFirstPendingMutation = std::move(record);
|
|
mLastPendingMutation = mFirstPendingMutation;
|
|
} else {
|
|
MOZ_ASSERT(mFirstPendingMutation);
|
|
mLastPendingMutation->mNext = std::move(record);
|
|
mLastPendingMutation = mLastPendingMutation->mNext;
|
|
}
|
|
++mPendingMutationCount;
|
|
}
|
|
|
|
void ClearPendingRecords() {
|
|
// Break down the pending mutation record list so that cycle collector
|
|
// can delete the objects sooner.
|
|
RefPtr<nsDOMMutationRecord> current = std::move(mFirstPendingMutation);
|
|
mLastPendingMutation = nullptr;
|
|
mPendingMutationCount = 0;
|
|
while (current) {
|
|
current = std::move(current->mNext);
|
|
}
|
|
}
|
|
|
|
// static methods
|
|
static void QueueMutationObserverMicroTask();
|
|
|
|
MOZ_CAN_RUN_SCRIPT
|
|
static void HandleMutations(mozilla::AutoSlowOperation& aAso);
|
|
|
|
static bool AllScheduledMutationObserversAreSuppressed() {
|
|
if (sScheduledMutationObservers) {
|
|
uint32_t len = sScheduledMutationObservers->Length();
|
|
if (len > 0) {
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
if (!(*sScheduledMutationObservers)[i]->Suppressed()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void EnterMutationHandling();
|
|
static void LeaveMutationHandling();
|
|
|
|
static void Shutdown();
|
|
|
|
protected:
|
|
virtual ~nsDOMMutationObserver();
|
|
|
|
friend class nsMutationReceiver;
|
|
friend class nsAnimationReceiver;
|
|
friend class nsAutoMutationBatch;
|
|
friend class nsAutoAnimationMutationBatch;
|
|
nsMutationReceiver* GetReceiverFor(nsINode* aNode, bool aMayCreate,
|
|
bool aWantsAnimations);
|
|
void RemoveReceiver(nsMutationReceiver* aReceiver);
|
|
|
|
void GetAllSubtreeObserversFor(nsINode* aNode,
|
|
nsTArray<nsMutationReceiver*>& aObservers);
|
|
void ScheduleForRun();
|
|
void RescheduleForRun();
|
|
|
|
nsDOMMutationRecord* CurrentRecord(nsAtom* aType);
|
|
bool HasCurrentRecord(const nsAString& aType);
|
|
|
|
bool Suppressed() {
|
|
return mOwner && nsGlobalWindowInner::Cast(mOwner)->IsInSyncOperation();
|
|
}
|
|
|
|
MOZ_CAN_RUN_SCRIPT
|
|
static void HandleMutationsInternal(mozilla::AutoSlowOperation& aAso);
|
|
|
|
static void AddCurrentlyHandlingObserver(nsDOMMutationObserver* aObserver,
|
|
uint32_t aMutationLevel);
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> mOwner;
|
|
|
|
nsCOMArray<nsMutationReceiver> mReceivers;
|
|
nsClassHashtable<nsISupportsHashKey, nsCOMArray<nsMutationReceiver>>
|
|
mTransientReceivers;
|
|
// MutationRecords which are being constructed.
|
|
AutoTArray<nsDOMMutationRecord*, 4> mCurrentMutations;
|
|
// MutationRecords which will be handed to the callback at the end of
|
|
// the microtask.
|
|
RefPtr<nsDOMMutationRecord> mFirstPendingMutation;
|
|
nsDOMMutationRecord* mLastPendingMutation;
|
|
uint32_t mPendingMutationCount;
|
|
|
|
RefPtr<mozilla::dom::MutationCallback> mCallback;
|
|
|
|
bool mWaitingForRun;
|
|
bool mIsChrome;
|
|
bool mMergeAttributeRecords;
|
|
|
|
uint64_t mId;
|
|
|
|
static uint64_t sCount;
|
|
static AutoTArray<RefPtr<nsDOMMutationObserver>, 4>*
|
|
sScheduledMutationObservers;
|
|
|
|
static uint32_t sMutationLevel;
|
|
static AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 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;
|
|
AutoTArray<BatchObserver, 2> mObservers;
|
|
nsTArray<nsCOMPtr<nsIContent>> mRemovedNodes;
|
|
nsTArray<nsCOMPtr<nsIContent>> mAddedNodes;
|
|
nsINode* mBatchTarget;
|
|
bool mRemovalDone;
|
|
bool mFromFirstToLast;
|
|
bool mAllowNestedBatches;
|
|
nsCOMPtr<nsINode> mPrevSibling;
|
|
nsCOMPtr<nsINode> mNextSibling;
|
|
};
|
|
|
|
class nsAutoAnimationMutationBatch {
|
|
struct Entry;
|
|
|
|
public:
|
|
explicit nsAutoAnimationMutationBatch(mozilla::dom::Document* aDocument) {
|
|
Init(aDocument);
|
|
}
|
|
|
|
void Init(mozilla::dom::Document* aDocument) {
|
|
if (!aDocument || !aDocument->MayHaveDOMMutationObservers() ||
|
|
sCurrentBatch) {
|
|
return;
|
|
}
|
|
|
|
sCurrentBatch = this;
|
|
nsDOMMutationObserver::EnterMutationHandling();
|
|
}
|
|
|
|
~nsAutoAnimationMutationBatch() { Done(); }
|
|
|
|
void Done();
|
|
|
|
static bool IsBatching() { return !!sCurrentBatch; }
|
|
|
|
static nsAutoAnimationMutationBatch* GetCurrentBatch() {
|
|
return sCurrentBatch;
|
|
}
|
|
|
|
static void AddObserver(nsDOMMutationObserver* aObserver) {
|
|
if (sCurrentBatch->mObservers.Contains(aObserver)) {
|
|
return;
|
|
}
|
|
sCurrentBatch->mObservers.AppendElement(aObserver);
|
|
}
|
|
|
|
static void AnimationAdded(mozilla::dom::Animation* aAnimation,
|
|
nsINode* aTarget) {
|
|
if (!IsBatching()) {
|
|
return;
|
|
}
|
|
|
|
Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
|
|
if (entry) {
|
|
switch (entry->mState) {
|
|
case eState_RemainedAbsent:
|
|
entry->mState = eState_Added;
|
|
break;
|
|
case eState_Removed:
|
|
entry->mState = eState_RemainedPresent;
|
|
break;
|
|
case eState_Added:
|
|
// FIXME bug 1189015
|
|
NS_ERROR("shouldn't have observed an animation being added twice");
|
|
break;
|
|
case eState_RemainedPresent:
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"shouldn't have observed an animation "
|
|
"remaining present");
|
|
break;
|
|
}
|
|
} else {
|
|
entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
|
|
entry->mState = eState_Added;
|
|
entry->mChanged = false;
|
|
}
|
|
}
|
|
|
|
static void AnimationChanged(mozilla::dom::Animation* aAnimation,
|
|
nsINode* aTarget) {
|
|
Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
|
|
if (entry) {
|
|
NS_ASSERTION(entry->mState == eState_RemainedPresent ||
|
|
entry->mState == eState_Added,
|
|
"shouldn't have observed an animation being changed after "
|
|
"being removed");
|
|
entry->mChanged = true;
|
|
} else {
|
|
entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
|
|
entry->mState = eState_RemainedPresent;
|
|
entry->mChanged = true;
|
|
}
|
|
}
|
|
|
|
static void AnimationRemoved(mozilla::dom::Animation* aAnimation,
|
|
nsINode* aTarget) {
|
|
Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
|
|
if (entry) {
|
|
switch (entry->mState) {
|
|
case eState_RemainedPresent:
|
|
entry->mState = eState_Removed;
|
|
break;
|
|
case eState_Added:
|
|
entry->mState = eState_RemainedAbsent;
|
|
break;
|
|
case eState_RemainedAbsent:
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"shouldn't have observed an animation "
|
|
"remaining absent");
|
|
break;
|
|
case eState_Removed:
|
|
// FIXME bug 1189015
|
|
NS_ERROR("shouldn't have observed an animation being removed twice");
|
|
break;
|
|
}
|
|
} else {
|
|
entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
|
|
entry->mState = eState_Removed;
|
|
entry->mChanged = false;
|
|
}
|
|
}
|
|
|
|
private:
|
|
Entry* FindEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget) {
|
|
EntryArray* entries = mEntryTable.Get(aTarget);
|
|
if (!entries) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (Entry& e : *entries) {
|
|
if (e.mAnimation == aAnimation) {
|
|
return &e;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Entry* AddEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget) {
|
|
EntryArray* entries = sCurrentBatch->mEntryTable.GetOrInsertNew(aTarget);
|
|
if (entries->IsEmpty()) {
|
|
sCurrentBatch->mBatchTargets.AppendElement(aTarget);
|
|
}
|
|
Entry* entry = entries->AppendElement();
|
|
entry->mAnimation = aAnimation;
|
|
return entry;
|
|
}
|
|
|
|
enum State {
|
|
eState_RemainedPresent,
|
|
eState_RemainedAbsent,
|
|
eState_Added,
|
|
eState_Removed
|
|
};
|
|
|
|
struct Entry {
|
|
RefPtr<mozilla::dom::Animation> mAnimation;
|
|
State mState;
|
|
bool mChanged;
|
|
};
|
|
|
|
static nsAutoAnimationMutationBatch* sCurrentBatch;
|
|
AutoTArray<nsDOMMutationObserver*, 2> mObservers;
|
|
using EntryArray = nsTArray<Entry>;
|
|
nsClassHashtable<nsPtrHashKey<nsINode>, EntryArray> mEntryTable;
|
|
// List of nodes referred to by mEntryTable so we can sort them
|
|
// For a specific pseudo element, we use its parent element as the
|
|
// batch target, so they will be put in the same EntryArray.
|
|
nsTArray<nsINode*> mBatchTargets;
|
|
};
|
|
|
|
inline nsDOMMutationObserver* nsMutationReceiverBase::Observer() {
|
|
return mParent ? mParent->Observer()
|
|
: static_cast<nsDOMMutationObserver*>(mObserver);
|
|
}
|
|
|
|
class MOZ_RAII nsDOMMutationEnterLeave {
|
|
public:
|
|
explicit nsDOMMutationEnterLeave(mozilla::dom::Document* aDoc)
|
|
: mNeeded(aDoc->MayHaveDOMMutationObservers()) {
|
|
if (mNeeded) {
|
|
nsDOMMutationObserver::EnterMutationHandling();
|
|
}
|
|
}
|
|
~nsDOMMutationEnterLeave() {
|
|
if (mNeeded) {
|
|
nsDOMMutationObserver::LeaveMutationHandling();
|
|
}
|
|
}
|
|
|
|
private:
|
|
const bool mNeeded;
|
|
};
|
|
|
|
#endif
|