/* -*- 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/. */ /* * Base class for all DOM nodes. */ #include "nsINode.h" #include "AccessCheck.h" #include "jsapi.h" #include "js/ForOfIterator.h" // JS::ForOfIterator #include "js/JSON.h" // JS_ParseJSON #include "mozAutoDocUpdate.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/CORSMode.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventListenerManager.h" #include "mozilla/HTMLEditor.h" #include "mozilla/InternalMutationEvent.h" #include "mozilla/Likely.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/PresShell.h" #include "mozilla/ServoBindings.h" #include "mozilla/Telemetry.h" #include "mozilla/TextControlElement.h" #include "mozilla/TextEditor.h" #include "mozilla/TimeStamp.h" #include "mozilla/dom/BindContext.h" #include "mozilla/dom/CharacterData.h" #include "mozilla/dom/ChildIterator.h" #include "mozilla/dom/CustomElementRegistry.h" #include "mozilla/dom/DebuggerNotificationBinding.h" #include "mozilla/dom/DocumentType.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/Exceptions.h" #include "mozilla/dom/Link.h" #include "mozilla/dom/HTMLImageElement.h" #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/dom/HTMLTemplateElement.h" #include "mozilla/dom/MutationObservers.h" #include "mozilla/dom/Selection.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/SVGUseElement.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/L10nOverlays.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/StaticPrefs_layout.h" #include "nsAttrValueOrString.h" #include "nsCCUncollectableMarker.h" #include "nsContentCreatorFunctions.h" #include "nsContentList.h" #include "nsContentUtils.h" #include "nsCOMArray.h" #include "nsCycleCollectionParticipant.h" #include "mozilla/dom/Attr.h" #include "nsDOMAttributeMap.h" #include "nsDOMCID.h" #include "nsDOMCSSAttrDeclaration.h" #include "nsError.h" #include "nsDOMMutationObserver.h" #include "nsDOMString.h" #include "nsDOMTokenList.h" #include "nsFocusManager.h" #include "nsFrameSelection.h" #include "nsGenericHTMLElement.h" #include "nsGkAtoms.h" #include "nsIAnonymousContentCreator.h" #include "nsAtom.h" #include "nsIContentInlines.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "nsIFrameInlines.h" #include "mozilla/dom/NodeInfo.h" #include "mozilla/dom/NodeInfoInlines.h" #include "nsIScriptGlobalObject.h" #include "nsIScrollableFrame.h" #include "nsView.h" #include "nsViewManager.h" #include "nsIWidget.h" #include "nsLayoutUtils.h" #include "nsNameSpaceManager.h" #include "nsNodeInfoManager.h" #include "nsObjectLoadingContent.h" #include "nsPIDOMWindow.h" #include "nsPresContext.h" #include "nsPrintfCString.h" #include "nsRange.h" #include "nsString.h" #include "nsStyleConsts.h" #include "nsTextNode.h" #include "nsUnicharUtils.h" #include "nsWindowSizes.h" #include "mozilla/Preferences.h" #include "xpcpublic.h" #include "HTMLLegendElement.h" #include "nsWrapperCacheInlines.h" #include "WrapperFactory.h" #include #include "nsGlobalWindow.h" #include "GeometryUtils.h" #include "nsIAnimationObserver.h" #include "nsChildContentList.h" #include "mozilla/dom/NodeBinding.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/AncestorIterator.h" #include "xpcprivate.h" #include "XPathGenerator.h" #ifdef ACCESSIBILITY # include "mozilla/dom/AccessibleNode.h" #endif using namespace mozilla; using namespace mozilla::dom; static bool ShouldUseNACScope(const nsINode* aNode) { return aNode->IsInNativeAnonymousSubtree(); } static bool ShouldUseUAWidgetScope(const nsINode* aNode) { return aNode->HasBeenInUAWidget(); } void* nsINode::operator new(size_t aSize, nsNodeInfoManager* aManager) { if (StaticPrefs::dom_arena_allocator_enabled_AtStartup()) { MOZ_ASSERT(aManager, "nsNodeInfoManager needs to be initialized"); return aManager->Allocate(aSize); } return ::operator new(aSize); } void nsINode::operator delete(void* aPtr) { free_impl(aPtr); } bool nsINode::IsInclusiveDescendantOf(const nsINode* aNode) const { MOZ_ASSERT(aNode, "The node is nullptr."); if (aNode == this) { return true; } if (!aNode->HasFlag(NODE_MAY_HAVE_ELEMENT_CHILDREN)) { return GetParentNode() == aNode; } for (nsINode* node : Ancestors(*this)) { if (node == aNode) { return true; } } return false; } bool nsINode::IsInclusiveFlatTreeDescendantOf(const nsINode* aNode) const { MOZ_ASSERT(aNode, "The node is nullptr."); for (nsINode* node : InclusiveFlatTreeAncestors(*this)) { if (node == aNode) { return true; } } return false; } bool nsINode::IsShadowIncludingInclusiveDescendantOf( const nsINode* aNode) const { MOZ_ASSERT(aNode, "The node is nullptr."); if (this->GetComposedDoc() == aNode) { return true; } const nsINode* node = this; do { if (node == aNode) { return true; } node = node->GetParentOrShadowHostNode(); } while (node); return false; } nsINode::nsSlots::nsSlots() : mWeakReference(nullptr) {} nsINode::nsSlots::~nsSlots() { if (mChildNodes) { mChildNodes->InvalidateCacheIfAvailable(); } if (mWeakReference) { mWeakReference->NoticeNodeDestruction(); } } void nsINode::nsSlots::Traverse(nsCycleCollectionTraversalCallback& cb) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mChildNodes"); cb.NoteXPCOMChild(mChildNodes); } void nsINode::nsSlots::Unlink(nsINode&) { if (mChildNodes) { mChildNodes->InvalidateCacheIfAvailable(); ImplCycleCollectionUnlink(mChildNodes); } } //---------------------------------------------------------------------- #ifdef MOZILLA_INTERNAL_API nsINode::nsINode(already_AddRefed&& aNodeInfo) : mNodeInfo(std::move(aNodeInfo)), mParent(nullptr) # ifndef BOOL_FLAGS_ON_WRAPPER_CACHE , mBoolFlags(0) # endif , mChildCount(0), mPreviousOrLastSibling(nullptr), mSubtreeRoot(this), mSlots(nullptr) { } #endif nsINode::~nsINode() { MOZ_ASSERT(!HasSlots(), "LastRelease was not called?"); MOZ_ASSERT(mSubtreeRoot == this, "Didn't restore state properly?"); } #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED void nsINode::AssertInvariantsOnNodeInfoChange() { MOZ_DIAGNOSTIC_ASSERT(!IsInComposedDoc()); if (nsCOMPtr link = do_QueryInterface(this)) { MOZ_DIAGNOSTIC_ASSERT(!link->HasPendingLinkUpdate()); } } #endif void* nsINode::GetProperty(const nsAtom* aPropertyName, nsresult* aStatus) const { if (!HasProperties()) { // a fast HasFlag() test if (aStatus) { *aStatus = NS_PROPTABLE_PROP_NOT_THERE; } return nullptr; } return OwnerDoc()->PropertyTable().GetProperty(this, aPropertyName, aStatus); } nsresult nsINode::SetProperty(nsAtom* aPropertyName, void* aValue, NSPropertyDtorFunc aDtor, bool aTransfer) { nsresult rv = OwnerDoc()->PropertyTable().SetProperty( this, aPropertyName, aValue, aDtor, nullptr, aTransfer); if (NS_SUCCEEDED(rv)) { SetFlags(NODE_HAS_PROPERTIES); } return rv; } void nsINode::RemoveProperty(const nsAtom* aPropertyName) { OwnerDoc()->PropertyTable().RemoveProperty(this, aPropertyName); } void* nsINode::TakeProperty(const nsAtom* aPropertyName, nsresult* aStatus) { return OwnerDoc()->PropertyTable().TakeProperty(this, aPropertyName, aStatus); } nsIContentSecurityPolicy* nsINode::GetCsp() const { return OwnerDoc()->GetCsp(); } nsINode::nsSlots* nsINode::CreateSlots() { return new nsSlots(); } static const nsINode* GetClosestCommonInclusiveAncestorForRangeInSelection( const nsINode* aNode) { while (aNode && !aNode->IsClosestCommonInclusiveAncestorForRangeInSelection()) { if (!aNode ->IsDescendantOfClosestCommonInclusiveAncestorForRangeInSelection()) { return nullptr; } aNode = aNode->GetParentNode(); } return aNode; } /** * A Comparator suitable for mozilla::BinarySearchIf for searching a collection * of nsRange* for an overlap of (mNode, mStartOffset) .. (mNode, mEndOffset). */ class IsItemInRangeComparator { public: // @param aStartOffset has to be less or equal to aEndOffset. IsItemInRangeComparator(const nsINode& aNode, const uint32_t aStartOffset, const uint32_t aEndOffset, nsContentUtils::ComparePointsCache* aCache) : mNode(aNode), mStartOffset(aStartOffset), mEndOffset(aEndOffset), mCache(aCache) { MOZ_ASSERT(aStartOffset <= aEndOffset); } int operator()(const AbstractRange* const aRange) const { int32_t cmp = nsContentUtils::ComparePoints_Deprecated( &mNode, mEndOffset, aRange->GetStartContainer(), aRange->StartOffset(), nullptr, mCache); if (cmp == 1) { cmp = nsContentUtils::ComparePoints_Deprecated( &mNode, mStartOffset, aRange->GetEndContainer(), aRange->EndOffset(), nullptr, mCache); if (cmp == -1) { return 0; } return 1; } return -1; } private: const nsINode& mNode; const uint32_t mStartOffset; const uint32_t mEndOffset; nsContentUtils::ComparePointsCache* mCache; }; bool nsINode::IsSelected(const uint32_t aStartOffset, const uint32_t aEndOffset) const { MOZ_ASSERT(aStartOffset <= aEndOffset); const nsINode* n = GetClosestCommonInclusiveAncestorForRangeInSelection(this); NS_ASSERTION(n || !IsMaybeSelected(), "A node without a common inclusive ancestor for a range in " "Selection is for sure not selected."); // Collect the selection objects for potential ranges. nsTHashSet ancestorSelections; for (; n; n = GetClosestCommonInclusiveAncestorForRangeInSelection( n->GetParentNode())) { const LinkedList* ranges = n->GetExistingClosestCommonInclusiveAncestorRanges(); if (!ranges) { continue; } for (const AbstractRange* range : *ranges) { MOZ_ASSERT(range->IsInAnySelection(), "Why is this range registered with a node?"); // Looks like that IsInSelection() assert fails sometimes... if (range->IsInAnySelection()) { for (const WeakPtr& selection : range->GetSelections()) { ancestorSelections.Insert(selection); } } } } nsContentUtils::ComparePointsCache cache; IsItemInRangeComparator comparator{*this, aStartOffset, aEndOffset, &cache}; for (Selection* selection : ancestorSelections) { // Binary search the sorted ranges in this selection. // (Selection::GetRangeAt returns its ranges ordered). size_t low = 0; size_t high = selection->RangeCount(); while (high != low) { size_t middle = low + (high - low) / 2; const AbstractRange* const range = selection->GetAbstractRangeAt(middle); int result = comparator(range); if (result == 0) { if (!range->Collapsed()) { return true; } const AbstractRange* middlePlus1; const AbstractRange* middleMinus1; // if node end > start of middle+1, result = 1 if (middle + 1 < high && (middlePlus1 = selection->GetAbstractRangeAt(middle + 1)) && nsContentUtils::ComparePoints_Deprecated( this, aEndOffset, middlePlus1->GetStartContainer(), middlePlus1->StartOffset(), nullptr, &cache) > 0) { result = 1; // if node start < end of middle - 1, result = -1 } else if (middle >= 1 && (middleMinus1 = selection->GetAbstractRangeAt(middle - 1)) && nsContentUtils::ComparePoints_Deprecated( this, aStartOffset, middleMinus1->GetEndContainer(), middleMinus1->EndOffset(), nullptr, &cache) < 0) { result = -1; } else { break; } } if (result < 0) { high = middle; } else { low = middle + 1; } } } return false; } Element* nsINode::GetAnonymousRootElementOfTextEditor( TextEditor** aTextEditor) { if (aTextEditor) { *aTextEditor = nullptr; } RefPtr textControlElement; if (IsInNativeAnonymousSubtree()) { textControlElement = TextControlElement::FromNodeOrNull( GetClosestNativeAnonymousSubtreeRootParentOrHost()); } else { textControlElement = TextControlElement::FromNode(this); } if (!textControlElement) { return nullptr; } RefPtr textEditor = textControlElement->GetTextEditor(); if (!textEditor) { // The found `TextControlElement` may be an input element which is not a // text control element. In this case, such element must not be in a // native anonymous tree of a `TextEditor` so this node is not in any // `TextEditor`. return nullptr; } Element* rootElement = textEditor->GetRoot(); if (aTextEditor) { textEditor.forget(aTextEditor); } return rootElement; } void nsINode::QueueDevtoolsAnonymousEvent(bool aIsRemove) { MOZ_ASSERT(IsRootOfNativeAnonymousSubtree()); MOZ_ASSERT(OwnerDoc()->DevToolsAnonymousAndShadowEventsEnabled()); AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher( this, aIsRemove ? u"anonymousrootremoved"_ns : u"anonymousrootcreated"_ns, CanBubble::eYes, ChromeOnlyDispatch::eYes, Composed::eYes); dispatcher->PostDOMEvent(); } nsINode* nsINode::GetRootNode(const GetRootNodeOptions& aOptions) { if (aOptions.mComposed) { if (Document* doc = GetComposedDoc()) { return doc; } nsINode* node = this; while (node) { node = node->SubtreeRoot(); ShadowRoot* shadow = ShadowRoot::FromNode(node); if (!shadow) { break; } node = shadow->GetHost(); } return node; } return SubtreeRoot(); } nsIContent* nsINode::GetFirstChildOfTemplateOrNode() { if (IsTemplateElement()) { DocumentFragment* frag = static_cast(this)->Content(); return frag->GetFirstChild(); } return GetFirstChild(); } nsINode* nsINode::SubtreeRoot() const { auto RootOfNode = [](const nsINode* aStart) -> nsINode* { const nsINode* node = aStart; const nsINode* iter = node; while ((iter = iter->GetParentNode())) { node = iter; } return const_cast(node); }; // There are four cases of interest here. nsINodes that are really: // 1. Document nodes - Are always in the document. // 2.a nsIContent nodes not in a shadow tree - Are either in the document, // or mSubtreeRoot is updated in BindToTree/UnbindFromTree. // 2.b nsIContent nodes in a shadow tree - Are never in the document, // ignore mSubtreeRoot and return the containing shadow root. // 4. Attr nodes - Are never in the document, and mSubtreeRoot // is always 'this' (as set in nsINode's ctor). nsINode* node; if (IsInUncomposedDoc()) { node = OwnerDocAsNode(); } else if (IsContent()) { ShadowRoot* containingShadow = AsContent()->GetContainingShadow(); node = containingShadow ? containingShadow : mSubtreeRoot; if (!node) { NS_WARNING("Using SubtreeRoot() on unlinked element?"); node = RootOfNode(this); } } else { node = mSubtreeRoot; } MOZ_ASSERT(node, "Should always have a node here!"); #ifdef DEBUG { const nsINode* slowNode = RootOfNode(this); MOZ_ASSERT(slowNode == node, "These should always be in sync!"); } #endif return node; } static nsIContent* GetRootForContentSubtree(nsIContent* aContent) { NS_ENSURE_TRUE(aContent, nullptr); // Special case for ShadowRoot because the ShadowRoot itself is // the root. This is necessary to prevent selection from crossing // the ShadowRoot boundary. // // FIXME(emilio): The NAC check should probably be done before this? We can // have NAC inside shadow DOM. if (ShadowRoot* containingShadow = aContent->GetContainingShadow()) { return containingShadow; } if (nsIContent* nativeAnonRoot = aContent->GetClosestNativeAnonymousSubtreeRoot()) { return nativeAnonRoot; } if (Document* doc = aContent->GetUncomposedDoc()) { return doc->GetRootElement(); } return nsIContent::FromNode(aContent->SubtreeRoot()); } nsIContent* nsINode::GetSelectionRootContent(PresShell* aPresShell) { NS_ENSURE_TRUE(aPresShell, nullptr); if (IsDocument()) return AsDocument()->GetRootElement(); if (!IsContent()) return nullptr; if (GetComposedDoc() != aPresShell->GetDocument()) { return nullptr; } if (AsContent()->HasIndependentSelection() || IsInNativeAnonymousSubtree()) { // This node should be an inclusive descendant of input/textarea editor. // In that case, the anonymous
for TextEditor should be always the // selection root. // FIXME: If Selection for the document is collapsed in or //