diff options
Diffstat (limited to 'dom/base/MutationObservers.cpp')
-rw-r--r-- | dom/base/MutationObservers.cpp | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/dom/base/MutationObservers.cpp b/dom/base/MutationObservers.cpp new file mode 100644 index 0000000000..10ca0c7b99 --- /dev/null +++ b/dom/base/MutationObservers.cpp @@ -0,0 +1,242 @@ +/* -*- 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<void(nsIMutationObserver*)> aFunc) { + nsINode* last; + nsINode* node = aNode; + do { + nsAutoTObserverArray<nsIMutationObserver*, 1>* 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<void(PresShell*)> aNotifyPresShell, + FunctionRef<void(nsIMutationObserver*)> aNotifyObserver) { + Document* doc = aNode->OwnerDoc(); + nsDOMMutationEnterLeave enterLeave(doc); + NOTIFY_PRESSHELL_IF(aNode->GetComposedDoc(), aNotifyPresShell); + DebugOnly<nsINode*> last = ForEachAncestorObserver(aNode, aNotifyObserver); + MOZ_ASSERT((last == doc) == !!aNode->GetComposedDoc()); +} + +static inline void NotifyRemovalImplNoAssert( + nsINode* aNode, FunctionRef<void(PresShell*)> aNotifyPresShell, + FunctionRef<void(nsIMutationObserver*)> aNotifyObserver) { + Document* doc = aNode->OwnerDoc(); + nsDOMMutationEnterLeave enterLeave(doc); + NOTIFY_PRESSHELL_IF(aNode->GetComposedDoc(), aNotifyPresShell); + ForEachAncestorObserver(aNode, aNotifyObserver); +} + +static inline void NotifyNonRemovalImpl( + nsINode* aNode, FunctionRef<void(PresShell*)> aNotifyPresShell, + FunctionRef<void(nsIMutationObserver*)> 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<nsIAnimationObserver> 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); +} |