1 /* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*- */
2 /* vim: set sw=4 ts=8 et tw=80 : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef nsDOMMutationObserver_h
8 #define nsDOMMutationObserver_h
10 #include "mozilla/Attributes.h"
11 #include "nsCycleCollectionParticipant.h"
12 #include "nsPIDOMWindow.h"
13 #include "nsIScriptContext.h"
14 #include "nsStubMutationObserver.h"
15 #include "nsCOMArray.h"
17 #include "nsAutoPtr.h"
18 #include "nsIVariant.h"
19 #include "nsContentList.h"
20 #include "mozilla/dom/Element.h"
21 #include "nsClassHashtable.h"
22 #include "nsNodeUtils.h"
23 #include "nsIDOMMutationEvent.h"
24 #include "nsWrapperCache.h"
25 #include "mozilla/dom/MutationObserverBinding.h"
26 #include "nsIDocument.h"
28 class nsDOMMutationObserver
;
29 using mozilla::dom::MutationObservingInfo
;
31 class nsDOMMutationRecord MOZ_FINAL
: public nsISupports
,
34 virtual ~nsDOMMutationRecord() {}
37 nsDOMMutationRecord(nsIAtom
* aType
, nsISupports
* aOwner
)
38 : mType(aType
), mAttrNamespace(NullString()), mPrevValue(NullString()), mOwner(aOwner
)
42 nsISupports
* GetParentObject() const
47 virtual JSObject
* WrapObject(JSContext
* aCx
) MOZ_OVERRIDE
49 return mozilla::dom::MutationRecordBinding::Wrap(aCx
, this);
52 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
53 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationRecord
)
55 void GetType(mozilla::dom::DOMString
& aRetVal
) const
57 aRetVal
.SetOwnedAtom(mType
, mozilla::dom::DOMString::eNullNotExpected
);
60 nsINode
* GetTarget() const
65 nsINodeList
* AddedNodes();
67 nsINodeList
* RemovedNodes();
69 nsINode
* GetPreviousSibling() const
71 return mPreviousSibling
;
74 nsINode
* GetNextSibling() const
79 void GetAttributeName(mozilla::dom::DOMString
& aRetVal
) const
81 aRetVal
.SetOwnedAtom(mAttrName
, mozilla::dom::DOMString::eTreatNullAsNull
);
84 void GetAttributeNamespace(mozilla::dom::DOMString
& aRetVal
) const
86 aRetVal
.SetOwnedString(mAttrNamespace
);
89 void GetOldValue(mozilla::dom::DOMString
& aRetVal
) const
91 aRetVal
.SetOwnedString(mPrevValue
);
94 nsCOMPtr
<nsINode
> mTarget
;
95 nsCOMPtr
<nsIAtom
> mType
;
96 nsCOMPtr
<nsIAtom
> mAttrName
;
97 nsString mAttrNamespace
;
99 nsRefPtr
<nsSimpleContentList
> mAddedNodes
;
100 nsRefPtr
<nsSimpleContentList
> mRemovedNodes
;
101 nsCOMPtr
<nsINode
> mPreviousSibling
;
102 nsCOMPtr
<nsINode
> mNextSibling
;
104 nsRefPtr
<nsDOMMutationRecord
> mNext
;
105 nsCOMPtr
<nsISupports
> mOwner
;
108 // Base class just prevents direct access to
109 // members to make sure we go through getters/setters.
110 class nsMutationReceiverBase
: public nsStubMutationObserver
113 virtual ~nsMutationReceiverBase() { }
115 nsDOMMutationObserver
* Observer();
116 nsINode
* Target() { return mParent
? mParent
->Target() : mTarget
; }
117 nsINode
* RegisterTarget() { return mRegisterTarget
; }
119 bool Subtree() { return mParent
? mParent
->Subtree() : mSubtree
; }
120 void SetSubtree(bool aSubtree
)
122 NS_ASSERTION(!mParent
, "Shouldn't have parent");
126 bool ChildList() { return mParent
? mParent
->ChildList() : mChildList
; }
127 void SetChildList(bool aChildList
)
129 NS_ASSERTION(!mParent
, "Shouldn't have parent");
130 mChildList
= aChildList
;
135 return mParent
? mParent
->CharacterData() : mCharacterData
;
137 void SetCharacterData(bool aCharacterData
)
139 NS_ASSERTION(!mParent
, "Shouldn't have parent");
140 mCharacterData
= aCharacterData
;
143 bool CharacterDataOldValue()
145 return mParent
? mParent
->CharacterDataOldValue() : mCharacterDataOldValue
;
147 void SetCharacterDataOldValue(bool aOldValue
)
149 NS_ASSERTION(!mParent
, "Shouldn't have parent");
150 mCharacterDataOldValue
= aOldValue
;
153 bool Attributes() { return mParent
? mParent
->Attributes() : mAttributes
; }
154 void SetAttributes(bool aAttributes
)
156 NS_ASSERTION(!mParent
, "Shouldn't have parent");
157 mAttributes
= aAttributes
;
162 return mParent
? mParent
->AllAttributes()
165 void SetAllAttributes(bool aAll
)
167 NS_ASSERTION(!mParent
, "Shouldn't have parent");
168 mAllAttributes
= aAll
;
171 bool AttributeOldValue() {
172 return mParent
? mParent
->AttributeOldValue()
173 : mAttributeOldValue
;
175 void SetAttributeOldValue(bool aOldValue
)
177 NS_ASSERTION(!mParent
, "Shouldn't have parent");
178 mAttributeOldValue
= aOldValue
;
181 nsCOMArray
<nsIAtom
>& AttributeFilter() { return mAttributeFilter
; }
182 void SetAttributeFilter(nsCOMArray
<nsIAtom
>& aFilter
)
184 NS_ASSERTION(!mParent
, "Shouldn't have parent");
185 mAttributeFilter
.Clear();
186 mAttributeFilter
.AppendObjects(aFilter
);
189 void AddClone(nsMutationReceiverBase
* aClone
)
191 mTransientReceivers
.AppendObject(aClone
);
194 void RemoveClone(nsMutationReceiverBase
* aClone
)
196 mTransientReceivers
.RemoveObject(aClone
);
200 nsMutationReceiverBase(nsINode
* aTarget
, nsDOMMutationObserver
* aObserver
)
201 : mTarget(aTarget
), mObserver(aObserver
), mRegisterTarget(aTarget
)
203 mRegisterTarget
->AddMutationObserver(this);
204 mRegisterTarget
->SetMayHaveDOMMutationObserver();
205 mRegisterTarget
->OwnerDoc()->SetMayHaveDOMMutationObservers();
208 nsMutationReceiverBase(nsINode
* aRegisterTarget
,
209 nsMutationReceiverBase
* aParent
)
210 : mTarget(nullptr), mObserver(nullptr), mParent(aParent
),
211 mRegisterTarget(aRegisterTarget
), mKungFuDeathGrip(aParent
->Target())
213 NS_ASSERTION(mParent
->Subtree(), "Should clone a non-subtree observer!");
214 mRegisterTarget
->AddMutationObserver(this);
215 mRegisterTarget
->SetMayHaveDOMMutationObserver();
216 mRegisterTarget
->OwnerDoc()->SetMayHaveDOMMutationObservers();
219 bool ObservesAttr(mozilla::dom::Element
* aElement
,
220 int32_t aNameSpaceID
,
224 return mParent
->ObservesAttr(aElement
, aNameSpaceID
, aAttr
);
226 if (!Attributes() || (!Subtree() && aElement
!= Target())) {
229 if (AllAttributes()) {
233 if (aNameSpaceID
!= kNameSpaceID_None
) {
237 nsCOMArray
<nsIAtom
>& filters
= AttributeFilter();
238 for (int32_t i
= 0; i
< filters
.Count(); ++i
) {
239 if (filters
[i
] == aAttr
) {
246 // The target for the MutationObserver.observe() method.
248 nsDOMMutationObserver
* mObserver
;
249 nsRefPtr
<nsMutationReceiverBase
> mParent
; // Cleared after microtask.
250 // The node to which Gecko-internal nsIMutationObserver was registered to.
251 // This is different than mTarget when dealing with transient observers.
252 nsINode
* mRegisterTarget
;
253 nsCOMArray
<nsMutationReceiverBase
> mTransientReceivers
;
254 // While we have transient receivers, keep the original mutation receiver
255 // alive so it doesn't go away and disconnect all its transient receivers.
256 nsCOMPtr
<nsINode
> mKungFuDeathGrip
;
262 bool mCharacterDataOldValue
;
265 bool mAttributeOldValue
;
266 nsCOMArray
<nsIAtom
> mAttributeFilter
;
270 class nsMutationReceiver
: public nsMutationReceiverBase
273 virtual ~nsMutationReceiver() { Disconnect(false); }
276 nsMutationReceiver(nsINode
* aTarget
, nsDOMMutationObserver
* aObserver
);
278 nsMutationReceiver(nsINode
* aRegisterTarget
, nsMutationReceiverBase
* aParent
)
279 : nsMutationReceiverBase(aRegisterTarget
, aParent
)
281 NS_ASSERTION(!static_cast<nsMutationReceiver
*>(aParent
)->GetParent(),
282 "Shouldn't create deep observer hierarchies!");
283 aParent
->AddClone(this);
286 nsMutationReceiver
* GetParent()
288 return static_cast<nsMutationReceiver
*>(mParent
.get());
293 for (int32_t i
= 0; i
< mTransientReceivers
.Count(); ++i
) {
294 nsMutationReceiver
* r
=
295 static_cast<nsMutationReceiver
*>(mTransientReceivers
[i
]);
296 r
->DisconnectTransientReceiver();
298 mTransientReceivers
.Clear();
301 void DisconnectTransientReceiver()
303 if (mRegisterTarget
) {
304 mRegisterTarget
->RemoveMutationObserver(this);
305 mRegisterTarget
= nullptr;
309 NS_ASSERTION(!mTarget
, "Should not have mTarget");
310 NS_ASSERTION(!mObserver
, "Should not have mObserver");
313 void Disconnect(bool aRemoveFromObserver
);
315 NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
318 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
319 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATAWILLCHANGE
320 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
321 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
322 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
323 NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
325 virtual void AttributeSetToCurrentValue(nsIDocument
* aDocument
,
326 mozilla::dom::Element
* aElement
,
327 int32_t aNameSpaceID
,
328 nsIAtom
* aAttribute
) MOZ_OVERRIDE
330 // We can reuse AttributeWillChange implementation.
331 AttributeWillChange(aDocument
, aElement
, aNameSpaceID
, aAttribute
,
332 nsIDOMMutationEvent::MODIFICATION
);
336 #define NS_DOM_MUTATION_OBSERVER_IID \
337 { 0x0c3b91f8, 0xcc3b, 0x4b08, \
338 { 0x9e, 0xab, 0x07, 0x47, 0xa9, 0xe4, 0x65, 0xb4 } }
340 class nsDOMMutationObserver MOZ_FINAL
: public nsISupports
,
341 public nsWrapperCache
344 nsDOMMutationObserver(already_AddRefed
<nsPIDOMWindow
>&& aOwner
,
345 mozilla::dom::MutationCallback
& aCb
)
346 : mOwner(aOwner
), mLastPendingMutation(nullptr), mPendingMutationCount(0),
347 mCallback(&aCb
), mWaitingForRun(false), mId(++sCount
)
350 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
351 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationObserver
)
352 NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_MUTATION_OBSERVER_IID
)
354 static already_AddRefed
<nsDOMMutationObserver
>
355 Constructor(const mozilla::dom::GlobalObject
& aGlobal
,
356 mozilla::dom::MutationCallback
& aCb
,
357 mozilla::ErrorResult
& aRv
);
359 virtual JSObject
* WrapObject(JSContext
* aCx
) MOZ_OVERRIDE
361 return mozilla::dom::MutationObserverBinding::Wrap(aCx
, this);
364 nsISupports
* GetParentObject() const
369 void Observe(nsINode
& aTarget
,
370 const mozilla::dom::MutationObserverInit
& aOptions
,
371 mozilla::ErrorResult
& aRv
);
375 void TakeRecords(nsTArray
<nsRefPtr
<nsDOMMutationRecord
> >& aRetVal
);
377 void HandleMutation();
379 void GetObservingInfo(nsTArray
<Nullable
<MutationObservingInfo
> >& aResult
);
381 mozilla::dom::MutationCallback
* MutationCallback() { return mCallback
; }
383 void AppendMutationRecord(already_AddRefed
<nsDOMMutationRecord
> aRecord
)
385 nsRefPtr
<nsDOMMutationRecord
> record
= aRecord
;
387 if (!mLastPendingMutation
) {
388 MOZ_ASSERT(!mFirstPendingMutation
);
389 mFirstPendingMutation
= record
.forget();
390 mLastPendingMutation
= mFirstPendingMutation
;
392 MOZ_ASSERT(mFirstPendingMutation
);
393 mLastPendingMutation
->mNext
= record
.forget();
394 mLastPendingMutation
= mLastPendingMutation
->mNext
;
396 ++mPendingMutationCount
;
399 void ClearPendingRecords()
401 mFirstPendingMutation
= nullptr;
402 mLastPendingMutation
= nullptr;
403 mPendingMutationCount
= 0;
407 static void HandleMutations()
409 if (sScheduledMutationObservers
) {
410 HandleMutationsInternal();
414 static void EnterMutationHandling();
415 static void LeaveMutationHandling();
417 static void Shutdown();
419 virtual ~nsDOMMutationObserver();
421 friend class nsMutationReceiver
;
422 friend class nsAutoMutationBatch
;
423 nsMutationReceiver
* GetReceiverFor(nsINode
* aNode
, bool aMayCreate
);
424 void RemoveReceiver(nsMutationReceiver
* aReceiver
);
426 already_AddRefed
<nsIVariant
> TakeRecords();
428 void GetAllSubtreeObserversFor(nsINode
* aNode
,
429 nsTArray
<nsMutationReceiver
*>& aObservers
);
430 void ScheduleForRun();
431 void RescheduleForRun();
433 nsDOMMutationRecord
* CurrentRecord(nsIAtom
* aType
);
434 bool HasCurrentRecord(const nsAString
& aType
);
439 nsCOMPtr
<nsIDocument
> d
= mOwner
->GetExtantDoc();
440 return d
&& d
->IsInSyncOperation();
445 static void HandleMutationsInternal();
447 static void AddCurrentlyHandlingObserver(nsDOMMutationObserver
* aObserver
);
449 nsCOMPtr
<nsPIDOMWindow
> mOwner
;
451 nsCOMArray
<nsMutationReceiver
> mReceivers
;
452 nsClassHashtable
<nsISupportsHashKey
,
453 nsCOMArray
<nsMutationReceiver
> > mTransientReceivers
;
454 // MutationRecords which are being constructed.
455 nsAutoTArray
<nsDOMMutationRecord
*, 4> mCurrentMutations
;
456 // MutationRecords which will be handed to the callback at the end of
458 nsRefPtr
<nsDOMMutationRecord
> mFirstPendingMutation
;
459 nsDOMMutationRecord
* mLastPendingMutation
;
460 uint32_t mPendingMutationCount
;
462 nsRefPtr
<mozilla::dom::MutationCallback
> mCallback
;
468 static uint64_t sCount
;
469 static nsAutoTArray
<nsRefPtr
<nsDOMMutationObserver
>, 4>* sScheduledMutationObservers
;
470 static nsDOMMutationObserver
* sCurrentObserver
;
472 static uint32_t sMutationLevel
;
473 static nsAutoTArray
<nsAutoTArray
<nsRefPtr
<nsDOMMutationObserver
>, 4>, 4>*
474 sCurrentlyHandlingObservers
;
477 NS_DEFINE_STATIC_IID_ACCESSOR(nsDOMMutationObserver
, NS_DOM_MUTATION_OBSERVER_IID
)
479 class nsAutoMutationBatch
482 nsAutoMutationBatch()
483 : mPreviousBatch(nullptr), mBatchTarget(nullptr), mRemovalDone(false),
484 mFromFirstToLast(false), mAllowNestedBatches(false)
488 nsAutoMutationBatch(nsINode
* aTarget
, bool aFromFirstToLast
,
489 bool aAllowNestedBatches
)
490 : mPreviousBatch(nullptr), mBatchTarget(nullptr), mRemovalDone(false),
491 mFromFirstToLast(false), mAllowNestedBatches(false)
493 Init(aTarget
, aFromFirstToLast
, aAllowNestedBatches
);
496 void Init(nsINode
* aTarget
, bool aFromFirstToLast
, bool aAllowNestedBatches
)
498 if (aTarget
&& aTarget
->OwnerDoc()->MayHaveDOMMutationObservers()) {
499 if (sCurrentBatch
&& !sCurrentBatch
->mAllowNestedBatches
) {
502 mBatchTarget
= aTarget
;
503 mFromFirstToLast
= aFromFirstToLast
;
504 mAllowNestedBatches
= aAllowNestedBatches
;
505 mPreviousBatch
= sCurrentBatch
;
506 sCurrentBatch
= this;
507 nsDOMMutationObserver::EnterMutationHandling();
511 void RemovalDone() { mRemovalDone
= true; }
512 static bool IsRemovalDone() { return sCurrentBatch
->mRemovalDone
; }
514 void SetPrevSibling(nsINode
* aNode
) { mPrevSibling
= aNode
; }
515 void SetNextSibling(nsINode
* aNode
) { mNextSibling
= aNode
; }
519 ~nsAutoMutationBatch() { NodesAdded(); }
521 static bool IsBatching()
523 return !!sCurrentBatch
;
526 static nsAutoMutationBatch
* GetCurrentBatch()
528 return sCurrentBatch
;
531 static void UpdateObserver(nsDOMMutationObserver
* aObserver
,
532 bool aWantsChildList
)
534 uint32_t l
= sCurrentBatch
->mObservers
.Length();
535 for (uint32_t i
= 0; i
< l
; ++i
) {
536 if (sCurrentBatch
->mObservers
[i
].mObserver
== aObserver
) {
537 if (aWantsChildList
) {
538 sCurrentBatch
->mObservers
[i
].mWantsChildList
= aWantsChildList
;
543 BatchObserver
* bo
= sCurrentBatch
->mObservers
.AppendElement();
544 bo
->mObserver
= aObserver
;
545 bo
->mWantsChildList
= aWantsChildList
;
549 static nsINode
* GetBatchTarget() { return sCurrentBatch
->mBatchTarget
; }
551 // Mutation receivers notify the batch about removed child nodes.
552 static void NodeRemoved(nsIContent
* aChild
)
554 if (IsBatching() && !sCurrentBatch
->mRemovalDone
) {
555 uint32_t len
= sCurrentBatch
->mRemovedNodes
.Length();
557 sCurrentBatch
->mRemovedNodes
[len
- 1] != aChild
) {
558 sCurrentBatch
->mRemovedNodes
.AppendElement(aChild
);
563 // Called after new child nodes have been added to the batch target.
566 if (sCurrentBatch
!= this) {
571 mPrevSibling
? mPrevSibling
->GetNextSibling() :
572 mBatchTarget
->GetFirstChild();
573 for (; c
!= mNextSibling
; c
= c
->GetNextSibling()) {
574 mAddedNodes
.AppendElement(c
);
582 nsDOMMutationObserver
* mObserver
;
583 bool mWantsChildList
;
586 static nsAutoMutationBatch
* sCurrentBatch
;
587 nsAutoMutationBatch
* mPreviousBatch
;
588 nsAutoTArray
<BatchObserver
, 2> mObservers
;
589 nsTArray
<nsCOMPtr
<nsIContent
> > mRemovedNodes
;
590 nsTArray
<nsCOMPtr
<nsIContent
> > mAddedNodes
;
591 nsINode
* mBatchTarget
;
593 bool mFromFirstToLast
;
594 bool mAllowNestedBatches
;
595 nsCOMPtr
<nsINode
> mPrevSibling
;
596 nsCOMPtr
<nsINode
> mNextSibling
;
600 nsDOMMutationObserver
*
601 nsMutationReceiverBase::Observer()
604 mParent
->Observer() : static_cast<nsDOMMutationObserver
*>(mObserver
);