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
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "MutationObservers.h"
9 #include "nsContentUtils.h"
10 #include "nsCSSPseudoElements.h"
12 #include "nsIContent.h"
13 #include "nsIContentInlines.h"
14 #include "mozilla/dom/Document.h"
15 #include "mozilla/dom/DocumentInlines.h"
16 #include "mozilla/dom/Element.h"
17 #include "nsIMutationObserver.h"
18 #include "mozilla/EventListenerManager.h"
19 #include "PLDHashTable.h"
20 #include "nsCOMArray.h"
21 #include "nsPIDOMWindow.h"
23 # include "nsXULElement.h"
25 #include "nsGenericHTMLElement.h"
26 #include "mozilla/AnimationTarget.h"
27 #include "mozilla/Assertions.h"
28 #include "mozilla/ErrorResult.h"
29 #include "mozilla/dom/Animation.h"
30 #include "mozilla/dom/KeyframeEffect.h"
31 #include "mozilla/PresShell.h"
32 #include "nsWrapperCacheInlines.h"
33 #include "nsDOMMutationObserver.h"
34 #include "mozilla/dom/BindingUtils.h"
35 #include "mozilla/dom/CustomElementRegistry.h"
36 #include "mozilla/dom/HTMLTemplateElement.h"
37 #include "mozilla/dom/ShadowRoot.h"
39 using namespace mozilla
;
40 using namespace mozilla::dom
;
42 #define NOTIFY_PRESSHELL(notify_) \
43 if (PresShell* presShell = doc->GetObservingPresShell()) { \
47 #define DEFINE_NOTIFIERS(func_, params_) \
48 auto notifyPresShell = [&](PresShell* aPresShell) { \
49 aPresShell->func_ params_; \
51 auto notifyObserver = [&](nsIMutationObserver* aObserver) { \
52 aObserver->func_ params_; \
55 template <typename NotifyObserver
>
56 static inline nsINode
* ForEachAncestorObserver(nsINode
* aNode
,
57 NotifyObserver
& aFunc
) {
59 nsINode
* node
= aNode
;
61 nsAutoTObserverArray
<nsIMutationObserver
*, 1>* observers
=
62 node
->GetMutationObservers();
63 if (observers
&& !observers
->IsEmpty()) {
64 for (nsIMutationObserver
* obs
: observers
->ForwardRange()) {
69 if (ShadowRoot
* shadow
= ShadowRoot::FromNode(node
)) {
70 node
= shadow
->GetHost();
72 node
= node
->GetParentNode();
78 enum class IsRemoval
{ No
, Yes
};
79 enum class ShouldAssert
{ No
, Yes
};
81 template <IsRemoval aIsRemoval
= IsRemoval::No
,
82 ShouldAssert aShouldAssert
= ShouldAssert::Yes
,
83 typename NotifyPresShell
, typename NotifyObserver
>
84 static inline void Notify(nsINode
* aNode
, NotifyPresShell
& aNotifyPresShell
,
85 NotifyObserver
& aNotifyObserver
) {
86 Document
* doc
= aNode
->OwnerDoc();
87 nsDOMMutationEnterLeave
enterLeave(doc
);
89 const bool wasConnected
= aNode
->IsInComposedDoc();
90 // For removals, the pres shell gets notified first, since it needs to operate
91 // on the "old" DOM shape.
92 if (aIsRemoval
== IsRemoval::Yes
&& wasConnected
) {
93 NOTIFY_PRESSHELL(aNotifyPresShell
);
95 nsINode
* last
= ForEachAncestorObserver(aNode
, aNotifyObserver
);
96 // For non-removals, the pres shell gets notified last, since it needs to
97 // operate on the "final" DOM shape.
98 if (aIsRemoval
== IsRemoval::No
&& last
== doc
) {
99 NOTIFY_PRESSHELL(aNotifyPresShell
);
101 if (aShouldAssert
== ShouldAssert::Yes
) {
102 MOZ_ASSERT((last
== doc
) == wasConnected
,
103 "If we were connected we should notify all ancestors all the "
104 "way to the document");
108 #define IMPL_ANIMATION_NOTIFICATION(func_, content_, params_) \
110 nsDOMMutationEnterLeave enterLeave(doc); \
111 auto forEach = [&](nsIMutationObserver* aObserver) { \
112 if (nsCOMPtr<nsIAnimationObserver> obs = do_QueryInterface(aObserver)) { \
113 obs->func_ params_; \
116 ForEachAncestorObserver(content_, forEach); \
120 void MutationObservers::NotifyCharacterDataWillChange(
121 nsIContent
* aContent
, const CharacterDataChangeInfo
& aInfo
) {
122 DEFINE_NOTIFIERS(CharacterDataWillChange
, (aContent
, aInfo
));
123 Notify(aContent
, notifyPresShell
, notifyObserver
);
126 void MutationObservers::NotifyCharacterDataChanged(
127 nsIContent
* aContent
, const CharacterDataChangeInfo
& aInfo
) {
128 aContent
->OwnerDoc()->Changed();
129 DEFINE_NOTIFIERS(CharacterDataChanged
, (aContent
, aInfo
));
130 Notify(aContent
, notifyPresShell
, notifyObserver
);
133 void MutationObservers::NotifyAttributeWillChange(Element
* aElement
,
134 int32_t aNameSpaceID
,
137 DEFINE_NOTIFIERS(AttributeWillChange
,
138 (aElement
, aNameSpaceID
, aAttribute
, aModType
));
139 Notify(aElement
, notifyPresShell
, notifyObserver
);
142 void MutationObservers::NotifyAttributeChanged(Element
* aElement
,
143 int32_t aNameSpaceID
,
146 const nsAttrValue
* aOldValue
) {
147 aElement
->OwnerDoc()->Changed();
148 DEFINE_NOTIFIERS(AttributeChanged
,
149 (aElement
, aNameSpaceID
, aAttribute
, aModType
, aOldValue
));
150 Notify(aElement
, notifyPresShell
, notifyObserver
);
153 void MutationObservers::NotifyAttributeSetToCurrentValue(Element
* aElement
,
154 int32_t aNameSpaceID
,
155 nsAtom
* aAttribute
) {
156 DEFINE_NOTIFIERS(AttributeSetToCurrentValue
,
157 (aElement
, aNameSpaceID
, aAttribute
));
158 Notify(aElement
, notifyPresShell
, notifyObserver
);
161 void MutationObservers::NotifyContentAppended(nsIContent
* aContainer
,
162 nsIContent
* aFirstNewContent
) {
163 aContainer
->OwnerDoc()->Changed();
164 DEFINE_NOTIFIERS(ContentAppended
, (aFirstNewContent
));
165 Notify(aContainer
, notifyPresShell
, notifyObserver
);
168 void MutationObservers::NotifyNativeAnonymousChildListChange(
169 nsIContent
* aContent
, bool aIsRemove
) {
170 DEFINE_NOTIFIERS(NativeAnonymousChildListChange
, (aContent
, aIsRemove
));
172 // We can't actually assert that we reach the document if we're connected,
173 // since this notification runs from UnbindFromTree.
174 Notify
<IsRemoval::Yes
, ShouldAssert::No
>(aContent
, notifyPresShell
,
177 Notify(aContent
, notifyPresShell
, notifyObserver
);
181 void MutationObservers::NotifyContentInserted(nsINode
* aContainer
,
182 nsIContent
* aChild
) {
183 MOZ_ASSERT(aContainer
->IsContent() || aContainer
->IsDocument(),
184 "container must be an nsIContent or an Document");
185 aContainer
->OwnerDoc()->Changed();
186 DEFINE_NOTIFIERS(ContentInserted
, (aChild
));
187 Notify(aContainer
, notifyPresShell
, notifyObserver
);
190 void MutationObservers::NotifyContentRemoved(nsINode
* aContainer
,
192 nsIContent
* aPreviousSibling
) {
193 MOZ_ASSERT(aContainer
->IsContent() || aContainer
->IsDocument(),
194 "container must be an nsIContent or an Document");
195 aContainer
->OwnerDoc()->Changed();
196 MOZ_ASSERT(aChild
->GetParentNode() == aContainer
,
197 "We expect the parent link to be still around at this point");
198 DEFINE_NOTIFIERS(ContentRemoved
, (aChild
, aPreviousSibling
));
199 Notify
<IsRemoval::Yes
>(aContainer
, notifyPresShell
, notifyObserver
);
202 } // namespace mozilla
204 void MutationObservers::NotifyAnimationMutated(
205 dom::Animation
* aAnimation
, AnimationMutationType aMutatedType
) {
206 MOZ_ASSERT(aAnimation
);
208 NonOwningAnimationTarget target
= aAnimation
->GetTargetForAnimation();
213 // A pseudo element and its parent element use the same owner doc.
214 Document
* doc
= target
.mElement
->OwnerDoc();
215 if (doc
->MayHaveAnimationObservers()) {
216 // we use the its parent element as the subject in DOM Mutation Observer.
217 Element
* elem
= target
.mElement
;
218 switch (aMutatedType
) {
219 case AnimationMutationType::Added
:
220 IMPL_ANIMATION_NOTIFICATION(AnimationAdded
, elem
, (aAnimation
));
222 case AnimationMutationType::Changed
:
223 IMPL_ANIMATION_NOTIFICATION(AnimationChanged
, elem
, (aAnimation
));
225 case AnimationMutationType::Removed
:
226 IMPL_ANIMATION_NOTIFICATION(AnimationRemoved
, elem
, (aAnimation
));
229 MOZ_ASSERT_UNREACHABLE("unexpected mutation type");
234 void MutationObservers::NotifyAnimationAdded(dom::Animation
* aAnimation
) {
235 NotifyAnimationMutated(aAnimation
, AnimationMutationType::Added
);
238 void MutationObservers::NotifyAnimationChanged(dom::Animation
* aAnimation
) {
239 NotifyAnimationMutated(aAnimation
, AnimationMutationType::Changed
);
242 void MutationObservers::NotifyAnimationRemoved(dom::Animation
* aAnimation
) {
243 NotifyAnimationMutated(aAnimation
, AnimationMutationType::Removed
);