MutationObservers.cpp (10008B)
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/. */ 6 7 #include "MutationObservers.h" 8 9 #include "PLDHashTable.h" 10 #include "mozilla/AnimationTarget.h" 11 #include "mozilla/Assertions.h" 12 #include "mozilla/ErrorResult.h" 13 #include "mozilla/EventListenerManager.h" 14 #include "mozilla/PresShell.h" 15 #include "mozilla/dom/Animation.h" 16 #include "mozilla/dom/BindingUtils.h" 17 #include "mozilla/dom/CustomElementRegistry.h" 18 #include "mozilla/dom/Document.h" 19 #include "mozilla/dom/DocumentInlines.h" 20 #include "mozilla/dom/Element.h" 21 #include "mozilla/dom/HTMLTemplateElement.h" 22 #include "mozilla/dom/KeyframeEffect.h" 23 #include "mozilla/dom/ShadowRoot.h" 24 #include "nsCOMArray.h" 25 #include "nsCSSPseudoElements.h" 26 #include "nsContentUtils.h" 27 #include "nsDOMMutationObserver.h" 28 #include "nsGenericHTMLElement.h" 29 #include "nsIContent.h" 30 #include "nsIContentInlines.h" 31 #include "nsIMutationObserver.h" 32 #include "nsINode.h" 33 #include "nsPIDOMWindow.h" 34 #include "nsWrapperCacheInlines.h" 35 #include "nsXULElement.h" 36 37 using namespace mozilla; 38 using namespace mozilla::dom; 39 40 #define NOTIFY_PRESSHELL(notify_) \ 41 if (PresShell* presShell = doc->GetObservingPresShell()) { \ 42 notify_(presShell); \ 43 } 44 45 #define NOTIFIER(func_, ...) \ 46 [&](nsIMutationObserver* aObserver) { aObserver->func_(__VA_ARGS__); } 47 48 template <typename NotifyObserver> 49 static inline nsINode* ForEachAncestorObserver(nsINode* aNode, 50 NotifyObserver& aFunc, 51 uint32_t aCallback) { 52 nsINode* last; 53 nsINode* node = aNode; 54 do { 55 mozilla::SafeDoublyLinkedList<nsIMutationObserver>* observers = 56 node->GetMutationObservers(); 57 if (observers) { 58 for (auto iter = observers->begin(); iter != observers->end(); ++iter) { 59 if (iter->IsCallbackEnabled(aCallback)) { 60 aFunc(&*iter); 61 } 62 } 63 } 64 last = node; 65 if (!(node = node->GetParentNode())) { 66 if (ShadowRoot* shadow = ShadowRoot::FromNode(last)) { 67 node = shadow->GetHost(); 68 } 69 } 70 } while (node); 71 return last; 72 } 73 74 // Whether to notify to the PresShell about a mutation. 75 // For removals, the pres shell gets notified first, since it needs to operate 76 // on the "old" DOM shape. 77 enum class NotifyPresShell { No, Before, After }; 78 79 template <NotifyPresShell aNotifyPresShell = NotifyPresShell::After, 80 typename NotifyObserver> 81 static inline void Notify(nsINode* aNode, NotifyObserver&& aNotify, 82 uint32_t aCallback) { 83 Document* doc = aNode->OwnerDoc(); 84 nsDOMMutationEnterLeave enterLeave(doc); 85 86 #ifdef DEBUG 87 const bool wasConnected = aNode->IsInComposedDoc(); 88 #endif 89 if constexpr (aNotifyPresShell == NotifyPresShell::Before) { 90 if (aNode->IsInComposedDoc()) { 91 NOTIFY_PRESSHELL(aNotify); 92 } 93 } 94 nsINode* last = ForEachAncestorObserver(aNode, aNotify, aCallback); 95 // For non-removals, the pres shell gets notified last, since it needs to 96 // operate on the "final" DOM shape. 97 if constexpr (aNotifyPresShell == NotifyPresShell::After) { 98 if (last == doc) { 99 NOTIFY_PRESSHELL(aNotify); 100 } 101 } 102 MOZ_ASSERT((last == doc) == wasConnected, 103 "If we were connected we should notify all ancestors all the " 104 "way to the document"); 105 } 106 107 #define IMPL_ANIMATION_NOTIFICATION(func_, content_, params_) \ 108 PR_BEGIN_MACRO \ 109 nsDOMMutationEnterLeave enterLeave(doc); \ 110 auto forEach = [&](nsIMutationObserver* aObserver) { \ 111 if (nsCOMPtr<nsIAnimationObserver> obs = do_QueryInterface(aObserver)) { \ 112 obs->func_ params_; \ 113 } \ 114 }; \ 115 ForEachAncestorObserver(content_, forEach, nsIMutationObserver::k##func_); \ 116 PR_END_MACRO 117 118 namespace mozilla { 119 void MutationObservers::NotifyCharacterDataWillChange( 120 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { 121 Notify(aContent, NOTIFIER(CharacterDataWillChange, aContent, aInfo), 122 nsIMutationObserver::kCharacterDataWillChange); 123 } 124 125 void MutationObservers::NotifyCharacterDataChanged( 126 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { 127 aContent->OwnerDoc()->Changed(); 128 Notify(aContent, NOTIFIER(CharacterDataChanged, aContent, aInfo), 129 nsIMutationObserver::kCharacterDataChanged); 130 } 131 132 void MutationObservers::NotifyAttributeWillChange(Element* aElement, 133 int32_t aNameSpaceID, 134 nsAtom* aAttribute, 135 AttrModType aModType) { 136 Notify(aElement, 137 NOTIFIER(AttributeWillChange, aElement, aNameSpaceID, aAttribute, 138 aModType), 139 nsIMutationObserver::kAttributeWillChange); 140 } 141 142 void MutationObservers::NotifyAttributeChanged(Element* aElement, 143 int32_t aNameSpaceID, 144 nsAtom* aAttribute, 145 AttrModType aModType, 146 const nsAttrValue* aOldValue) { 147 aElement->OwnerDoc()->Changed(); 148 Notify(aElement, 149 NOTIFIER(AttributeChanged, aElement, aNameSpaceID, aAttribute, 150 aModType, aOldValue), 151 nsIMutationObserver::kAttributeChanged); 152 } 153 154 void MutationObservers::NotifyAttributeSetToCurrentValue(Element* aElement, 155 int32_t aNameSpaceID, 156 nsAtom* aAttribute) { 157 Notify( 158 aElement, 159 NOTIFIER(AttributeSetToCurrentValue, aElement, aNameSpaceID, aAttribute), 160 nsIMutationObserver::kAttributeSetToCurrentValue); 161 } 162 163 void MutationObservers::NotifyContentAppended(nsIContent* aContainer, 164 nsIContent* aFirstNewContent, 165 const ContentAppendInfo& aInfo) { 166 aContainer->OwnerDoc()->Changed(); 167 Notify(aContainer, NOTIFIER(ContentAppended, aFirstNewContent, aInfo), 168 nsIMutationObserver::kContentAppended); 169 } 170 171 void MutationObservers::NotifyContentInserted(nsINode* aContainer, 172 nsIContent* aChild, 173 const ContentInsertInfo& aInfo) { 174 MOZ_ASSERT(aContainer->IsContent() || aContainer->IsDocument(), 175 "container must be an nsIContent or an Document"); 176 aContainer->OwnerDoc()->Changed(); 177 Notify(aContainer, NOTIFIER(ContentInserted, aChild, aInfo), 178 nsIMutationObserver::kContentInserted); 179 } 180 181 void MutationObservers::NotifyContentWillBeRemoved( 182 nsINode* aContainer, nsIContent* aChild, const ContentRemoveInfo& aInfo) { 183 MOZ_ASSERT(aContainer->IsContent() || aContainer->IsDocument(), 184 "container must be an nsIContent or an Document"); 185 MOZ_ASSERT(aChild->GetParentNode() == aContainer, 186 "We expect the parent link to be still around at this point"); 187 aContainer->OwnerDoc()->Changed(); 188 Notify<NotifyPresShell::Before>(aContainer, 189 NOTIFIER(ContentWillBeRemoved, aChild, aInfo), 190 nsIMutationObserver::kContentWillBeRemoved); 191 } 192 193 void MutationObservers::NotifyARIAAttributeDefaultWillChange( 194 mozilla::dom::Element* aElement, nsAtom* aAttribute, AttrModType aModType) { 195 Notify<NotifyPresShell::No>( 196 aElement, 197 NOTIFIER(ARIAAttributeDefaultWillChange, aElement, aAttribute, aModType), 198 nsIMutationObserver::kARIAAttributeDefaultWillChange); 199 } 200 201 void MutationObservers::NotifyARIAAttributeDefaultChanged( 202 mozilla::dom::Element* aElement, nsAtom* aAttribute, AttrModType aModType) { 203 Notify<NotifyPresShell::No>( 204 aElement, 205 NOTIFIER(ARIAAttributeDefaultChanged, aElement, aAttribute, aModType), 206 nsIMutationObserver::kARIAAttributeDefaultChanged); 207 } 208 209 } // namespace mozilla 210 211 void MutationObservers::NotifyAnimationMutated( 212 dom::Animation* aAnimation, AnimationMutationType aMutatedType) { 213 MOZ_ASSERT(aAnimation); 214 215 NonOwningAnimationTarget target = aAnimation->GetTargetForAnimation(); 216 if (!target) { 217 return; 218 } 219 220 // A pseudo element and its parent element use the same owner doc. 221 Document* doc = target.mElement->OwnerDoc(); 222 if (doc->MayHaveAnimationObservers()) { 223 // we use the its parent element as the subject in DOM Mutation Observer. 224 Element* elem = target.mElement; 225 switch (aMutatedType) { 226 case AnimationMutationType::Added: 227 IMPL_ANIMATION_NOTIFICATION(AnimationAdded, elem, (aAnimation)); 228 break; 229 case AnimationMutationType::Changed: 230 IMPL_ANIMATION_NOTIFICATION(AnimationChanged, elem, (aAnimation)); 231 break; 232 case AnimationMutationType::Removed: 233 IMPL_ANIMATION_NOTIFICATION(AnimationRemoved, elem, (aAnimation)); 234 break; 235 default: 236 MOZ_ASSERT_UNREACHABLE("unexpected mutation type"); 237 } 238 } 239 } 240 241 void MutationObservers::NotifyAnimationAdded(dom::Animation* aAnimation) { 242 NotifyAnimationMutated(aAnimation, AnimationMutationType::Added); 243 } 244 245 void MutationObservers::NotifyAnimationChanged(dom::Animation* aAnimation) { 246 NotifyAnimationMutated(aAnimation, AnimationMutationType::Changed); 247 } 248 249 void MutationObservers::NotifyAnimationRemoved(dom::Animation* aAnimation) { 250 NotifyAnimationMutated(aAnimation, AnimationMutationType::Removed); 251 }