/src/mozilla-central/dom/base/nsDOMMutationObserver.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 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/. */ |
6 | | |
7 | | #include "nsDOMMutationObserver.h" |
8 | | |
9 | | #include "mozilla/AnimationTarget.h" |
10 | | #include "mozilla/Maybe.h" |
11 | | #include "mozilla/OwningNonNull.h" |
12 | | |
13 | | #include "mozilla/dom/Animation.h" |
14 | | #include "mozilla/dom/KeyframeEffect.h" |
15 | | #include "mozilla/dom/DocGroup.h" |
16 | | |
17 | | #include "nsContentUtils.h" |
18 | | #include "nsCSSPseudoElements.h" |
19 | | #include "nsError.h" |
20 | | #include "nsIScriptGlobalObject.h" |
21 | | #include "nsServiceManagerUtils.h" |
22 | | #include "nsTextFragment.h" |
23 | | #include "nsThreadUtils.h" |
24 | | |
25 | | using namespace mozilla; |
26 | | using namespace mozilla::dom; |
27 | | using mozilla::dom::DocGroup; |
28 | | using mozilla::dom::HTMLSlotElement; |
29 | | |
30 | | AutoTArray<RefPtr<nsDOMMutationObserver>, 4>* |
31 | | nsDOMMutationObserver::sScheduledMutationObservers = nullptr; |
32 | | |
33 | | uint32_t nsDOMMutationObserver::sMutationLevel = 0; |
34 | | uint64_t nsDOMMutationObserver::sCount = 0; |
35 | | |
36 | | AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 4>, 4>* |
37 | | nsDOMMutationObserver::sCurrentlyHandlingObservers = nullptr; |
38 | | |
39 | | nsINodeList* |
40 | | nsDOMMutationRecord::AddedNodes() |
41 | 0 | { |
42 | 0 | if (!mAddedNodes) { |
43 | 0 | mAddedNodes = new nsSimpleContentList(mTarget); |
44 | 0 | } |
45 | 0 | return mAddedNodes; |
46 | 0 | } |
47 | | |
48 | | nsINodeList* |
49 | | nsDOMMutationRecord::RemovedNodes() |
50 | 0 | { |
51 | 0 | if (!mRemovedNodes) { |
52 | 0 | mRemovedNodes = new nsSimpleContentList(mTarget); |
53 | 0 | } |
54 | 0 | return mRemovedNodes; |
55 | 0 | } |
56 | | |
57 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMMutationRecord) |
58 | 0 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
59 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
60 | 0 | NS_INTERFACE_MAP_END |
61 | | |
62 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMutationRecord) |
63 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMutationRecord) |
64 | | |
65 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMMutationRecord, |
66 | | mTarget, |
67 | | mPreviousSibling, mNextSibling, |
68 | | mAddedNodes, mRemovedNodes, |
69 | | mAddedAnimations, mRemovedAnimations, |
70 | | mChangedAnimations, |
71 | | mNext, mOwner) |
72 | | |
73 | | // Observer |
74 | | |
75 | | bool |
76 | | nsMutationReceiverBase::IsObservable(nsIContent* aContent) |
77 | 0 | { |
78 | 0 | return !aContent->ChromeOnlyAccess() && |
79 | 0 | (Observer()->IsChrome() || !aContent->IsInAnonymousSubtree()); |
80 | 0 | } |
81 | | |
82 | | NS_IMPL_ADDREF(nsMutationReceiver) |
83 | | NS_IMPL_RELEASE(nsMutationReceiver) |
84 | | |
85 | 0 | NS_INTERFACE_MAP_BEGIN(nsMutationReceiver) |
86 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
87 | 0 | NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) |
88 | 0 | NS_INTERFACE_MAP_END |
89 | | |
90 | | nsMutationReceiver::nsMutationReceiver(nsINode* aTarget, |
91 | | nsDOMMutationObserver* aObserver) |
92 | | : nsMutationReceiverBase(aTarget, aObserver) |
93 | 0 | { |
94 | 0 | mTarget->BindObject(aObserver); |
95 | 0 | } |
96 | | |
97 | | void |
98 | | nsMutationReceiver::Disconnect(bool aRemoveFromObserver) |
99 | 0 | { |
100 | 0 | if (mRegisterTarget) { |
101 | 0 | mRegisterTarget->RemoveMutationObserver(this); |
102 | 0 | mRegisterTarget = nullptr; |
103 | 0 | } |
104 | 0 |
|
105 | 0 | mParent = nullptr; |
106 | 0 | nsINode* target = mTarget; |
107 | 0 | mTarget = nullptr; |
108 | 0 | nsDOMMutationObserver* observer = mObserver; |
109 | 0 | mObserver = nullptr; |
110 | 0 | RemoveClones(); |
111 | 0 |
|
112 | 0 | if (target && observer) { |
113 | 0 | if (aRemoveFromObserver) { |
114 | 0 | static_cast<nsDOMMutationObserver*>(observer)->RemoveReceiver(this); |
115 | 0 | } |
116 | 0 | // UnbindObject may delete 'this'! |
117 | 0 | target->UnbindObject(observer); |
118 | 0 | } |
119 | 0 | } |
120 | | |
121 | | void |
122 | | nsMutationReceiver::NativeAnonymousChildListChange(nsIContent* aContent, |
123 | 0 | bool aIsRemove) { |
124 | 0 | if (!NativeAnonymousChildList()) { |
125 | 0 | return; |
126 | 0 | } |
127 | 0 | |
128 | 0 | nsINode* parent = aContent->GetParentNode(); |
129 | 0 | if (!parent || |
130 | 0 | (!Subtree() && Target() != parent) || |
131 | 0 | (Subtree() && RegisterTarget()->SubtreeRoot() != parent->SubtreeRoot())) { |
132 | 0 | return; |
133 | 0 | } |
134 | 0 | |
135 | 0 | nsDOMMutationRecord* m = |
136 | 0 | Observer()->CurrentRecord(nsGkAtoms::nativeAnonymousChildList); |
137 | 0 |
|
138 | 0 | if (m->mTarget) { |
139 | 0 | return; |
140 | 0 | } |
141 | 0 | m->mTarget = parent; |
142 | 0 |
|
143 | 0 | if (aIsRemove) { |
144 | 0 | m->mRemovedNodes = new nsSimpleContentList(parent); |
145 | 0 | m->mRemovedNodes->AppendElement(aContent); |
146 | 0 | } else { |
147 | 0 | m->mAddedNodes = new nsSimpleContentList(parent); |
148 | 0 | m->mAddedNodes->AppendElement(aContent); |
149 | 0 | } |
150 | 0 | } |
151 | | |
152 | | void |
153 | | nsMutationReceiver::AttributeWillChange(mozilla::dom::Element* aElement, |
154 | | int32_t aNameSpaceID, |
155 | | nsAtom* aAttribute, |
156 | | int32_t aModType, |
157 | | const nsAttrValue* aNewValue) |
158 | 0 | { |
159 | 0 | if (nsAutoMutationBatch::IsBatching() || |
160 | 0 | !ObservesAttr(RegisterTarget(), aElement, aNameSpaceID, aAttribute)) { |
161 | 0 | return; |
162 | 0 | } |
163 | 0 | |
164 | 0 | nsDOMMutationRecord* m = |
165 | 0 | Observer()->CurrentRecord(nsGkAtoms::attributes); |
166 | 0 |
|
167 | 0 | NS_ASSERTION(!m->mTarget || m->mTarget == aElement, |
168 | 0 | "Wrong target!"); |
169 | 0 | NS_ASSERTION(!m->mAttrName || m->mAttrName == aAttribute, |
170 | 0 | "Wrong attribute!"); |
171 | 0 | if (!m->mTarget) { |
172 | 0 | m->mTarget = aElement; |
173 | 0 | m->mAttrName = aAttribute; |
174 | 0 | if (aNameSpaceID == kNameSpaceID_None) { |
175 | 0 | m->mAttrNamespace.SetIsVoid(true); |
176 | 0 | } else { |
177 | 0 | nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNameSpaceID, |
178 | 0 | m->mAttrNamespace); |
179 | 0 | } |
180 | 0 | } |
181 | 0 |
|
182 | 0 | if (AttributeOldValue() && m->mPrevValue.IsVoid()) { |
183 | 0 | if (!aElement->GetAttr(aNameSpaceID, aAttribute, m->mPrevValue)) { |
184 | 0 | m->mPrevValue.SetIsVoid(true); |
185 | 0 | } |
186 | 0 | } |
187 | 0 | } |
188 | | |
189 | | void |
190 | | nsMutationReceiver::CharacterDataWillChange(nsIContent* aContent, |
191 | | const CharacterDataChangeInfo&) |
192 | 0 | { |
193 | 0 | if (nsAutoMutationBatch::IsBatching() || |
194 | 0 | !CharacterData() || |
195 | 0 | (!Subtree() && aContent != Target()) || |
196 | 0 | (Subtree() && RegisterTarget()->SubtreeRoot() != aContent->SubtreeRoot()) || |
197 | 0 | !IsObservable(aContent)) { |
198 | 0 | return; |
199 | 0 | } |
200 | 0 | |
201 | 0 | nsDOMMutationRecord* m = |
202 | 0 | Observer()->CurrentRecord(nsGkAtoms::characterData); |
203 | 0 |
|
204 | 0 | NS_ASSERTION(!m->mTarget || m->mTarget == aContent, |
205 | 0 | "Wrong target!"); |
206 | 0 |
|
207 | 0 | if (!m->mTarget) { |
208 | 0 | m->mTarget = aContent; |
209 | 0 | } |
210 | 0 | if (CharacterDataOldValue() && m->mPrevValue.IsVoid()) { |
211 | 0 | aContent->GetText()->AppendTo(m->mPrevValue); |
212 | 0 | } |
213 | 0 | } |
214 | | |
215 | | void |
216 | | nsMutationReceiver::ContentAppended(nsIContent* aFirstNewContent) |
217 | 0 | { |
218 | 0 | nsINode* parent = aFirstNewContent->GetParentNode(); |
219 | 0 | bool wantsChildList = |
220 | 0 | ChildList() && |
221 | 0 | ((Subtree() && RegisterTarget()->SubtreeRoot() == parent->SubtreeRoot()) || |
222 | 0 | parent == Target()); |
223 | 0 | if (!wantsChildList || !IsObservable(aFirstNewContent)) { |
224 | 0 | return; |
225 | 0 | } |
226 | 0 | |
227 | 0 | if (nsAutoMutationBatch::IsBatching()) { |
228 | 0 | if (parent == nsAutoMutationBatch::GetBatchTarget()) { |
229 | 0 | nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList); |
230 | 0 | } |
231 | 0 | return; |
232 | 0 | } |
233 | 0 |
|
234 | 0 | nsDOMMutationRecord* m = |
235 | 0 | Observer()->CurrentRecord(nsGkAtoms::childList); |
236 | 0 | NS_ASSERTION(!m->mTarget || m->mTarget == parent, |
237 | 0 | "Wrong target!"); |
238 | 0 | if (m->mTarget) { |
239 | 0 | // Already handled case. |
240 | 0 | return; |
241 | 0 | } |
242 | 0 | m->mTarget = parent; |
243 | 0 | m->mAddedNodes = new nsSimpleContentList(parent); |
244 | 0 |
|
245 | 0 | nsINode* n = aFirstNewContent; |
246 | 0 | while (n) { |
247 | 0 | m->mAddedNodes->AppendElement(static_cast<nsIContent*>(n)); |
248 | 0 | n = n->GetNextSibling(); |
249 | 0 | } |
250 | 0 | m->mPreviousSibling = aFirstNewContent->GetPreviousSibling(); |
251 | 0 | } |
252 | | |
253 | | void |
254 | | nsMutationReceiver::ContentInserted(nsIContent* aChild) |
255 | 0 | { |
256 | 0 | nsINode* parent = aChild->GetParentNode(); |
257 | 0 | bool wantsChildList = |
258 | 0 | ChildList() && |
259 | 0 | ((Subtree() && RegisterTarget()->SubtreeRoot() == parent->SubtreeRoot()) || |
260 | 0 | parent == Target()); |
261 | 0 | if (!wantsChildList || !IsObservable(aChild)) { |
262 | 0 | return; |
263 | 0 | } |
264 | 0 | |
265 | 0 | if (nsAutoMutationBatch::IsBatching()) { |
266 | 0 | if (parent == nsAutoMutationBatch::GetBatchTarget()) { |
267 | 0 | nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList); |
268 | 0 | } |
269 | 0 | return; |
270 | 0 | } |
271 | 0 |
|
272 | 0 | nsDOMMutationRecord* m = |
273 | 0 | Observer()->CurrentRecord(nsGkAtoms::childList); |
274 | 0 | if (m->mTarget) { |
275 | 0 | // Already handled case. |
276 | 0 | return; |
277 | 0 | } |
278 | 0 | m->mTarget = parent; |
279 | 0 | m->mAddedNodes = new nsSimpleContentList(parent); |
280 | 0 | m->mAddedNodes->AppendElement(aChild); |
281 | 0 | m->mPreviousSibling = aChild->GetPreviousSibling(); |
282 | 0 | m->mNextSibling = aChild->GetNextSibling(); |
283 | 0 | } |
284 | | |
285 | | void |
286 | | nsMutationReceiver::ContentRemoved(nsIContent* aChild, |
287 | | nsIContent* aPreviousSibling) |
288 | 0 | { |
289 | 0 | if (!IsObservable(aChild)) { |
290 | 0 | return; |
291 | 0 | } |
292 | 0 | |
293 | 0 | nsINode* parent = aChild->GetParentNode(); |
294 | 0 | if (Subtree() && parent->SubtreeRoot() != RegisterTarget()->SubtreeRoot()) { |
295 | 0 | return; |
296 | 0 | } |
297 | 0 | if (nsAutoMutationBatch::IsBatching()) { |
298 | 0 | if (nsAutoMutationBatch::IsRemovalDone()) { |
299 | 0 | // This can happen for example if HTML parser parses to |
300 | 0 | // context node, but needs to move elements around. |
301 | 0 | return; |
302 | 0 | } |
303 | 0 | if (nsAutoMutationBatch::GetBatchTarget() != parent) { |
304 | 0 | return; |
305 | 0 | } |
306 | 0 | |
307 | 0 | bool wantsChildList = ChildList() && (Subtree() || parent == Target()); |
308 | 0 | if (wantsChildList || Subtree()) { |
309 | 0 | nsAutoMutationBatch::NodeRemoved(aChild); |
310 | 0 | nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList); |
311 | 0 | } |
312 | 0 |
|
313 | 0 | return; |
314 | 0 | } |
315 | 0 |
|
316 | 0 | if (Subtree()) { |
317 | 0 | // Try to avoid creating transient observer if the node |
318 | 0 | // already has an observer observing the same set of nodes. |
319 | 0 | nsMutationReceiver* orig = GetParent() ? GetParent() : this; |
320 | 0 | if (Observer()->GetReceiverFor(aChild, false, false) != orig) { |
321 | 0 | bool transientExists = false; |
322 | 0 | bool isNewEntry = false; |
323 | 0 | nsCOMArray<nsMutationReceiver>* transientReceivers = |
324 | 0 | Observer()->mTransientReceivers.LookupForAdd(aChild).OrInsert( |
325 | 0 | [&isNewEntry] () { |
326 | 0 | isNewEntry = true; |
327 | 0 | return new nsCOMArray<nsMutationReceiver>(); |
328 | 0 | }); |
329 | 0 | if (!isNewEntry) { |
330 | 0 | for (int32_t i = 0; i < transientReceivers->Count(); ++i) { |
331 | 0 | nsMutationReceiver* r = transientReceivers->ObjectAt(i); |
332 | 0 | if (r->GetParent() == orig) { |
333 | 0 | transientExists = true; |
334 | 0 | } |
335 | 0 | } |
336 | 0 | } |
337 | 0 | if (!transientExists) { |
338 | 0 | // Make sure the elements which are removed from the |
339 | 0 | // subtree are kept in the same observation set. |
340 | 0 | nsMutationReceiver* tr; |
341 | 0 | if (orig->Animations()) { |
342 | 0 | tr = nsAnimationReceiver::Create(aChild, orig); |
343 | 0 | } else { |
344 | 0 | tr = nsMutationReceiver::Create(aChild, orig); |
345 | 0 | } |
346 | 0 | transientReceivers->AppendObject(tr); |
347 | 0 | } |
348 | 0 | } |
349 | 0 | } |
350 | 0 |
|
351 | 0 | if (ChildList() && (Subtree() || parent == Target())) { |
352 | 0 | nsDOMMutationRecord* m = |
353 | 0 | Observer()->CurrentRecord(nsGkAtoms::childList); |
354 | 0 | if (m->mTarget) { |
355 | 0 | // Already handled case. |
356 | 0 | return; |
357 | 0 | } |
358 | 0 | MOZ_ASSERT(parent); |
359 | 0 |
|
360 | 0 | m->mTarget = parent; |
361 | 0 | m->mRemovedNodes = new nsSimpleContentList(parent); |
362 | 0 | m->mRemovedNodes->AppendElement(aChild); |
363 | 0 | m->mPreviousSibling = aPreviousSibling; |
364 | 0 | m->mNextSibling = aPreviousSibling ? |
365 | 0 | aPreviousSibling->GetNextSibling() : parent->GetFirstChild(); |
366 | 0 | } |
367 | 0 | // We need to schedule always, so that after microtask mTransientReceivers |
368 | 0 | // can be cleared correctly. |
369 | 0 | Observer()->ScheduleForRun(); |
370 | 0 | } |
371 | | |
372 | | void nsMutationReceiver::NodeWillBeDestroyed(const nsINode *aNode) |
373 | 0 | { |
374 | 0 | NS_ASSERTION(!mParent, "Shouldn't have mParent here!"); |
375 | 0 | Disconnect(true); |
376 | 0 | } |
377 | | |
378 | | void |
379 | | nsAnimationReceiver::RecordAnimationMutation(Animation* aAnimation, |
380 | | AnimationMutation aMutationType) |
381 | 0 | { |
382 | 0 | mozilla::dom::AnimationEffect* effect = aAnimation->GetEffect(); |
383 | 0 | if (!effect) { |
384 | 0 | return; |
385 | 0 | } |
386 | 0 | |
387 | 0 | mozilla::dom::KeyframeEffect* keyframeEffect = effect->AsKeyframeEffect(); |
388 | 0 | if (!keyframeEffect) { |
389 | 0 | return; |
390 | 0 | } |
391 | 0 | |
392 | 0 | Maybe<NonOwningAnimationTarget> animationTarget = keyframeEffect->GetTarget(); |
393 | 0 | if (!animationTarget) { |
394 | 0 | return; |
395 | 0 | } |
396 | 0 | |
397 | 0 | Element* elem = animationTarget->mElement; |
398 | 0 | if (!Animations() || !(Subtree() || elem == Target()) || |
399 | 0 | elem->ChromeOnlyAccess()) { |
400 | 0 | return; |
401 | 0 | } |
402 | 0 | |
403 | 0 | // Record animations targeting to a pseudo element only when subtree is true. |
404 | 0 | if (animationTarget->mPseudoType != mozilla::CSSPseudoElementType::NotPseudo && |
405 | 0 | !Subtree()) { |
406 | 0 | return; |
407 | 0 | } |
408 | 0 | |
409 | 0 | if (nsAutoAnimationMutationBatch::IsBatching()) { |
410 | 0 | switch (aMutationType) { |
411 | 0 | case eAnimationMutation_Added: |
412 | 0 | nsAutoAnimationMutationBatch::AnimationAdded(aAnimation, elem); |
413 | 0 | break; |
414 | 0 | case eAnimationMutation_Changed: |
415 | 0 | nsAutoAnimationMutationBatch::AnimationChanged(aAnimation, elem); |
416 | 0 | break; |
417 | 0 | case eAnimationMutation_Removed: |
418 | 0 | nsAutoAnimationMutationBatch::AnimationRemoved(aAnimation, elem); |
419 | 0 | break; |
420 | 0 | } |
421 | 0 | |
422 | 0 | nsAutoAnimationMutationBatch::AddObserver(Observer()); |
423 | 0 | return; |
424 | 0 | } |
425 | 0 | |
426 | 0 | nsDOMMutationRecord* m = |
427 | 0 | Observer()->CurrentRecord(nsGkAtoms::animations); |
428 | 0 |
|
429 | 0 | NS_ASSERTION(!m->mTarget, "Wrong target!"); |
430 | 0 |
|
431 | 0 | m->mTarget = elem; |
432 | 0 |
|
433 | 0 | switch (aMutationType) { |
434 | 0 | case eAnimationMutation_Added: |
435 | 0 | m->mAddedAnimations.AppendElement(aAnimation); |
436 | 0 | break; |
437 | 0 | case eAnimationMutation_Changed: |
438 | 0 | m->mChangedAnimations.AppendElement(aAnimation); |
439 | 0 | break; |
440 | 0 | case eAnimationMutation_Removed: |
441 | 0 | m->mRemovedAnimations.AppendElement(aAnimation); |
442 | 0 | break; |
443 | 0 | } |
444 | 0 | } |
445 | | |
446 | | void |
447 | | nsAnimationReceiver::AnimationAdded(Animation* aAnimation) |
448 | 0 | { |
449 | 0 | RecordAnimationMutation(aAnimation, eAnimationMutation_Added); |
450 | 0 | } |
451 | | |
452 | | void |
453 | | nsAnimationReceiver::AnimationChanged(Animation* aAnimation) |
454 | 0 | { |
455 | 0 | RecordAnimationMutation(aAnimation, eAnimationMutation_Changed); |
456 | 0 | } |
457 | | |
458 | | void |
459 | | nsAnimationReceiver::AnimationRemoved(Animation* aAnimation) |
460 | 0 | { |
461 | 0 | RecordAnimationMutation(aAnimation, eAnimationMutation_Removed); |
462 | 0 | } |
463 | | |
464 | | NS_IMPL_ISUPPORTS_INHERITED(nsAnimationReceiver, nsMutationReceiver, |
465 | | nsIAnimationObserver) |
466 | | |
467 | | // Observer |
468 | | |
469 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMMutationObserver) |
470 | 0 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
471 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
472 | 0 | NS_INTERFACE_MAP_ENTRY(nsDOMMutationObserver) |
473 | 0 | NS_INTERFACE_MAP_END |
474 | | |
475 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMMutationObserver) |
476 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMMutationObserver) |
477 | | |
478 | | NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMMutationObserver) |
479 | | |
480 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDOMMutationObserver) |
481 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER |
482 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_END |
483 | | |
484 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDOMMutationObserver) |
485 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER |
486 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) |
487 | 0 | for (int32_t i = 0; i < tmp->mReceivers.Count(); ++i) { |
488 | 0 | tmp->mReceivers[i]->Disconnect(false); |
489 | 0 | } |
490 | 0 | tmp->mReceivers.Clear(); |
491 | 0 | tmp->ClearPendingRecords(); |
492 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) |
493 | 0 | // No need to handle mTransientReceivers |
494 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
495 | | |
496 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsDOMMutationObserver) |
497 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) |
498 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReceivers) |
499 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFirstPendingMutation) |
500 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) |
501 | 0 | // No need to handle mTransientReceivers |
502 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
503 | | |
504 | | nsMutationReceiver* |
505 | | nsDOMMutationObserver::GetReceiverFor(nsINode* aNode, bool aMayCreate, |
506 | | bool aWantsAnimations) |
507 | 0 | { |
508 | 0 | MOZ_ASSERT(aMayCreate || !aWantsAnimations, |
509 | 0 | "the value of aWantsAnimations doesn't matter when aMayCreate is " |
510 | 0 | "false, so just pass in false for it"); |
511 | 0 |
|
512 | 0 | if (!aMayCreate && !aNode->MayHaveDOMMutationObserver()) { |
513 | 0 | return nullptr; |
514 | 0 | } |
515 | 0 | |
516 | 0 | for (int32_t i = 0; i < mReceivers.Count(); ++i) { |
517 | 0 | if (mReceivers[i]->Target() == aNode) { |
518 | 0 | return mReceivers[i]; |
519 | 0 | } |
520 | 0 | } |
521 | 0 | if (!aMayCreate) { |
522 | 0 | return nullptr; |
523 | 0 | } |
524 | 0 | |
525 | 0 | nsMutationReceiver* r; |
526 | 0 | if (aWantsAnimations) { |
527 | 0 | r = nsAnimationReceiver::Create(aNode, this); |
528 | 0 | } else { |
529 | 0 | r = nsMutationReceiver::Create(aNode, this); |
530 | 0 | } |
531 | 0 | mReceivers.AppendObject(r); |
532 | 0 | return r; |
533 | 0 | } |
534 | | |
535 | | void |
536 | | nsDOMMutationObserver::RemoveReceiver(nsMutationReceiver* aReceiver) |
537 | 0 | { |
538 | 0 | mReceivers.RemoveObject(aReceiver); |
539 | 0 | } |
540 | | |
541 | | void |
542 | | nsDOMMutationObserver::GetAllSubtreeObserversFor(nsINode* aNode, |
543 | | nsTArray<nsMutationReceiver*>& |
544 | | aReceivers) |
545 | 0 | { |
546 | 0 | nsINode* n = aNode; |
547 | 0 | while (n) { |
548 | 0 | if (n->MayHaveDOMMutationObserver()) { |
549 | 0 | nsMutationReceiver* r = GetReceiverFor(n, false, false); |
550 | 0 | if (r && r->Subtree() && !aReceivers.Contains(r)) { |
551 | 0 | aReceivers.AppendElement(r); |
552 | 0 | // If we've found all the receivers the observer has, |
553 | 0 | // no need to search for more. |
554 | 0 | if (mReceivers.Count() == int32_t(aReceivers.Length())) { |
555 | 0 | return; |
556 | 0 | } |
557 | 0 | } |
558 | 0 | nsCOMArray<nsMutationReceiver>* transientReceivers = nullptr; |
559 | 0 | if (mTransientReceivers.Get(n, &transientReceivers) && transientReceivers) { |
560 | 0 | for (int32_t i = 0; i < transientReceivers->Count(); ++i) { |
561 | 0 | nsMutationReceiver* r = transientReceivers->ObjectAt(i); |
562 | 0 | nsMutationReceiver* parent = r->GetParent(); |
563 | 0 | if (r->Subtree() && parent && !aReceivers.Contains(parent)) { |
564 | 0 | aReceivers.AppendElement(parent); |
565 | 0 | } |
566 | 0 | } |
567 | 0 | if (mReceivers.Count() == int32_t(aReceivers.Length())) { |
568 | 0 | return; |
569 | 0 | } |
570 | 0 | } |
571 | 0 | } |
572 | 0 | n = n->GetParentNode(); |
573 | 0 | } |
574 | 0 | } |
575 | | |
576 | | void |
577 | | nsDOMMutationObserver::ScheduleForRun() |
578 | 0 | { |
579 | 0 | nsDOMMutationObserver::AddCurrentlyHandlingObserver(this, sMutationLevel); |
580 | 0 |
|
581 | 0 | if (mWaitingForRun) { |
582 | 0 | return; |
583 | 0 | } |
584 | 0 | mWaitingForRun = true; |
585 | 0 | RescheduleForRun(); |
586 | 0 | } |
587 | | |
588 | | class MutationObserverMicroTask final : public MicroTaskRunnable |
589 | | { |
590 | | public: |
591 | | virtual void Run(AutoSlowOperation& aAso) override |
592 | 0 | { |
593 | 0 | nsDOMMutationObserver::HandleMutations(aAso); |
594 | 0 | } |
595 | | |
596 | | virtual bool Suppressed() override |
597 | 0 | { |
598 | 0 | return nsDOMMutationObserver::AllScheduledMutationObserversAreSuppressed(); |
599 | 0 | } |
600 | | }; |
601 | | |
602 | | /* static */ void |
603 | | nsDOMMutationObserver::QueueMutationObserverMicroTask() |
604 | 0 | { |
605 | 0 | CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); |
606 | 0 | if (!ccjs) { |
607 | 0 | return; |
608 | 0 | } |
609 | 0 | |
610 | 0 | RefPtr<MutationObserverMicroTask> momt = |
611 | 0 | new MutationObserverMicroTask(); |
612 | 0 | ccjs->DispatchToMicroTask(momt.forget()); |
613 | 0 | } |
614 | | |
615 | | void |
616 | | nsDOMMutationObserver::HandleMutations(mozilla::AutoSlowOperation& aAso) |
617 | 0 | { |
618 | 0 | if (sScheduledMutationObservers || |
619 | 0 | mozilla::dom::DocGroup::sPendingDocGroups) { |
620 | 0 | HandleMutationsInternal(aAso); |
621 | 0 | } |
622 | 0 | } |
623 | | |
624 | | void |
625 | | nsDOMMutationObserver::RescheduleForRun() |
626 | 0 | { |
627 | 0 | if (!sScheduledMutationObservers) { |
628 | 0 | CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); |
629 | 0 | if (!ccjs) { |
630 | 0 | return; |
631 | 0 | } |
632 | 0 | |
633 | 0 | RefPtr<MutationObserverMicroTask> momt = |
634 | 0 | new MutationObserverMicroTask(); |
635 | 0 | ccjs->DispatchToMicroTask(momt.forget()); |
636 | 0 | sScheduledMutationObservers = new AutoTArray<RefPtr<nsDOMMutationObserver>, 4>; |
637 | 0 | } |
638 | 0 |
|
639 | 0 | bool didInsert = false; |
640 | 0 | for (uint32_t i = 0; i < sScheduledMutationObservers->Length(); ++i) { |
641 | 0 | if (static_cast<nsDOMMutationObserver*>((*sScheduledMutationObservers)[i]) |
642 | 0 | ->mId > mId) { |
643 | 0 | sScheduledMutationObservers->InsertElementAt(i, this); |
644 | 0 | didInsert = true; |
645 | 0 | break; |
646 | 0 | } |
647 | 0 | } |
648 | 0 | if (!didInsert) { |
649 | 0 | sScheduledMutationObservers->AppendElement(this); |
650 | 0 | } |
651 | 0 | } |
652 | | |
653 | | void |
654 | | nsDOMMutationObserver::Observe(nsINode& aTarget, |
655 | | const mozilla::dom::MutationObserverInit& aOptions, |
656 | | mozilla::ErrorResult& aRv) |
657 | 0 | { |
658 | 0 |
|
659 | 0 | bool childList = aOptions.mChildList; |
660 | 0 | bool attributes = |
661 | 0 | aOptions.mAttributes.WasPassed() && |
662 | 0 | aOptions.mAttributes.Value(); |
663 | 0 | bool characterData = |
664 | 0 | aOptions.mCharacterData.WasPassed() && |
665 | 0 | aOptions.mCharacterData.Value(); |
666 | 0 | bool subtree = aOptions.mSubtree; |
667 | 0 | bool attributeOldValue = |
668 | 0 | aOptions.mAttributeOldValue.WasPassed() && |
669 | 0 | aOptions.mAttributeOldValue.Value(); |
670 | 0 | bool nativeAnonymousChildList = aOptions.mNativeAnonymousChildList; |
671 | 0 | bool characterDataOldValue = |
672 | 0 | aOptions.mCharacterDataOldValue.WasPassed() && |
673 | 0 | aOptions.mCharacterDataOldValue.Value(); |
674 | 0 | bool animations = aOptions.mAnimations; |
675 | 0 |
|
676 | 0 | if (!aOptions.mAttributes.WasPassed() && |
677 | 0 | (aOptions.mAttributeOldValue.WasPassed() || |
678 | 0 | aOptions.mAttributeFilter.WasPassed())) { |
679 | 0 | attributes = true; |
680 | 0 | } |
681 | 0 |
|
682 | 0 | if (!aOptions.mCharacterData.WasPassed() && |
683 | 0 | aOptions.mCharacterDataOldValue.WasPassed()) { |
684 | 0 | characterData = true; |
685 | 0 | } |
686 | 0 |
|
687 | 0 | if (!(childList || attributes || characterData || animations || |
688 | 0 | nativeAnonymousChildList)) { |
689 | 0 | aRv.Throw(NS_ERROR_DOM_TYPE_ERR); |
690 | 0 | return; |
691 | 0 | } |
692 | 0 | |
693 | 0 | if (aOptions.mAttributeOldValue.WasPassed() && |
694 | 0 | aOptions.mAttributeOldValue.Value() && |
695 | 0 | aOptions.mAttributes.WasPassed() && |
696 | 0 | !aOptions.mAttributes.Value()) { |
697 | 0 | aRv.Throw(NS_ERROR_DOM_TYPE_ERR); |
698 | 0 | return; |
699 | 0 | } |
700 | 0 | |
701 | 0 | if (aOptions.mAttributeFilter.WasPassed() && |
702 | 0 | aOptions.mAttributes.WasPassed() && |
703 | 0 | !aOptions.mAttributes.Value()) { |
704 | 0 | aRv.Throw(NS_ERROR_DOM_TYPE_ERR); |
705 | 0 | return; |
706 | 0 | } |
707 | 0 | |
708 | 0 | if (aOptions.mCharacterDataOldValue.WasPassed() && |
709 | 0 | aOptions.mCharacterDataOldValue.Value() && |
710 | 0 | aOptions.mCharacterData.WasPassed() && |
711 | 0 | !aOptions.mCharacterData.Value()) { |
712 | 0 | aRv.Throw(NS_ERROR_DOM_TYPE_ERR); |
713 | 0 | return; |
714 | 0 | } |
715 | 0 | |
716 | 0 | nsTArray<RefPtr<nsAtom>> filters; |
717 | 0 | bool allAttrs = true; |
718 | 0 | if (aOptions.mAttributeFilter.WasPassed()) { |
719 | 0 | allAttrs = false; |
720 | 0 | const mozilla::dom::Sequence<nsString>& filtersAsString = |
721 | 0 | aOptions.mAttributeFilter.Value(); |
722 | 0 | uint32_t len = filtersAsString.Length(); |
723 | 0 | filters.SetCapacity(len); |
724 | 0 |
|
725 | 0 | for (uint32_t i = 0; i < len; ++i) { |
726 | 0 | filters.AppendElement(NS_Atomize(filtersAsString[i])); |
727 | 0 | } |
728 | 0 | } |
729 | 0 |
|
730 | 0 | nsMutationReceiver* r = GetReceiverFor(&aTarget, true, animations); |
731 | 0 | r->SetChildList(childList); |
732 | 0 | r->SetAttributes(attributes); |
733 | 0 | r->SetCharacterData(characterData); |
734 | 0 | r->SetSubtree(subtree); |
735 | 0 | r->SetAttributeOldValue(attributeOldValue); |
736 | 0 | r->SetCharacterDataOldValue(characterDataOldValue); |
737 | 0 | r->SetNativeAnonymousChildList(nativeAnonymousChildList); |
738 | 0 | r->SetAttributeFilter(std::move(filters)); |
739 | 0 | r->SetAllAttributes(allAttrs); |
740 | 0 | r->SetAnimations(animations); |
741 | 0 | r->RemoveClones(); |
742 | 0 |
|
743 | | #ifdef DEBUG |
744 | | for (int32_t i = 0; i < mReceivers.Count(); ++i) { |
745 | | NS_WARNING_ASSERTION(mReceivers[i]->Target(), |
746 | | "All the receivers should have a target!"); |
747 | | } |
748 | | #endif |
749 | | } |
750 | | |
751 | | void |
752 | | nsDOMMutationObserver::Disconnect() |
753 | 0 | { |
754 | 0 | for (int32_t i = 0; i < mReceivers.Count(); ++i) { |
755 | 0 | mReceivers[i]->Disconnect(false); |
756 | 0 | } |
757 | 0 | mReceivers.Clear(); |
758 | 0 | mCurrentMutations.Clear(); |
759 | 0 | ClearPendingRecords(); |
760 | 0 | } |
761 | | |
762 | | void |
763 | | nsDOMMutationObserver::TakeRecords( |
764 | | nsTArray<RefPtr<nsDOMMutationRecord> >& aRetVal) |
765 | 0 | { |
766 | 0 | aRetVal.Clear(); |
767 | 0 | aRetVal.SetCapacity(mPendingMutationCount); |
768 | 0 | RefPtr<nsDOMMutationRecord> current; |
769 | 0 | current.swap(mFirstPendingMutation); |
770 | 0 | for (uint32_t i = 0; i < mPendingMutationCount; ++i) { |
771 | 0 | RefPtr<nsDOMMutationRecord> next; |
772 | 0 | current->mNext.swap(next); |
773 | 0 | if (!mMergeAttributeRecords || |
774 | 0 | !MergeableAttributeRecord(aRetVal.SafeLastElement(nullptr), |
775 | 0 | current)) { |
776 | 0 | *aRetVal.AppendElement() = current.forget(); |
777 | 0 | } |
778 | 0 | current.swap(next); |
779 | 0 | } |
780 | 0 | ClearPendingRecords(); |
781 | 0 | } |
782 | | |
783 | | void |
784 | | nsDOMMutationObserver::GetObservingInfo( |
785 | | nsTArray<Nullable<MutationObservingInfo>>& aResult, |
786 | | mozilla::ErrorResult& aRv) |
787 | 0 | { |
788 | 0 | aResult.SetCapacity(mReceivers.Count()); |
789 | 0 | for (int32_t i = 0; i < mReceivers.Count(); ++i) { |
790 | 0 | MutationObservingInfo& info = aResult.AppendElement()->SetValue(); |
791 | 0 | nsMutationReceiver* mr = mReceivers[i]; |
792 | 0 | info.mChildList = mr->ChildList(); |
793 | 0 | info.mAttributes.Construct(mr->Attributes()); |
794 | 0 | info.mCharacterData.Construct(mr->CharacterData()); |
795 | 0 | info.mSubtree = mr->Subtree(); |
796 | 0 | info.mAttributeOldValue.Construct(mr->AttributeOldValue()); |
797 | 0 | info.mCharacterDataOldValue.Construct(mr->CharacterDataOldValue()); |
798 | 0 | info.mNativeAnonymousChildList = mr->NativeAnonymousChildList(); |
799 | 0 | info.mAnimations = mr->Animations(); |
800 | 0 | nsTArray<RefPtr<nsAtom>>& filters = mr->AttributeFilter(); |
801 | 0 | if (filters.Length()) { |
802 | 0 | info.mAttributeFilter.Construct(); |
803 | 0 | mozilla::dom::Sequence<nsString>& filtersAsStrings = |
804 | 0 | info.mAttributeFilter.Value(); |
805 | 0 | nsString* strings = filtersAsStrings.AppendElements(filters.Length(), |
806 | 0 | mozilla::fallible); |
807 | 0 | if (!strings) { |
808 | 0 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
809 | 0 | return; |
810 | 0 | } |
811 | 0 | for (size_t j = 0; j < filters.Length(); ++j) { |
812 | 0 | filters[j]->ToString(strings[j]); |
813 | 0 | } |
814 | 0 | } |
815 | 0 | info.mObservedNode = mr->Target(); |
816 | 0 | } |
817 | 0 | } |
818 | | |
819 | | // static |
820 | | already_AddRefed<nsDOMMutationObserver> |
821 | | nsDOMMutationObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal, |
822 | | mozilla::dom::MutationCallback& aCb, |
823 | | mozilla::ErrorResult& aRv) |
824 | 0 | { |
825 | 0 | nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports()); |
826 | 0 | if (!window) { |
827 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
828 | 0 | return nullptr; |
829 | 0 | } |
830 | 0 | bool isChrome = nsContentUtils::IsChromeDoc(window->GetExtantDoc()); |
831 | 0 | RefPtr<nsDOMMutationObserver> observer = |
832 | 0 | new nsDOMMutationObserver(window.forget(), aCb, isChrome); |
833 | 0 | return observer.forget(); |
834 | 0 | } |
835 | | |
836 | | |
837 | | bool |
838 | | nsDOMMutationObserver::MergeableAttributeRecord(nsDOMMutationRecord* aOldRecord, |
839 | | nsDOMMutationRecord* aRecord) |
840 | 0 | { |
841 | 0 | MOZ_ASSERT(mMergeAttributeRecords); |
842 | 0 | return |
843 | 0 | aOldRecord && |
844 | 0 | aOldRecord->mType == nsGkAtoms::attributes && |
845 | 0 | aOldRecord->mType == aRecord->mType && |
846 | 0 | aOldRecord->mTarget == aRecord->mTarget && |
847 | 0 | aOldRecord->mAttrName == aRecord->mAttrName && |
848 | 0 | aOldRecord->mAttrNamespace.Equals(aRecord->mAttrNamespace); |
849 | 0 | } |
850 | | |
851 | | void |
852 | | nsDOMMutationObserver::HandleMutation() |
853 | 0 | { |
854 | 0 | NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "Whaat!"); |
855 | 0 | NS_ASSERTION(mCurrentMutations.IsEmpty(), |
856 | 0 | "Still generating MutationRecords?"); |
857 | 0 |
|
858 | 0 | mWaitingForRun = false; |
859 | 0 |
|
860 | 0 | for (int32_t i = 0; i < mReceivers.Count(); ++i) { |
861 | 0 | mReceivers[i]->RemoveClones(); |
862 | 0 | } |
863 | 0 | mTransientReceivers.Clear(); |
864 | 0 |
|
865 | 0 | nsPIDOMWindowOuter* outer = mOwner->GetOuterWindow(); |
866 | 0 | if (!mPendingMutationCount || !outer || |
867 | 0 | outer->GetCurrentInnerWindow() != mOwner) { |
868 | 0 | ClearPendingRecords(); |
869 | 0 | return; |
870 | 0 | } |
871 | 0 | |
872 | 0 | mozilla::dom::Sequence<mozilla::OwningNonNull<nsDOMMutationRecord> > |
873 | 0 | mutations; |
874 | 0 | if (mutations.SetCapacity(mPendingMutationCount, mozilla::fallible)) { |
875 | 0 | // We can't use TakeRecords easily here, because it deals with a |
876 | 0 | // different type of array, and we want to optimize out any extra copying. |
877 | 0 | RefPtr<nsDOMMutationRecord> current; |
878 | 0 | current.swap(mFirstPendingMutation); |
879 | 0 | for (uint32_t i = 0; i < mPendingMutationCount; ++i) { |
880 | 0 | RefPtr<nsDOMMutationRecord> next; |
881 | 0 | current->mNext.swap(next); |
882 | 0 | if (!mMergeAttributeRecords || |
883 | 0 | !MergeableAttributeRecord(mutations.Length() ? |
884 | 0 | mutations.LastElement().get() : nullptr, |
885 | 0 | current)) { |
886 | 0 | *mutations.AppendElement(mozilla::fallible) = current; |
887 | 0 | } |
888 | 0 | current.swap(next); |
889 | 0 | } |
890 | 0 | } |
891 | 0 | ClearPendingRecords(); |
892 | 0 |
|
893 | 0 | mCallback->Call(this, mutations, *this); |
894 | 0 | } |
895 | | |
896 | | void |
897 | | nsDOMMutationObserver::HandleMutationsInternal(AutoSlowOperation& aAso) |
898 | 0 | { |
899 | 0 | nsTArray<RefPtr<nsDOMMutationObserver> >* suppressedObservers = nullptr; |
900 | 0 |
|
901 | 0 | // This loop implements: |
902 | 0 | // * Let signalList be a copy of unit of related similar-origin browsing |
903 | 0 | // contexts' signal slot list. |
904 | 0 | // * Empty unit of related similar-origin browsing contexts' signal slot |
905 | 0 | // list. |
906 | 0 | nsTArray<RefPtr<HTMLSlotElement>> signalList; |
907 | 0 | if (DocGroup::sPendingDocGroups) { |
908 | 0 | for (DocGroup* docGroup : *DocGroup::sPendingDocGroups) { |
909 | 0 | docGroup->MoveSignalSlotListTo(signalList); |
910 | 0 | } |
911 | 0 | delete DocGroup::sPendingDocGroups; |
912 | 0 | DocGroup::sPendingDocGroups = nullptr; |
913 | 0 | } |
914 | 0 |
|
915 | 0 | if (sScheduledMutationObservers) { |
916 | 0 | AutoTArray<RefPtr<nsDOMMutationObserver>, 4>* observers = |
917 | 0 | sScheduledMutationObservers; |
918 | 0 | sScheduledMutationObservers = nullptr; |
919 | 0 | for (uint32_t i = 0; i < observers->Length(); ++i) { |
920 | 0 | RefPtr<nsDOMMutationObserver> currentObserver = |
921 | 0 | static_cast<nsDOMMutationObserver*>((*observers)[i]); |
922 | 0 | if (!currentObserver->Suppressed()) { |
923 | 0 | currentObserver->HandleMutation(); |
924 | 0 | } else { |
925 | 0 | if (!suppressedObservers) { |
926 | 0 | suppressedObservers = new nsTArray<RefPtr<nsDOMMutationObserver> >; |
927 | 0 | } |
928 | 0 | if (!suppressedObservers->Contains(currentObserver)) { |
929 | 0 | suppressedObservers->AppendElement(currentObserver); |
930 | 0 | } |
931 | 0 | } |
932 | 0 | } |
933 | 0 | delete observers; |
934 | 0 | aAso.CheckForInterrupt(); |
935 | 0 | } |
936 | 0 |
|
937 | 0 | if (suppressedObservers) { |
938 | 0 | for (uint32_t i = 0; i < suppressedObservers->Length(); ++i) { |
939 | 0 | static_cast<nsDOMMutationObserver*>(suppressedObservers->ElementAt(i))-> |
940 | 0 | RescheduleForRun(); |
941 | 0 | } |
942 | 0 | delete suppressedObservers; |
943 | 0 | suppressedObservers = nullptr; |
944 | 0 | } |
945 | 0 |
|
946 | 0 | // Fire slotchange event for each slot in signalList. |
947 | 0 | for (uint32_t i = 0; i < signalList.Length(); ++i) { |
948 | 0 | signalList[i]->FireSlotChangeEvent(); |
949 | 0 | } |
950 | 0 | } |
951 | | |
952 | | nsDOMMutationRecord* |
953 | | nsDOMMutationObserver::CurrentRecord(nsAtom* aType) |
954 | 0 | { |
955 | 0 | NS_ASSERTION(sMutationLevel > 0, "Unexpected mutation level!"); |
956 | 0 |
|
957 | 0 | while (mCurrentMutations.Length() < sMutationLevel) { |
958 | 0 | mCurrentMutations.AppendElement(static_cast<nsDOMMutationRecord*>(nullptr)); |
959 | 0 | } |
960 | 0 |
|
961 | 0 | uint32_t last = sMutationLevel - 1; |
962 | 0 | if (!mCurrentMutations[last]) { |
963 | 0 | RefPtr<nsDOMMutationRecord> r = new nsDOMMutationRecord(aType, GetParentObject()); |
964 | 0 | mCurrentMutations[last] = r; |
965 | 0 | AppendMutationRecord(r.forget()); |
966 | 0 | ScheduleForRun(); |
967 | 0 | } |
968 | 0 |
|
969 | | #ifdef DEBUG |
970 | | MOZ_ASSERT(sCurrentlyHandlingObservers->Length() == sMutationLevel); |
971 | | for (size_t i = 0; i < sCurrentlyHandlingObservers->Length(); ++i) { |
972 | | MOZ_ASSERT(sCurrentlyHandlingObservers->ElementAt(i).Contains(this), |
973 | | "MutationObserver should be added as an observer of all the " |
974 | | "nested mutations!"); |
975 | | } |
976 | | #endif |
977 | |
|
978 | 0 | NS_ASSERTION(mCurrentMutations[last]->mType == aType, |
979 | 0 | "Unexpected MutationRecord type!"); |
980 | 0 |
|
981 | 0 | return mCurrentMutations[last]; |
982 | 0 | } |
983 | | |
984 | | nsDOMMutationObserver::~nsDOMMutationObserver() |
985 | 0 | { |
986 | 0 | for (int32_t i = 0; i < mReceivers.Count(); ++i) { |
987 | 0 | mReceivers[i]->RemoveClones(); |
988 | 0 | } |
989 | 0 | } |
990 | | |
991 | | void |
992 | | nsDOMMutationObserver::EnterMutationHandling() |
993 | 0 | { |
994 | 0 | ++sMutationLevel; |
995 | 0 | } |
996 | | |
997 | | // Leave the current mutation level (there can be several levels if in case |
998 | | // of nested calls to the nsIMutationObserver methods). |
999 | | // The most recent mutation record is removed from mCurrentMutations, so |
1000 | | // that is doesn't get modified anymore by receivers. |
1001 | | void |
1002 | | nsDOMMutationObserver::LeaveMutationHandling() |
1003 | 0 | { |
1004 | 0 | if (sCurrentlyHandlingObservers && |
1005 | 0 | sCurrentlyHandlingObservers->Length() == sMutationLevel) { |
1006 | 0 | nsTArray<RefPtr<nsDOMMutationObserver> >& obs = |
1007 | 0 | sCurrentlyHandlingObservers->ElementAt(sMutationLevel - 1); |
1008 | 0 | for (uint32_t i = 0; i < obs.Length(); ++i) { |
1009 | 0 | nsDOMMutationObserver* o = |
1010 | 0 | static_cast<nsDOMMutationObserver*>(obs[i]); |
1011 | 0 | if (o->mCurrentMutations.Length() == sMutationLevel) { |
1012 | 0 | // It is already in pending mutations. |
1013 | 0 | o->mCurrentMutations.RemoveElementAt(sMutationLevel - 1); |
1014 | 0 | } |
1015 | 0 | } |
1016 | 0 | sCurrentlyHandlingObservers->RemoveElementAt(sMutationLevel - 1); |
1017 | 0 | } |
1018 | 0 | --sMutationLevel; |
1019 | 0 | } |
1020 | | |
1021 | | void |
1022 | | nsDOMMutationObserver::AddCurrentlyHandlingObserver(nsDOMMutationObserver* aObserver, |
1023 | | uint32_t aMutationLevel) |
1024 | 0 | { |
1025 | 0 | NS_ASSERTION(aMutationLevel > 0, "Unexpected mutation level!"); |
1026 | 0 |
|
1027 | 0 | if (aMutationLevel > 1) { |
1028 | 0 | // MutationObserver must be in the currently handling observer list |
1029 | 0 | // in all the nested levels. |
1030 | 0 | AddCurrentlyHandlingObserver(aObserver, aMutationLevel - 1); |
1031 | 0 | } |
1032 | 0 |
|
1033 | 0 | if (!sCurrentlyHandlingObservers) { |
1034 | 0 | sCurrentlyHandlingObservers = |
1035 | 0 | new AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 4>, 4>; |
1036 | 0 | } |
1037 | 0 |
|
1038 | 0 | while (sCurrentlyHandlingObservers->Length() < aMutationLevel) { |
1039 | 0 | sCurrentlyHandlingObservers->InsertElementAt( |
1040 | 0 | sCurrentlyHandlingObservers->Length()); |
1041 | 0 | } |
1042 | 0 |
|
1043 | 0 | uint32_t index = aMutationLevel - 1; |
1044 | 0 | if (!sCurrentlyHandlingObservers->ElementAt(index).Contains(aObserver)) { |
1045 | 0 | sCurrentlyHandlingObservers->ElementAt(index).AppendElement(aObserver); |
1046 | 0 | } |
1047 | 0 | } |
1048 | | |
1049 | | void |
1050 | | nsDOMMutationObserver::Shutdown() |
1051 | 0 | { |
1052 | 0 | delete sCurrentlyHandlingObservers; |
1053 | 0 | sCurrentlyHandlingObservers = nullptr; |
1054 | 0 | delete sScheduledMutationObservers; |
1055 | 0 | sScheduledMutationObservers = nullptr; |
1056 | 0 | } |
1057 | | |
1058 | | nsAutoMutationBatch* |
1059 | | nsAutoMutationBatch::sCurrentBatch = nullptr; |
1060 | | |
1061 | | void |
1062 | | nsAutoMutationBatch::Done() |
1063 | 0 | { |
1064 | 0 | if (sCurrentBatch != this) { |
1065 | 0 | return; |
1066 | 0 | } |
1067 | 0 | |
1068 | 0 | sCurrentBatch = mPreviousBatch; |
1069 | 0 | if (mObservers.IsEmpty()) { |
1070 | 0 | nsDOMMutationObserver::LeaveMutationHandling(); |
1071 | 0 | // Nothing to do. |
1072 | 0 | return; |
1073 | 0 | } |
1074 | 0 | |
1075 | 0 | uint32_t len = mObservers.Length(); |
1076 | 0 | for (uint32_t i = 0; i < len; ++i) { |
1077 | 0 | nsDOMMutationObserver* ob = mObservers[i].mObserver; |
1078 | 0 | bool wantsChildList = mObservers[i].mWantsChildList; |
1079 | 0 |
|
1080 | 0 | RefPtr<nsSimpleContentList> removedList; |
1081 | 0 | if (wantsChildList) { |
1082 | 0 | removedList = new nsSimpleContentList(mBatchTarget); |
1083 | 0 | } |
1084 | 0 |
|
1085 | 0 | nsTArray<nsMutationReceiver*> allObservers; |
1086 | 0 | ob->GetAllSubtreeObserversFor(mBatchTarget, allObservers); |
1087 | 0 |
|
1088 | 0 | int32_t j = mFromFirstToLast ? 0 : mRemovedNodes.Length() - 1; |
1089 | 0 | int32_t end = mFromFirstToLast ? mRemovedNodes.Length() : -1; |
1090 | 0 | for (; j != end; mFromFirstToLast ? ++j : --j) { |
1091 | 0 | nsCOMPtr<nsIContent> removed = mRemovedNodes[j]; |
1092 | 0 | if (removedList) { |
1093 | 0 | removedList->AppendElement(removed); |
1094 | 0 | } |
1095 | 0 |
|
1096 | 0 | if (allObservers.Length()) { |
1097 | 0 | nsCOMArray<nsMutationReceiver>* transientReceivers = |
1098 | 0 | ob->mTransientReceivers.LookupForAdd(removed).OrInsert( |
1099 | 0 | [] () { return new nsCOMArray<nsMutationReceiver>(); }); |
1100 | 0 | for (uint32_t k = 0; k < allObservers.Length(); ++k) { |
1101 | 0 | nsMutationReceiver* r = allObservers[k]; |
1102 | 0 | nsMutationReceiver* orig = r->GetParent() ? r->GetParent() : r; |
1103 | 0 | if (ob->GetReceiverFor(removed, false, false) != orig) { |
1104 | 0 | // Make sure the elements which are removed from the |
1105 | 0 | // subtree are kept in the same observation set. |
1106 | 0 | nsMutationReceiver* tr; |
1107 | 0 | if (orig->Animations()) { |
1108 | 0 | tr = nsAnimationReceiver::Create(removed, orig); |
1109 | 0 | } else { |
1110 | 0 | tr = nsMutationReceiver::Create(removed, orig); |
1111 | 0 | } |
1112 | 0 | transientReceivers->AppendObject(tr); |
1113 | 0 | } |
1114 | 0 | } |
1115 | 0 | } |
1116 | 0 | } |
1117 | 0 | if (wantsChildList && (mRemovedNodes.Length() || mAddedNodes.Length())) { |
1118 | 0 | RefPtr<nsSimpleContentList> addedList = |
1119 | 0 | new nsSimpleContentList(mBatchTarget); |
1120 | 0 | for (uint32_t i = 0; i < mAddedNodes.Length(); ++i) { |
1121 | 0 | addedList->AppendElement(mAddedNodes[i]); |
1122 | 0 | } |
1123 | 0 | RefPtr<nsDOMMutationRecord> m = |
1124 | 0 | new nsDOMMutationRecord(nsGkAtoms::childList, |
1125 | 0 | ob->GetParentObject()); |
1126 | 0 | m->mTarget = mBatchTarget; |
1127 | 0 | m->mRemovedNodes = removedList; |
1128 | 0 | m->mAddedNodes = addedList; |
1129 | 0 | m->mPreviousSibling = mPrevSibling; |
1130 | 0 | m->mNextSibling = mNextSibling; |
1131 | 0 | ob->AppendMutationRecord(m.forget()); |
1132 | 0 | } |
1133 | 0 | // Always schedule the observer so that transient receivers are |
1134 | 0 | // removed correctly. |
1135 | 0 | ob->ScheduleForRun(); |
1136 | 0 | } |
1137 | 0 | nsDOMMutationObserver::LeaveMutationHandling(); |
1138 | 0 | } |
1139 | | |
1140 | | nsAutoAnimationMutationBatch* |
1141 | | nsAutoAnimationMutationBatch::sCurrentBatch = nullptr; |
1142 | | |
1143 | | void |
1144 | | nsAutoAnimationMutationBatch::Done() |
1145 | 0 | { |
1146 | 0 | if (sCurrentBatch != this) { |
1147 | 0 | return; |
1148 | 0 | } |
1149 | 0 | |
1150 | 0 | sCurrentBatch = nullptr; |
1151 | 0 | if (mObservers.IsEmpty()) { |
1152 | 0 | nsDOMMutationObserver::LeaveMutationHandling(); |
1153 | 0 | // Nothing to do. |
1154 | 0 | return; |
1155 | 0 | } |
1156 | 0 | |
1157 | 0 | mBatchTargets.Sort(TreeOrderComparator()); |
1158 | 0 |
|
1159 | 0 | for (nsDOMMutationObserver* ob : mObservers) { |
1160 | 0 | bool didAddRecords = false; |
1161 | 0 |
|
1162 | 0 | for (nsINode* target : mBatchTargets) { |
1163 | 0 | EntryArray* entries = mEntryTable.Get(target); |
1164 | 0 | MOZ_ASSERT(entries, |
1165 | 0 | "Targets in entry table and targets list should match"); |
1166 | 0 |
|
1167 | 0 | RefPtr<nsDOMMutationRecord> m = |
1168 | 0 | new nsDOMMutationRecord(nsGkAtoms::animations, ob->GetParentObject()); |
1169 | 0 | m->mTarget = target; |
1170 | 0 |
|
1171 | 0 | for (const Entry& e : *entries) { |
1172 | 0 | if (e.mState == eState_Added) { |
1173 | 0 | m->mAddedAnimations.AppendElement(e.mAnimation); |
1174 | 0 | } else if (e.mState == eState_Removed) { |
1175 | 0 | m->mRemovedAnimations.AppendElement(e.mAnimation); |
1176 | 0 | } else if (e.mState == eState_RemainedPresent && e.mChanged) { |
1177 | 0 | m->mChangedAnimations.AppendElement(e.mAnimation); |
1178 | 0 | } |
1179 | 0 | } |
1180 | 0 |
|
1181 | 0 | if (!m->mAddedAnimations.IsEmpty() || |
1182 | 0 | !m->mChangedAnimations.IsEmpty() || |
1183 | 0 | !m->mRemovedAnimations.IsEmpty()) { |
1184 | 0 | ob->AppendMutationRecord(m.forget()); |
1185 | 0 | didAddRecords = true; |
1186 | 0 | } |
1187 | 0 | } |
1188 | 0 |
|
1189 | 0 | if (didAddRecords) { |
1190 | 0 | ob->ScheduleForRun(); |
1191 | 0 | } |
1192 | 0 | } |
1193 | 0 | nsDOMMutationObserver::LeaveMutationHandling(); |
1194 | 0 | } |