/* -*- 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/. */ #include "MutationObservers.h" #include "nsContentUtils.h" #include "nsCSSPseudoElements.h" #include "nsINode.h" #include "nsIContent.h" #include "nsIContentInlines.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/Element.h" #include "nsIMutationObserver.h" #include "mozilla/EventListenerManager.h" #include "PLDHashTable.h" #include "nsCOMArray.h" #include "nsPIDOMWindow.h" #ifdef MOZ_XUL # include "nsXULElement.h" #endif #include "nsGenericHTMLElement.h" #include "mozilla/AnimationTarget.h" #include "mozilla/Assertions.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/Animation.h" #include "mozilla/dom/KeyframeEffect.h" #include "mozilla/PresShell.h" #include "nsWrapperCacheInlines.h" #include "nsDOMMutationObserver.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/CustomElementRegistry.h" #include "mozilla/dom/HTMLTemplateElement.h" #include "mozilla/dom/ShadowRoot.h" using namespace mozilla; using namespace mozilla::dom; using mozilla::AutoJSContext; #define NOTIFY_PRESSHELL_IF(condition_, forEach_) \ if (condition_) { \ if (PresShell* presShell = doc->GetObservingPresShell()) { \ forEach_(presShell); \ } \ } #define DEFINE_NOTIFIERS(func_, params_) \ auto notifyPresShell = [&](PresShell* aPresShell) { \ aPresShell->func_ params_; \ }; \ auto notifyObserver = [&](nsIMutationObserver* aObserver) { \ aObserver->func_ params_; \ }; static inline nsINode* ForEachAncestorObserver( nsINode* aNode, FunctionRef aFunc) { nsINode* last; nsINode* node = aNode; do { nsAutoTObserverArray* observers = node->GetMutationObservers(); if (observers && !observers->IsEmpty()) { for (nsIMutationObserver* obs : observers->ForwardRange()) { aFunc(obs); } } last = node; if (ShadowRoot* shadow = ShadowRoot::FromNode(node)) { node = shadow->GetHost(); } else { node = node->GetParentNode(); } } while (node); return last; } static inline void NotifyRemovalImpl( nsINode* aNode, FunctionRef aNotifyPresShell, FunctionRef aNotifyObserver) { Document* doc = aNode->OwnerDoc(); nsDOMMutationEnterLeave enterLeave(doc); NOTIFY_PRESSHELL_IF(aNode->GetComposedDoc(), aNotifyPresShell); DebugOnly last = ForEachAncestorObserver(aNode, aNotifyObserver); MOZ_ASSERT((last == doc) == !!aNode->GetComposedDoc()); } static inline void NotifyRemovalImplNoAssert( nsINode* aNode, FunctionRef aNotifyPresShell, FunctionRef aNotifyObserver) { Document* doc = aNode->OwnerDoc(); nsDOMMutationEnterLeave enterLeave(doc); NOTIFY_PRESSHELL_IF(aNode->GetComposedDoc(), aNotifyPresShell); ForEachAncestorObserver(aNode, aNotifyObserver); } static inline void NotifyNonRemovalImpl( nsINode* aNode, FunctionRef aNotifyPresShell, FunctionRef aNotifyObserver) { Document* doc = aNode->OwnerDoc(); nsDOMMutationEnterLeave enterLeave(doc); nsINode* last = ForEachAncestorObserver(aNode, aNotifyObserver); NOTIFY_PRESSHELL_IF(last == doc, aNotifyPresShell); MOZ_ASSERT((last == doc) == !!aNode->GetComposedDoc()); } #define IMPL_ANIMATION_NOTIFICATION(func_, content_, params_) \ PR_BEGIN_MACRO \ nsDOMMutationEnterLeave enterLeave(doc); \ auto forEach = [&](nsIMutationObserver* aObserver) { \ if (nsCOMPtr obs = do_QueryInterface(aObserver)) { \ obs->func_ params_; \ } \ }; \ ForEachAncestorObserver(content_, forEach); \ PR_END_MACRO namespace mozilla { void MutationObservers::NotifyCharacterDataWillChange( nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { DEFINE_NOTIFIERS(CharacterDataWillChange, (aContent, aInfo)); NotifyNonRemovalImpl(aContent, notifyPresShell, notifyObserver); } void MutationObservers::NotifyCharacterDataChanged( nsIContent* aContent, const CharacterDataChangeInfo& aInfo) { aContent->OwnerDoc()->Changed(); DEFINE_NOTIFIERS(CharacterDataChanged, (aContent, aInfo)); NotifyNonRemovalImpl(aContent, notifyPresShell, notifyObserver); } void MutationObservers::NotifyAttributeWillChange(Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType) { DEFINE_NOTIFIERS(AttributeWillChange, (aElement, aNameSpaceID, aAttribute, aModType)); NotifyNonRemovalImpl(aElement, notifyPresShell, notifyObserver); } void MutationObservers::NotifyAttributeChanged(Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) { aElement->OwnerDoc()->Changed(); DEFINE_NOTIFIERS(AttributeChanged, (aElement, aNameSpaceID, aAttribute, aModType, aOldValue)); NotifyNonRemovalImpl(aElement, notifyPresShell, notifyObserver); } void MutationObservers::NotifyAttributeSetToCurrentValue(Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute) { DEFINE_NOTIFIERS(AttributeSetToCurrentValue, (aElement, aNameSpaceID, aAttribute)); NotifyNonRemovalImpl(aElement, notifyPresShell, notifyObserver); } void MutationObservers::NotifyContentAppended(nsIContent* aContainer, nsIContent* aFirstNewContent) { aContainer->OwnerDoc()->Changed(); DEFINE_NOTIFIERS(ContentAppended, (aFirstNewContent)); NotifyNonRemovalImpl(aContainer, notifyPresShell, notifyObserver); } void MutationObservers::NotifyNativeAnonymousChildListChange( nsIContent* aContent, bool aIsRemove) { DEFINE_NOTIFIERS(NativeAnonymousChildListChange, (aContent, aIsRemove)); if (aIsRemove) { // It doesn't reach the document since it runs from UnbindFromTree NotifyRemovalImplNoAssert(aContent, notifyPresShell, notifyObserver); } else { NotifyNonRemovalImpl(aContent, notifyPresShell, notifyObserver); } } void MutationObservers::NotifyContentInserted(nsINode* aContainer, nsIContent* aChild) { MOZ_ASSERT(aContainer->IsContent() || aContainer->IsDocument(), "container must be an nsIContent or an Document"); aContainer->OwnerDoc()->Changed(); DEFINE_NOTIFIERS(ContentInserted, (aChild)); NotifyNonRemovalImpl(aContainer, notifyPresShell, notifyObserver); } void MutationObservers::NotifyContentRemoved(nsINode* aContainer, nsIContent* aChild, nsIContent* aPreviousSibling) { MOZ_ASSERT(aContainer->IsContent() || aContainer->IsDocument(), "container must be an nsIContent or an Document"); aContainer->OwnerDoc()->Changed(); MOZ_ASSERT(aChild->GetParentNode() == aContainer, "We expect the parent link to be still around at this point"); DEFINE_NOTIFIERS(ContentRemoved, (aChild, aPreviousSibling)); NotifyRemovalImpl(aContainer, notifyPresShell, notifyObserver); } } // namespace mozilla void MutationObservers::NotifyAnimationMutated( dom::Animation* aAnimation, AnimationMutationType aMutatedType) { MOZ_ASSERT(aAnimation); NonOwningAnimationTarget target = aAnimation->GetTargetForAnimation(); if (!target) { return; } // A pseudo element and its parent element use the same owner doc. Document* doc = target.mElement->OwnerDoc(); if (doc->MayHaveAnimationObservers()) { // we use the its parent element as the subject in DOM Mutation Observer. Element* elem = target.mElement; switch (aMutatedType) { case AnimationMutationType::Added: IMPL_ANIMATION_NOTIFICATION(AnimationAdded, elem, (aAnimation)); break; case AnimationMutationType::Changed: IMPL_ANIMATION_NOTIFICATION(AnimationChanged, elem, (aAnimation)); break; case AnimationMutationType::Removed: IMPL_ANIMATION_NOTIFICATION(AnimationRemoved, elem, (aAnimation)); break; default: MOZ_ASSERT_UNREACHABLE("unexpected mutation type"); } } } void MutationObservers::NotifyAnimationAdded(dom::Animation* aAnimation) { NotifyAnimationMutated(aAnimation, AnimationMutationType::Added); } void MutationObservers::NotifyAnimationChanged(dom::Animation* aAnimation) { NotifyAnimationMutated(aAnimation, AnimationMutationType::Changed); } void MutationObservers::NotifyAnimationRemoved(dom::Animation* aAnimation) { NotifyAnimationMutated(aAnimation, AnimationMutationType::Removed); }