| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * (C) 2001 Dirk Mueller (mueller@kde.org) |
| * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All |
| * rights reserved. |
| * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) |
| * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. |
| * (http://www.torchmobile.com/) |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "third_party/blink/renderer/core/dom/node.h" |
| |
| #include "third_party/blink/renderer/bindings/core/v8/node_or_string.h" |
| #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_script.h" |
| #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" |
| #include "third_party/blink/renderer/core/css/css_selector.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_resolver.h" |
| #include "third_party/blink/renderer/core/css/style_change_reason.h" |
| #include "third_party/blink/renderer/core/css/style_engine.h" |
| #include "third_party/blink/renderer/core/dom/attr.h" |
| #include "third_party/blink/renderer/core/dom/attribute.h" |
| #include "third_party/blink/renderer/core/dom/child_list_mutation_scope.h" |
| #include "third_party/blink/renderer/core/dom/child_node_list.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/document_fragment.h" |
| #include "third_party/blink/renderer/core/dom/document_type.h" |
| #include "third_party/blink/renderer/core/dom/dom_node_ids.h" |
| #include "third_party/blink/renderer/core/dom/element.h" |
| #include "third_party/blink/renderer/core/dom/element_rare_data.h" |
| #include "third_party/blink/renderer/core/dom/element_traversal.h" |
| #include "third_party/blink/renderer/core/dom/events/event.h" |
| #include "third_party/blink/renderer/core/dom/events/event_dispatch_forbidden_scope.h" |
| #include "third_party/blink/renderer/core/dom/events/event_dispatcher.h" |
| #include "third_party/blink/renderer/core/dom/events/event_listener.h" |
| #include "third_party/blink/renderer/core/dom/flat_tree_node_data.h" |
| #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h" |
| #include "third_party/blink/renderer/core/dom/get_root_node_options.h" |
| #include "third_party/blink/renderer/core/dom/layout_tree_builder_traversal.h" |
| #include "third_party/blink/renderer/core/dom/mutation_observer_registration.h" |
| #include "third_party/blink/renderer/core/dom/node_lists_node_data.h" |
| #include "third_party/blink/renderer/core/dom/node_rare_data.h" |
| #include "third_party/blink/renderer/core/dom/node_traversal.h" |
| #include "third_party/blink/renderer/core/dom/processing_instruction.h" |
| #include "third_party/blink/renderer/core/dom/range.h" |
| #include "third_party/blink/renderer/core/dom/shadow_root.h" |
| #include "third_party/blink/renderer/core/dom/slot_assignment.h" |
| #include "third_party/blink/renderer/core/dom/slot_assignment_engine.h" |
| #include "third_party/blink/renderer/core/dom/static_node_list.h" |
| #include "third_party/blink/renderer/core/dom/template_content_document_fragment.h" |
| #include "third_party/blink/renderer/core/dom/text.h" |
| #include "third_party/blink/renderer/core/dom/tree_scope_adopter.h" |
| #include "third_party/blink/renderer/core/dom/user_action_element_set.h" |
| #include "third_party/blink/renderer/core/dom/v0_insertion_point.h" |
| #include "third_party/blink/renderer/core/editing/editing_utilities.h" |
| #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h" |
| #include "third_party/blink/renderer/core/events/gesture_event.h" |
| #include "third_party/blink/renderer/core/events/input_event.h" |
| #include "third_party/blink/renderer/core/events/keyboard_event.h" |
| #include "third_party/blink/renderer/core/events/mouse_event.h" |
| #include "third_party/blink/renderer/core/events/mutation_event.h" |
| #include "third_party/blink/renderer/core/events/pointer_event.h" |
| #include "third_party/blink/renderer/core/events/pointer_event_factory.h" |
| #include "third_party/blink/renderer/core/events/text_event.h" |
| #include "third_party/blink/renderer/core/events/touch_event.h" |
| #include "third_party/blink/renderer/core/events/ui_event.h" |
| #include "third_party/blink/renderer/core/events/wheel_event.h" |
| #include "third_party/blink/renderer/core/exported/web_plugin_container_impl.h" |
| #include "third_party/blink/renderer/core/frame/event_handler_registry.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/use_counter.h" |
| #include "third_party/blink/renderer/core/fullscreen/fullscreen.h" |
| #include "third_party/blink/renderer/core/html/custom/custom_element.h" |
| #include "third_party/blink/renderer/core/html/html_dialog_element.h" |
| #include "third_party/blink/renderer/core/html/html_frame_owner_element.h" |
| #include "third_party/blink/renderer/core/html/html_slot_element.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/input/event_handler.h" |
| #include "third_party/blink/renderer/core/input/input_device_capabilities.h" |
| #include "third_party/blink/renderer/core/layout/layout_box.h" |
| #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/mathml_names.h" |
| #include "third_party/blink/renderer/core/page/context_menu_controller.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/page/scrolling/root_scroller_util.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scroll_customization_callbacks.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scroll_state.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scroll_state_callback.h" |
| #include "third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/core/svg/graphics/svg_image.h" |
| #include "third_party/blink/renderer/core/svg/svg_element.h" |
| #include "third_party/blink/renderer/core/trustedtypes/trusted_script.h" |
| #include "third_party/blink/renderer/platform/bindings/dom_data_store.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/bindings/microtask.h" |
| #include "third_party/blink/renderer/platform/bindings/script_wrappable_visitor.h" |
| #include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h" |
| #include "third_party/blink/renderer/platform/instance_counters.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" |
| #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/wtf/allocator/partitions.h" |
| #include "third_party/blink/renderer/platform/wtf/hash_set.h" |
| #include "third_party/blink/renderer/platform/wtf/text/cstring.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| #include "third_party/blink/renderer/platform/wtf/vector.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // We need to retain the scroll customization callbacks until the element |
| // they're associated with is destroyed. It would be simplest if the callbacks |
| // could be stored in ElementRareData, but we can't afford the space increase. |
| // Instead, keep the scroll customization callbacks here. The other option would |
| // be to store these callbacks on the Page or document, but that necessitates a |
| // bunch more logic for transferring the callbacks between Pages when elements |
| // are moved around. |
| ScrollCustomizationCallbacks& GetScrollCustomizationCallbacks() { |
| DEFINE_STATIC_LOCAL(Persistent<ScrollCustomizationCallbacks>, |
| scroll_customization_callbacks, |
| (MakeGarbageCollected<ScrollCustomizationCallbacks>())); |
| return *scroll_customization_callbacks; |
| } |
| |
| // TODO(crbug.com/545926): Unsafe hack to avoid triggering the |
| // ThreadRestrictionVerifier on StringImpl. This should be fixed completely, and |
| // we should always avoid accessing these strings from the impl thread. |
| // Currently code that calls into this method from the impl thread tries to make |
| // sure that the main thread is not running at this time. |
| void AppendUnsafe(StringBuilder& builder, const String& off_thread_string) { |
| StringImpl* impl = off_thread_string.Impl(); |
| if (impl) { |
| builder.Append(impl->Is8Bit() |
| ? StringView(impl->Characters8(), impl->length()) |
| : StringView(impl->Characters16(), impl->length())); |
| } |
| } |
| |
| } // namespace |
| |
| using namespace html_names; |
| |
| struct SameSizeAsNode : EventTarget { |
| uint32_t node_flags_; |
| Member<void*> willbe_member_[4]; |
| void* pointer_; |
| }; |
| |
| NodeRenderingData::NodeRenderingData( |
| LayoutObject* layout_object, |
| scoped_refptr<ComputedStyle> computed_style) |
| : layout_object_(layout_object), computed_style_(computed_style) {} |
| |
| NodeRenderingData::~NodeRenderingData() { |
| CHECK(!layout_object_); |
| } |
| |
| void NodeRenderingData::SetComputedStyle( |
| scoped_refptr<ComputedStyle> computed_style) { |
| DCHECK_NE(&SharedEmptyData(), this); |
| computed_style_ = computed_style; |
| } |
| |
| NodeRenderingData& NodeRenderingData::SharedEmptyData() { |
| DEFINE_STATIC_LOCAL(NodeRenderingData, shared_empty_data, (nullptr, nullptr)); |
| return shared_empty_data; |
| } |
| |
| static_assert(sizeof(Node) <= sizeof(SameSizeAsNode), "Node should stay small"); |
| |
| #if DUMP_NODE_STATISTICS |
| using WeakNodeSet = HeapHashSet<WeakMember<Node>>; |
| static WeakNodeSet& liveNodeSet() { |
| DEFINE_STATIC_LOCAL(WeakNodeSet, set, (new WeakNodeSet)); |
| return set; |
| } |
| #endif |
| |
| void Node::DumpStatistics() { |
| #if DUMP_NODE_STATISTICS |
| size_t nodesWithRareData = 0; |
| |
| size_t elementNodes = 0; |
| size_t attrNodes = 0; |
| size_t textNodes = 0; |
| size_t cdataNodes = 0; |
| size_t commentNodes = 0; |
| size_t piNodes = 0; |
| size_t documentNodes = 0; |
| size_t docTypeNodes = 0; |
| size_t fragmentNodes = 0; |
| size_t shadowRootNodes = 0; |
| |
| HashMap<String, size_t> perTagCount; |
| |
| size_t attributes = 0; |
| size_t elementsWithAttributeStorage = 0; |
| size_t elementsWithRareData = 0; |
| size_t elementsWithNamedNodeMap = 0; |
| |
| { |
| ScriptForbiddenScope forbidScriptDuringRawIteration; |
| for (Node* node : liveNodeSet()) { |
| if (node->hasRareData()) { |
| ++nodesWithRareData; |
| if (node->isElementNode()) { |
| ++elementsWithRareData; |
| if (toElement(node)->hasNamedNodeMap()) |
| ++elementsWithNamedNodeMap; |
| } |
| } |
| |
| switch (node->getNodeType()) { |
| case kElementNode: { |
| ++elementNodes; |
| |
| // Tag stats |
| Element* element = toElement(node); |
| HashMap<String, size_t>::AddResult result = |
| perTagCount.add(element->tagName(), 1); |
| if (!result.isNewEntry) |
| result.storedValue->value++; |
| |
| size_t attributeCount = element->attributesWithoutUpdate().size(); |
| if (attributeCount) { |
| attributes += attributeCount; |
| ++elementsWithAttributeStorage; |
| } |
| break; |
| } |
| case kAttributeNode: { |
| ++attrNodes; |
| break; |
| } |
| case kTextNode: { |
| ++textNodes; |
| break; |
| } |
| case kCdataSectionNode: { |
| ++cdataNodes; |
| break; |
| } |
| case kCommentNode: { |
| ++commentNodes; |
| break; |
| } |
| case kProcessingInstructionNode: { |
| ++piNodes; |
| break; |
| } |
| case kDocumentNode: { |
| ++documentNodes; |
| break; |
| } |
| case kDocumentTypeNode: { |
| ++docTypeNodes; |
| break; |
| } |
| case kDocumentFragmentNode: { |
| if (node->isShadowRoot()) |
| ++shadowRootNodes; |
| else |
| ++fragmentNodes; |
| break; |
| } |
| } |
| } |
| } |
| |
| std::stringstream perTagStream; |
| for (const auto& entry : perTagCount) |
| perTagStream << " Number of <" << entry.key.utf8().data() |
| << "> tags: " << entry.value << "\n"; |
| |
| LOG(INFO) << "\n" |
| << "Number of Nodes: " << liveNodeSet().size() << "\n" |
| << "Number of Nodes with RareData: " << nodesWithRareData << "\n\n" |
| |
| << "NodeType distribution:\n" |
| << " Number of Element nodes: " << elementNodes << "\n" |
| << " Number of Attribute nodes: " << attrNodes << "\n" |
| << " Number of Text nodes: " << textNodes << "\n" |
| << " Number of CDATASection nodes: " << cdataNodes << "\n" |
| << " Number of Comment nodes: " << commentNodes << "\n" |
| << " Number of ProcessingInstruction nodes: " << piNodes << "\n" |
| << " Number of Document nodes: " << documentNodes << "\n" |
| << " Number of DocumentType nodes: " << docTypeNodes << "\n" |
| << " Number of DocumentFragment nodes: " << fragmentNodes << "\n" |
| << " Number of ShadowRoot nodes: " << shadowRootNodes << "\n" |
| |
| << "Element tag name distibution:\n" |
| << perTagStream.str() |
| |
| << "Attributes:\n" |
| << " Number of Attributes (non-Node and Node): " << attributes |
| << " x " << sizeof(Attribute) << "Bytes\n" |
| << " Number of Elements with attribute storage: " |
| << elementsWithAttributeStorage << " x " << sizeof(ElementData) |
| << "Bytes\n" |
| << " Number of Elements with RareData: " << elementsWithRareData |
| << "\n" |
| << " Number of Elements with NamedNodeMap: " |
| << elementsWithNamedNodeMap << " x " << sizeof(NamedNodeMap) |
| << "Bytes"; |
| #endif |
| } |
| |
| void Node::TrackForDebugging() { |
| #if DUMP_NODE_STATISTICS |
| liveNodeSet().add(this); |
| #endif |
| } |
| |
| Node::Node(TreeScope* tree_scope, ConstructionType type) |
| : node_flags_(type), |
| parent_or_shadow_host_node_(nullptr), |
| tree_scope_(tree_scope), |
| previous_(nullptr), |
| next_(nullptr) { |
| DCHECK(tree_scope_ || type == kCreateDocument || type == kCreateShadowRoot); |
| #if !defined(NDEBUG) || (defined(DUMP_NODE_STATISTICS) && DUMP_NODE_STATISTICS) |
| TrackForDebugging(); |
| #endif |
| InstanceCounters::IncrementCounter(InstanceCounters::kNodeCounter); |
| } |
| |
| Node::~Node() { |
| if (!HasRareData() && !data_.node_layout_data_->IsSharedEmptyData()) |
| delete data_.node_layout_data_; |
| InstanceCounters::DecrementCounter(InstanceCounters::kNodeCounter); |
| } |
| |
| NodeRareData& Node::CreateRareData() { |
| if (IsElementNode()) |
| data_.rare_data_ = ElementRareData::Create(data_.node_layout_data_); |
| else |
| data_.rare_data_ = NodeRareData::Create(data_.node_layout_data_); |
| |
| DCHECK(data_.rare_data_); |
| SetFlag(kHasRareDataFlag); |
| ScriptWrappableMarkingVisitor::WriteBarrier(RareData()); |
| MarkingVisitor::WriteBarrier(RareData()); |
| return *RareData(); |
| } |
| |
| Node* Node::ToNode() { |
| return this; |
| } |
| |
| int Node::tabIndex() const { |
| return 0; |
| } |
| |
| String Node::nodeValue() const { |
| return String(); |
| } |
| |
| void Node::setNodeValue(const String&) { |
| // By default, setting nodeValue has no effect. |
| } |
| |
| ContainerNode* Node::parentNode() const { |
| return IsShadowRoot() ? nullptr : ParentOrShadowHostNode(); |
| } |
| |
| ContainerNode* Node::ParentNodeWithCounting() const { |
| if (GetFlag(kInDOMNodeRemovedHandler)) |
| GetDocument().CountDetachingNodeAccessInDOMNodeRemovedHandler(); |
| return IsShadowRoot() ? nullptr : ParentOrShadowHostNode(); |
| } |
| |
| NodeList* Node::childNodes() { |
| ThreadState::MainThreadGCForbiddenScope gc_forbidden; |
| if (IsContainerNode()) |
| return EnsureRareData().EnsureNodeLists().EnsureChildNodeList( |
| ToContainerNode(*this)); |
| return EnsureRareData().EnsureNodeLists().EnsureEmptyChildNodeList(*this); |
| } |
| |
| Node* Node::PseudoAwarePreviousSibling() const { |
| if (parentElement() && !previousSibling()) { |
| Element* parent = parentElement(); |
| if (IsAfterPseudoElement() && parent->lastChild()) |
| return parent->lastChild(); |
| if (!IsBeforePseudoElement()) |
| return parent->GetPseudoElement(kPseudoIdBefore); |
| } |
| return previousSibling(); |
| } |
| |
| Node* Node::PseudoAwareNextSibling() const { |
| if (parentElement() && !nextSibling()) { |
| Element* parent = parentElement(); |
| if (IsBeforePseudoElement() && parent->HasChildren()) |
| return parent->firstChild(); |
| if (!IsAfterPseudoElement()) |
| return parent->GetPseudoElement(kPseudoIdAfter); |
| } |
| return nextSibling(); |
| } |
| |
| Node* Node::PseudoAwareFirstChild() const { |
| if (IsElementNode()) { |
| const Element* current_element = ToElement(this); |
| Node* first = current_element->GetPseudoElement(kPseudoIdBefore); |
| if (first) |
| return first; |
| first = current_element->firstChild(); |
| if (!first) |
| first = current_element->GetPseudoElement(kPseudoIdAfter); |
| return first; |
| } |
| |
| return firstChild(); |
| } |
| |
| Node* Node::PseudoAwareLastChild() const { |
| if (IsElementNode()) { |
| const Element* current_element = ToElement(this); |
| Node* last = current_element->GetPseudoElement(kPseudoIdAfter); |
| if (last) |
| return last; |
| last = current_element->lastChild(); |
| if (!last) |
| last = current_element->GetPseudoElement(kPseudoIdBefore); |
| return last; |
| } |
| |
| return lastChild(); |
| } |
| |
| Node& Node::TreeRoot() const { |
| if (IsInTreeScope()) |
| return ContainingTreeScope().RootNode(); |
| const Node* node = this; |
| while (node->parentNode()) |
| node = node->parentNode(); |
| return const_cast<Node&>(*node); |
| } |
| |
| Node* Node::getRootNode(const GetRootNodeOptions* options) const { |
| return (options->hasComposed() && options->composed()) |
| ? &ShadowIncludingRoot() |
| : &TreeRoot(); |
| } |
| |
| void Node::setDistributeScroll(V8ScrollStateCallback* scroll_state_callback, |
| const String& native_scroll_behavior) { |
| GetScrollCustomizationCallbacks().SetDistributeScroll( |
| this, ScrollStateCallbackV8Impl::Create(scroll_state_callback, |
| native_scroll_behavior)); |
| } |
| |
| void Node::setApplyScroll(V8ScrollStateCallback* scroll_state_callback, |
| const String& native_scroll_behavior) { |
| SetApplyScroll(ScrollStateCallbackV8Impl::Create(scroll_state_callback, |
| native_scroll_behavior)); |
| } |
| |
| void Node::SetApplyScroll(ScrollStateCallback* scroll_state_callback) { |
| GetScrollCustomizationCallbacks().SetApplyScroll(this, scroll_state_callback); |
| } |
| |
| void Node::RemoveApplyScroll() { |
| GetScrollCustomizationCallbacks().RemoveApplyScroll(this); |
| } |
| |
| ScrollStateCallback* Node::GetApplyScroll() { |
| return GetScrollCustomizationCallbacks().GetApplyScroll(this); |
| } |
| |
| void Node::NativeDistributeScroll(ScrollState& scroll_state) { |
| if (scroll_state.FullyConsumed()) |
| return; |
| |
| scroll_state.distributeToScrollChainDescendant(); |
| |
| // The scroll doesn't propagate, and we're currently scrolling an element |
| // other than this one, prevent the scroll from propagating to this element. |
| if (scroll_state.DeltaConsumedForScrollSequence() && |
| scroll_state.CurrentNativeScrollingNode() != this) { |
| return; |
| } |
| |
| const double delta_x = scroll_state.deltaX(); |
| const double delta_y = scroll_state.deltaY(); |
| |
| CallApplyScroll(scroll_state); |
| |
| if (delta_x != scroll_state.deltaX() || delta_y != scroll_state.deltaY()) |
| scroll_state.SetCurrentNativeScrollingNode(this); |
| } |
| |
| void Node::NativeApplyScroll(ScrollState& scroll_state) { |
| if (!GetLayoutObject()) |
| return; |
| |
| // All elements in the scroll chain should be boxes. |
| DCHECK(GetLayoutObject()->IsBox()); |
| |
| if (scroll_state.FullyConsumed()) |
| return; |
| |
| FloatSize delta(scroll_state.deltaX(), scroll_state.deltaY()); |
| |
| if (delta.IsZero()) |
| return; |
| |
| // TODO(esprehn): This should use |
| // updateStyleAndLayoutIgnorePendingStylesheetsForNode. |
| GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| LayoutBox* box_to_scroll = ToLayoutBox(GetLayoutObject()); |
| |
| ScrollableArea* scrollable_area = |
| box_to_scroll->EnclosingBox()->GetScrollableArea(); |
| |
| if (!scrollable_area) |
| return; |
| |
| ScrollResult result = scrollable_area->UserScroll( |
| ScrollGranularity(static_cast<int>(scroll_state.deltaGranularity())), |
| delta); |
| |
| if (!result.DidScroll()) |
| return; |
| |
| // FIXME: Native scrollers should only consume the scroll they |
| // apply. See crbug.com/457765. |
| scroll_state.ConsumeDeltaNative(delta.Width(), delta.Height()); |
| |
| // We need to setCurrentNativeScrollingElement in both the |
| // distributeScroll and applyScroll default implementations so |
| // that if JS overrides one of these methods, but not the |
| // other, this bookkeeping remains accurate. |
| scroll_state.SetCurrentNativeScrollingNode(this); |
| } |
| |
| void Node::CallDistributeScroll(ScrollState& scroll_state) { |
| TRACE_EVENT0("input", "Node::CallDistributeScroll"); |
| ScrollStateCallback* callback = |
| GetScrollCustomizationCallbacks().GetDistributeScroll(this); |
| |
| // TODO(bokan): Need to add tests before we allow calling custom callbacks |
| // for non-touch modalities. For now, just call into the native callback but |
| // allow the viewport scroll callback so we don't disable overscroll. |
| // crbug.com/623079. |
| bool disable_custom_callbacks = !scroll_state.isDirectManipulation() && |
| !GetDocument() |
| .GetPage() |
| ->GlobalRootScrollerController() |
| .IsViewportScrollCallback(callback); |
| |
| bool is_global_root_scroller = |
| GetLayoutObject() && GetLayoutObject()->IsGlobalRootScroller(); |
| |
| disable_custom_callbacks |= |
| !is_global_root_scroller && |
| RuntimeEnabledFeatures::ScrollCustomizationEnabled() && |
| !GetScrollCustomizationCallbacks().InScrollPhase(this); |
| |
| if (!callback || disable_custom_callbacks) { |
| NativeDistributeScroll(scroll_state); |
| return; |
| } |
| if (callback->NativeScrollBehavior() != |
| WebNativeScrollBehavior::kPerformAfterNativeScroll) |
| callback->Invoke(&scroll_state); |
| if (callback->NativeScrollBehavior() != |
| WebNativeScrollBehavior::kDisableNativeScroll) |
| NativeDistributeScroll(scroll_state); |
| if (callback->NativeScrollBehavior() == |
| WebNativeScrollBehavior::kPerformAfterNativeScroll) |
| callback->Invoke(&scroll_state); |
| } |
| |
| void Node::CallApplyScroll(ScrollState& scroll_state) { |
| TRACE_EVENT0("input", "Node::CallApplyScroll"); |
| // Hits ASSERTs when trying to determine whether we need to scroll on main |
| // or CC. http://crbug.com/625676. |
| DisableCompositingQueryAsserts disabler; |
| |
| if (!GetDocument().GetPage()) { |
| // We should always have a Page if we're scrolling. See |
| // crbug.com/689074 for details. |
| return; |
| } |
| |
| ScrollStateCallback* callback = |
| GetScrollCustomizationCallbacks().GetApplyScroll(this); |
| |
| // TODO(bokan): Need to add tests before we allow calling custom callbacks |
| // for non-touch modalities. For now, just call into the native callback but |
| // allow the viewport scroll callback so we don't disable overscroll. |
| // crbug.com/623079. |
| bool disable_custom_callbacks = !scroll_state.isDirectManipulation() && |
| !GetDocument() |
| .GetPage() |
| ->GlobalRootScrollerController() |
| .IsViewportScrollCallback(callback); |
| |
| bool is_global_root_scroller = |
| GetLayoutObject() && GetLayoutObject()->IsGlobalRootScroller(); |
| |
| disable_custom_callbacks |= |
| !is_global_root_scroller && |
| RuntimeEnabledFeatures::ScrollCustomizationEnabled() && |
| !GetScrollCustomizationCallbacks().InScrollPhase(this); |
| |
| if (!callback || disable_custom_callbacks) { |
| NativeApplyScroll(scroll_state); |
| return; |
| } |
| if (callback->NativeScrollBehavior() != |
| WebNativeScrollBehavior::kPerformAfterNativeScroll) |
| callback->Invoke(&scroll_state); |
| if (callback->NativeScrollBehavior() != |
| WebNativeScrollBehavior::kDisableNativeScroll) |
| NativeApplyScroll(scroll_state); |
| if (callback->NativeScrollBehavior() == |
| WebNativeScrollBehavior::kPerformAfterNativeScroll) |
| callback->Invoke(&scroll_state); |
| } |
| |
| void Node::WillBeginCustomizedScrollPhase( |
| scroll_customization::ScrollDirection direction) { |
| DCHECK(!GetScrollCustomizationCallbacks().InScrollPhase(this)); |
| LayoutBox* box = GetLayoutBox(); |
| if (!box) |
| return; |
| |
| scroll_customization::ScrollDirection scroll_customization = |
| box->Style()->ScrollCustomization(); |
| |
| GetScrollCustomizationCallbacks().SetInScrollPhase( |
| this, direction & scroll_customization); |
| } |
| |
| void Node::DidEndCustomizedScrollPhase() { |
| GetScrollCustomizationCallbacks().SetInScrollPhase(this, false); |
| } |
| |
| Node* Node::insertBefore(Node* new_child, |
| Node* ref_child, |
| ExceptionState& exception_state) { |
| if (IsContainerNode()) |
| return ToContainerNode(this)->InsertBefore(new_child, ref_child, |
| exception_state); |
| |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| "This node type does not support this method."); |
| return nullptr; |
| } |
| |
| Node* Node::insertBefore(Node* new_child, Node* ref_child) { |
| return insertBefore(new_child, ref_child, ASSERT_NO_EXCEPTION); |
| } |
| |
| Node* Node::replaceChild(Node* new_child, |
| Node* old_child, |
| ExceptionState& exception_state) { |
| if (IsContainerNode()) |
| return ToContainerNode(this)->ReplaceChild(new_child, old_child, |
| exception_state); |
| |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| "This node type does not support this method."); |
| return nullptr; |
| } |
| |
| Node* Node::replaceChild(Node* new_child, Node* old_child) { |
| return replaceChild(new_child, old_child, ASSERT_NO_EXCEPTION); |
| } |
| |
| Node* Node::removeChild(Node* old_child, ExceptionState& exception_state) { |
| if (IsContainerNode()) |
| return ToContainerNode(this)->RemoveChild(old_child, exception_state); |
| |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kNotFoundError, |
| "This node type does not support this method."); |
| return nullptr; |
| } |
| |
| Node* Node::removeChild(Node* old_child) { |
| return removeChild(old_child, ASSERT_NO_EXCEPTION); |
| } |
| |
| Node* Node::appendChild(Node* new_child, ExceptionState& exception_state) { |
| if (IsContainerNode()) |
| return ToContainerNode(this)->AppendChild(new_child, exception_state); |
| |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kHierarchyRequestError, |
| "This node type does not support this method."); |
| return nullptr; |
| } |
| |
| Node* Node::appendChild(Node* new_child) { |
| return appendChild(new_child, ASSERT_NO_EXCEPTION); |
| } |
| |
| static bool IsNodeInNodes(const Node* const node, |
| const HeapVector<NodeOrString>& nodes) { |
| for (const NodeOrString& node_or_string : nodes) { |
| if (node_or_string.IsNode() && node_or_string.GetAsNode() == node) |
| return true; |
| } |
| return false; |
| } |
| |
| static Node* FindViablePreviousSibling(const Node& node, |
| const HeapVector<NodeOrString>& nodes) { |
| for (Node* sibling = node.previousSibling(); sibling; |
| sibling = sibling->previousSibling()) { |
| if (!IsNodeInNodes(sibling, nodes)) |
| return sibling; |
| } |
| return nullptr; |
| } |
| |
| static Node* FindViableNextSibling(const Node& node, |
| const HeapVector<NodeOrString>& nodes) { |
| for (Node* sibling = node.nextSibling(); sibling; |
| sibling = sibling->nextSibling()) { |
| if (!IsNodeInNodes(sibling, nodes)) |
| return sibling; |
| } |
| return nullptr; |
| } |
| |
| static Node* NodeOrStringToNode(const NodeOrString& node_or_string, |
| Document& document) { |
| if (node_or_string.IsNode()) |
| return node_or_string.GetAsNode(); |
| return Text::Create(document, node_or_string.GetAsString()); |
| } |
| |
| // Returns nullptr if an exception was thrown. |
| static Node* ConvertNodesIntoNode(const HeapVector<NodeOrString>& nodes, |
| Document& document, |
| ExceptionState& exception_state) { |
| if (nodes.size() == 1) |
| return NodeOrStringToNode(nodes[0], document); |
| |
| Node* fragment = DocumentFragment::Create(document); |
| for (const NodeOrString& node_or_string : nodes) { |
| fragment->appendChild(NodeOrStringToNode(node_or_string, document), |
| exception_state); |
| if (exception_state.HadException()) |
| return nullptr; |
| } |
| return fragment; |
| } |
| |
| void Node::Prepend(const HeapVector<NodeOrString>& nodes, |
| ExceptionState& exception_state) { |
| if (Node* node = ConvertNodesIntoNode(nodes, GetDocument(), exception_state)) |
| insertBefore(node, firstChild(), exception_state); |
| } |
| |
| void Node::Append(const HeapVector<NodeOrString>& nodes, |
| ExceptionState& exception_state) { |
| if (Node* node = ConvertNodesIntoNode(nodes, GetDocument(), exception_state)) |
| appendChild(node, exception_state); |
| } |
| |
| void Node::Before(const HeapVector<NodeOrString>& nodes, |
| ExceptionState& exception_state) { |
| Node* parent = parentNode(); |
| if (!parent) |
| return; |
| Node* viable_previous_sibling = FindViablePreviousSibling(*this, nodes); |
| if (Node* node = ConvertNodesIntoNode(nodes, GetDocument(), exception_state)) |
| parent->insertBefore(node, |
| viable_previous_sibling |
| ? viable_previous_sibling->nextSibling() |
| : parent->firstChild(), |
| exception_state); |
| } |
| |
| void Node::After(const HeapVector<NodeOrString>& nodes, |
| ExceptionState& exception_state) { |
| Node* parent = parentNode(); |
| if (!parent) |
| return; |
| Node* viable_next_sibling = FindViableNextSibling(*this, nodes); |
| if (Node* node = ConvertNodesIntoNode(nodes, GetDocument(), exception_state)) |
| parent->insertBefore(node, viable_next_sibling, exception_state); |
| } |
| |
| void Node::ReplaceWith(const HeapVector<NodeOrString>& nodes, |
| ExceptionState& exception_state) { |
| Node* parent = parentNode(); |
| if (!parent) |
| return; |
| Node* viable_next_sibling = FindViableNextSibling(*this, nodes); |
| Node* node = ConvertNodesIntoNode(nodes, GetDocument(), exception_state); |
| if (exception_state.HadException()) |
| return; |
| if (parent == parentNode()) |
| parent->replaceChild(node, this, exception_state); |
| else |
| parent->insertBefore(node, viable_next_sibling, exception_state); |
| } |
| |
| void Node::remove(ExceptionState& exception_state) { |
| if (ContainerNode* parent = parentNode()) |
| parent->RemoveChild(this, exception_state); |
| } |
| |
| void Node::remove() { |
| remove(ASSERT_NO_EXCEPTION); |
| } |
| |
| Node* Node::cloneNode(bool deep, ExceptionState& exception_state) const { |
| // https://dom.spec.whatwg.org/#dom-node-clonenode |
| |
| // 1. If context object is a shadow root, then throw a |
| // "NotSupportedError" DOMException. |
| if (IsShadowRoot()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError, |
| "ShadowRoot nodes are not clonable."); |
| return nullptr; |
| } |
| |
| // 2. Return a clone of the context object, with the clone children |
| // flag set if deep is true. |
| return Clone(GetDocument(), |
| deep ? CloneChildrenFlag::kClone : CloneChildrenFlag::kSkip); |
| } |
| |
| Node* Node::cloneNode(bool deep) const { |
| return cloneNode(deep, ASSERT_NO_EXCEPTION); |
| } |
| |
| void Node::normalize() { |
| UpdateDistributionForFlatTreeTraversal(); |
| |
| // Go through the subtree beneath us, normalizing all nodes. This means that |
| // any two adjacent text nodes are merged and any empty text nodes are |
| // removed. |
| |
| Node* node = this; |
| while (Node* first_child = node->firstChild()) |
| node = first_child; |
| while (node) { |
| if (node == this) |
| break; |
| |
| if (node->getNodeType() == kTextNode) |
| node = ToText(node)->MergeNextSiblingNodesIfPossible(); |
| else |
| node = NodeTraversal::NextPostOrder(*node); |
| } |
| } |
| |
| LayoutBox* Node::GetLayoutBox() const { |
| LayoutObject* layout_object = GetLayoutObject(); |
| return layout_object && layout_object->IsBox() ? ToLayoutBox(layout_object) |
| : nullptr; |
| } |
| |
| void Node::SetLayoutObject(LayoutObject* layout_object) { |
| NodeRenderingData* node_layout_data = |
| HasRareData() ? data_.rare_data_->GetNodeRenderingData() |
| : data_.node_layout_data_; |
| |
| // Already pointing to a non empty NodeRenderingData so just set the pointer |
| // to the new LayoutObject. |
| if (!node_layout_data->IsSharedEmptyData()) { |
| node_layout_data->SetLayoutObject(layout_object); |
| return; |
| } |
| |
| if (!layout_object) |
| return; |
| |
| // Swap the NodeRenderingData to point to a new NodeRenderingData instead of |
| // the static SharedEmptyData instance. |
| DCHECK(!node_layout_data->GetComputedStyle()); |
| node_layout_data = new NodeRenderingData(layout_object, nullptr); |
| if (HasRareData()) |
| data_.rare_data_->SetNodeRenderingData(node_layout_data); |
| else |
| data_.node_layout_data_ = node_layout_data; |
| } |
| |
| void Node::SetComputedStyle(scoped_refptr<ComputedStyle> computed_style) { |
| // We don't set computed style for text nodes. |
| DCHECK(IsElementNode()); |
| |
| NodeRenderingData* node_layout_data = |
| HasRareData() ? data_.rare_data_->GetNodeRenderingData() |
| : data_.node_layout_data_; |
| |
| // Already pointing to a non empty NodeRenderingData so just set the pointer |
| // to the new LayoutObject. |
| if (!node_layout_data->IsSharedEmptyData()) { |
| node_layout_data->SetComputedStyle(computed_style); |
| return; |
| } |
| |
| if (!computed_style) |
| return; |
| |
| // Ensure we only set computed style for elements which are not part of the |
| // flat tree unless it's enforced for getComputedStyle(). |
| DCHECK(computed_style->IsEnsuredInDisplayNone() || |
| LayoutTreeBuilderTraversal::Parent(*this)); |
| |
| // Swap the NodeRenderingData to point to a new NodeRenderingData instead of |
| // the static SharedEmptyData instance. |
| DCHECK(!node_layout_data->GetLayoutObject()); |
| node_layout_data = new NodeRenderingData(nullptr, computed_style); |
| if (HasRareData()) |
| data_.rare_data_->SetNodeRenderingData(node_layout_data); |
| else |
| data_.node_layout_data_ = node_layout_data; |
| } |
| |
| LayoutBoxModelObject* Node::GetLayoutBoxModelObject() const { |
| LayoutObject* layout_object = GetLayoutObject(); |
| return layout_object && layout_object->IsBoxModelObject() |
| ? ToLayoutBoxModelObject(layout_object) |
| : nullptr; |
| } |
| |
| LayoutRect Node::BoundingBox() const { |
| if (GetLayoutObject()) |
| return LayoutRect(GetLayoutObject()->AbsoluteBoundingBoxRect()); |
| return LayoutRect(); |
| } |
| |
| LayoutRect Node::BoundingBoxForScrollIntoView() const { |
| if (GetLayoutObject()) { |
| return LayoutRect( |
| GetLayoutObject()->AbsoluteBoundingBoxRectForScrollIntoView()); |
| } |
| |
| return LayoutRect(); |
| } |
| |
| Node& Node::ShadowIncludingRoot() const { |
| if (isConnected()) |
| return GetDocument(); |
| Node* root = const_cast<Node*>(this); |
| while (Node* host = root->OwnerShadowHost()) |
| root = host; |
| while (Node* ancestor = root->parentNode()) |
| root = ancestor; |
| DCHECK(!root->OwnerShadowHost()); |
| return *root; |
| } |
| |
| bool Node::IsClosedShadowHiddenFrom(const Node& other) const { |
| if (!IsInShadowTree() || GetTreeScope() == other.GetTreeScope()) |
| return false; |
| |
| const TreeScope* scope = &GetTreeScope(); |
| for (; scope->ParentTreeScope(); scope = scope->ParentTreeScope()) { |
| const ContainerNode& root = scope->RootNode(); |
| auto* shadow_root = DynamicTo<ShadowRoot>(root); |
| if (shadow_root && !shadow_root->IsOpenOrV0()) |
| break; |
| } |
| |
| for (TreeScope* other_scope = &other.GetTreeScope(); other_scope; |
| other_scope = other_scope->ParentTreeScope()) { |
| if (other_scope == scope) |
| return false; |
| } |
| return true; |
| } |
| |
| bool Node::NeedsDistributionRecalc() const { |
| return ShadowIncludingRoot().ChildNeedsDistributionRecalc(); |
| } |
| |
| bool Node::MayContainLegacyNodeTreeWhereDistributionShouldBeSupported() const { |
| if (isConnected() && !GetDocument().MayContainV0Shadow()) { |
| // TODO(crbug.com/787717): Some built-in elements still use <content> |
| // elements in their user-agent shadow roots. DCHECK() fails if such an |
| // element is used. |
| DCHECK(!GetDocument().ChildNeedsDistributionRecalc()); |
| return false; |
| } |
| return true; |
| } |
| |
| void Node::UpdateDistributionForUnknownReasons() { |
| UpdateDistributionInternal(); |
| // For the sake of safety, call RecalcSlotAssignments as well as |
| // UpdateDistribution(). |
| if (isConnected()) |
| GetDocument().GetSlotAssignmentEngine().RecalcSlotAssignments(); |
| } |
| |
| void Node::UpdateDistributionInternal() { |
| if (!MayContainLegacyNodeTreeWhereDistributionShouldBeSupported()) |
| return; |
| // Extra early out to avoid spamming traces. |
| if (isConnected() && !GetDocument().ChildNeedsDistributionRecalc()) |
| return; |
| TRACE_EVENT0("blink", "Node::updateDistribution"); |
| ScriptForbiddenScope forbid_script; |
| Node& root = ShadowIncludingRoot(); |
| if (root.ChildNeedsDistributionRecalc()) |
| root.RecalcDistribution(); |
| } |
| |
| void Node::RecalcDistribution() { |
| DCHECK(ChildNeedsDistributionRecalc()); |
| |
| if (GetShadowRoot()) |
| GetShadowRoot()->DistributeIfNeeded(); |
| |
| DCHECK(ScriptForbiddenScope::IsScriptForbidden()); |
| for (Node* child = firstChild(); child; child = child->nextSibling()) { |
| if (child->ChildNeedsDistributionRecalc()) |
| child->RecalcDistribution(); |
| } |
| |
| if (ShadowRoot* root = GetShadowRoot()) { |
| if (root->ChildNeedsDistributionRecalc()) |
| root->RecalcDistribution(); |
| } |
| |
| ClearChildNeedsDistributionRecalc(); |
| } |
| |
| void Node::SetIsLink(bool is_link) { |
| SetFlag(is_link && !SVGImage::IsInSVGImage(ToElement(this)), kIsLinkFlag); |
| } |
| |
| void Node::SetNeedsStyleInvalidation() { |
| DCHECK(IsContainerNode()); |
| SetFlag(kNeedsStyleInvalidationFlag); |
| MarkAncestorsWithChildNeedsStyleInvalidation(); |
| } |
| |
| void Node::MarkAncestorsWithChildNeedsStyleInvalidation() { |
| ScriptForbiddenScope forbid_script_during_raw_iteration; |
| ContainerNode* ancestor = ParentOrShadowHostNode(); |
| bool parent_dirty = ancestor && ancestor->NeedsStyleInvalidation(); |
| for (; ancestor && !ancestor->ChildNeedsStyleInvalidation(); |
| ancestor = ancestor->ParentOrShadowHostNode()) { |
| if (!ancestor->isConnected()) |
| return; |
| ancestor->SetChildNeedsStyleInvalidation(); |
| if (ancestor->NeedsStyleInvalidation()) |
| break; |
| } |
| if (!isConnected()) |
| return; |
| // If the parent node is already dirty, we can keep the same invalidation |
| // root. The early return here is a performance optimization. |
| if (parent_dirty) |
| return; |
| GetDocument().GetStyleEngine().UpdateStyleInvalidationRoot(ancestor, this); |
| GetDocument().ScheduleLayoutTreeUpdateIfNeeded(); |
| } |
| |
| void Node::MarkAncestorsWithChildNeedsDistributionRecalc() { |
| ScriptForbiddenScope forbid_script_during_raw_iteration; |
| for (Node* node = this; node && !node->ChildNeedsDistributionRecalc(); |
| node = node->ParentOrShadowHostNode()) { |
| node->SetChildNeedsDistributionRecalc(); |
| } |
| GetDocument().ScheduleLayoutTreeUpdateIfNeeded(); |
| } |
| |
| void Node::MarkAncestorsWithChildNeedsStyleRecalc() { |
| ContainerNode* ancestor = ParentOrShadowHostNode(); |
| bool parent_dirty = ancestor && ancestor->NeedsStyleRecalc(); |
| for (; ancestor && !ancestor->ChildNeedsStyleRecalc(); |
| ancestor = ancestor->ParentOrShadowHostNode()) { |
| if (!ancestor->isConnected()) |
| return; |
| ancestor->SetChildNeedsStyleRecalc(); |
| if (ancestor->NeedsStyleRecalc()) |
| break; |
| // If we reach a locked ancestor, we should abort since the ancestor marking |
| // will be done when the lock is committed. |
| if (RuntimeEnabledFeatures::DisplayLockingEnabled()) { |
| if (ancestor->IsElementNode() && |
| ToElement(ancestor)->StyleRecalcBlockedByDisplayLock()) { |
| break; |
| } |
| } |
| } |
| if (!isConnected()) |
| return; |
| // If the parent node is already dirty, we can keep the same recalc root. The |
| // early return here is a performance optimization. |
| if (parent_dirty) |
| return; |
| |
| // If we're in a locked subtree, then we should not update the style recalc |
| // roots. These would be updated when we commit the lock. If we have locked |
| // display locks somewhere in the document, we iterate up the ancestor chain |
| // to check if we're in one such subtree. |
| if (RuntimeEnabledFeatures::DisplayLockingEnabled() && |
| GetDocument().LockedDisplayLockCount() > 0) { |
| for (auto* ancestor_copy = ancestor; ancestor_copy; |
| ancestor_copy = ancestor_copy->ParentOrShadowHostNode()) { |
| if (ancestor_copy->IsElementNode() && |
| ToElement(ancestor_copy)->StyleRecalcBlockedByDisplayLock()) { |
| return; |
| } |
| } |
| } |
| |
| GetDocument().GetStyleEngine().UpdateStyleRecalcRoot(ancestor, this); |
| GetDocument().ScheduleLayoutTreeUpdateIfNeeded(); |
| } |
| |
| ContainerNode* Node::GetReattachParent() const { |
| if (IsPseudoElement()) |
| return ParentOrShadowHostNode(); |
| if (IsChildOfV1ShadowHost()) { |
| if (HTMLSlotElement* slot = AssignedSlot()) |
| return slot; |
| } |
| if (IsInV0ShadowTree() || IsChildOfV0ShadowHost()) { |
| if (ShadowRootWhereNodeCanBeDistributedForV0(*this)) { |
| if (V0InsertionPoint* insertion_point = |
| const_cast<V0InsertionPoint*>(ResolveReprojection(this))) { |
| return insertion_point; |
| } |
| } |
| } |
| return ParentOrShadowHostNode(); |
| } |
| |
| void Node::MarkAncestorsWithChildNeedsReattachLayoutTree() { |
| DCHECK(isConnected()); |
| ContainerNode* ancestor = GetReattachParent(); |
| bool parent_dirty = ancestor && ancestor->NeedsReattachLayoutTree(); |
| for (; ancestor && !ancestor->ChildNeedsReattachLayoutTree(); |
| ancestor = ancestor->GetReattachParent()) { |
| ancestor->SetChildNeedsReattachLayoutTree(); |
| if (ancestor->NeedsReattachLayoutTree()) |
| break; |
| } |
| // If the parent node is already dirty, we can keep the same rebuild root. The |
| // early return here is a performance optimization. |
| if (parent_dirty) |
| return; |
| GetDocument().GetStyleEngine().UpdateLayoutTreeRebuildRoot(ancestor, this); |
| } |
| |
| void Node::SetNeedsReattachLayoutTree() { |
| DCHECK(GetDocument().InStyleRecalc()); |
| DCHECK(!GetDocument().ChildNeedsDistributionRecalc()); |
| DCHECK(IsElementNode() || IsTextNode()); |
| SetFlag(kNeedsReattachLayoutTree); |
| MarkAncestorsWithChildNeedsReattachLayoutTree(); |
| } |
| |
| void Node::SetNeedsStyleRecalc(StyleChangeType change_type, |
| const StyleChangeReasonForTracing& reason) { |
| DCHECK(!GetDocument().GetStyleEngine().InRebuildLayoutTree()); |
| DCHECK(change_type != kNoStyleChange); |
| |
| if (!InActiveDocument()) |
| return; |
| if (!IsContainerNode() && !IsTextNode()) |
| return; |
| |
| TRACE_EVENT_INSTANT1( |
| TRACE_DISABLED_BY_DEFAULT("devtools.timeline.invalidationTracking"), |
| "StyleRecalcInvalidationTracking", TRACE_EVENT_SCOPE_THREAD, "data", |
| inspector_style_recalc_invalidation_tracking_event::Data(this, reason)); |
| |
| StyleChangeType existing_change_type = GetStyleChangeType(); |
| if (change_type > existing_change_type) |
| SetStyleChange(change_type); |
| |
| if (existing_change_type == kNoStyleChange) |
| MarkAncestorsWithChildNeedsStyleRecalc(); |
| |
| if (IsElementNode() && HasRareData()) |
| ToElement(*this).SetAnimationStyleChange(false); |
| |
| if (IsSVGElement()) |
| ToSVGElement(this)->SetNeedsStyleRecalcForInstances(change_type, reason); |
| } |
| |
| void Node::ClearNeedsStyleRecalc() { |
| node_flags_ &= ~kStyleChangeMask; |
| ClearFlag(kForceReattachLayoutTree); |
| |
| if (IsElementNode() && HasRareData()) |
| ToElement(*this).SetAnimationStyleChange(false); |
| } |
| |
| bool Node::InActiveDocument() const { |
| return isConnected() && GetDocument().IsActive(); |
| } |
| |
| const Node* Node::FocusDelegate() const { |
| return this; |
| } |
| |
| bool Node::ShouldHaveFocusAppearance() const { |
| DCHECK(IsFocused()); |
| return true; |
| } |
| |
| bool Node::IsInert() const { |
| if (!isConnected() || !CanParticipateInFlatTree()) |
| return true; |
| |
| DCHECK(!ChildNeedsDistributionRecalc()); |
| |
| if (this != GetDocument()) { |
| const Element* modal_element = GetDocument().ActiveModalDialog(); |
| if (!modal_element) |
| modal_element = Fullscreen::FullscreenElementFrom(GetDocument()); |
| if (modal_element && !FlatTreeTraversal::ContainsIncludingPseudoElement( |
| *modal_element, *this)) { |
| return true; |
| } |
| } |
| |
| if (RuntimeEnabledFeatures::InertAttributeEnabled()) { |
| const Element* element = IsElementNode() |
| ? ToElement(this) |
| : FlatTreeTraversal::ParentElement(*this); |
| while (element) { |
| if (element->hasAttribute(html_names::kInertAttr)) |
| return true; |
| element = FlatTreeTraversal::ParentElement(*element); |
| } |
| } |
| return GetDocument().GetFrame() && GetDocument().GetFrame()->IsInert(); |
| } |
| |
| unsigned Node::NodeIndex() const { |
| const Node* temp_node = previousSibling(); |
| unsigned count = 0; |
| for (count = 0; temp_node; count++) |
| temp_node = temp_node->previousSibling(); |
| return count; |
| } |
| |
| NodeListsNodeData* Node::NodeLists() { |
| return HasRareData() ? RareData()->NodeLists() : nullptr; |
| } |
| |
| void Node::ClearNodeLists() { |
| RareData()->ClearNodeLists(); |
| } |
| |
| FlatTreeNodeData& Node::EnsureFlatTreeNodeData() { |
| return EnsureRareData().EnsureFlatTreeNodeData(); |
| } |
| |
| FlatTreeNodeData* Node::GetFlatTreeNodeData() const { |
| if (!HasRareData()) |
| return nullptr; |
| return RareData()->GetFlatTreeNodeData(); |
| } |
| |
| void Node::ClearFlatTreeNodeData() { |
| if (FlatTreeNodeData* data = GetFlatTreeNodeData()) |
| data->Clear(); |
| } |
| |
| bool Node::IsDescendantOf(const Node* other) const { |
| // Return true if other is an ancestor of this, otherwise false |
| if (!other || !other->hasChildren() || isConnected() != other->isConnected()) |
| return false; |
| if (other->GetTreeScope() != GetTreeScope()) |
| return false; |
| if (other->IsTreeScope()) |
| return !IsTreeScope(); |
| for (const ContainerNode* n = parentNode(); n; n = n->parentNode()) { |
| if (n == other) |
| return true; |
| } |
| return false; |
| } |
| |
| bool Node::contains(const Node* node) const { |
| if (!node) |
| return false; |
| return this == node || node->IsDescendantOf(this); |
| } |
| |
| bool Node::IsShadowIncludingInclusiveAncestorOf(const Node* node) const { |
| if (!node) |
| return false; |
| |
| if (this == node) |
| return true; |
| |
| if (GetDocument() != node->GetDocument()) |
| return false; |
| |
| if (isConnected() != node->isConnected()) |
| return false; |
| |
| bool has_children = IsContainerNode() && ToContainerNode(this)->HasChildren(); |
| bool has_shadow = IsShadowHost(this); |
| if (!has_children && !has_shadow) |
| return false; |
| |
| for (; node; node = node->OwnerShadowHost()) { |
| if (GetTreeScope() == node->GetTreeScope()) |
| return contains(node); |
| } |
| |
| return false; |
| } |
| |
| bool Node::ContainsIncludingHostElements(const Node& node) const { |
| const Node* current = &node; |
| do { |
| if (current == this) |
| return true; |
| if (current->IsDocumentFragment() && |
| ToDocumentFragment(current)->IsTemplateContent()) |
| current = |
| static_cast<const TemplateContentDocumentFragment*>(current)->Host(); |
| else |
| current = current->ParentOrShadowHostNode(); |
| } while (current); |
| return false; |
| } |
| |
| Node* Node::CommonAncestor(const Node& other, |
| ContainerNode* (*parent)(const Node&)) const { |
| if (this == other) |
| return const_cast<Node*>(this); |
| if (GetDocument() != other.GetDocument()) |
| return nullptr; |
| int this_depth = 0; |
| for (const Node* node = this; node; node = parent(*node)) { |
| if (node == &other) |
| return const_cast<Node*>(node); |
| this_depth++; |
| } |
| int other_depth = 0; |
| for (const Node* node = &other; node; node = parent(*node)) { |
| if (node == this) |
| return const_cast<Node*>(this); |
| other_depth++; |
| } |
| const Node* this_iterator = this; |
| const Node* other_iterator = &other; |
| if (this_depth > other_depth) { |
| for (int i = this_depth; i > other_depth; --i) |
| this_iterator = parent(*this_iterator); |
| } else if (other_depth > this_depth) { |
| for (int i = other_depth; i > this_depth; --i) |
| other_iterator = parent(*other_iterator); |
| } |
| while (this_iterator) { |
| if (this_iterator == other_iterator) |
| return const_cast<Node*>(this_iterator); |
| this_iterator = parent(*this_iterator); |
| other_iterator = parent(*other_iterator); |
| } |
| DCHECK(!other_iterator); |
| return nullptr; |
| } |
| |
| void Node::ReattachLayoutTree(AttachContext& context) { |
| context.performing_reattach = true; |
| |
| DetachLayoutTree(context); |
| AttachLayoutTree(context); |
| DCHECK(!NeedsReattachLayoutTree()); |
| } |
| |
| void Node::AttachLayoutTree(AttachContext& context) { |
| DCHECK(GetDocument().InStyleRecalc() || IsDocumentNode()); |
| DCHECK(!GetDocument().Lifecycle().InDetach()); |
| |
| LayoutObject* layout_object = GetLayoutObject(); |
| DCHECK(!layout_object || |
| (layout_object->Style() && |
| (layout_object->Parent() || layout_object->IsLayoutView()))); |
| |
| ClearNeedsReattachLayoutTree(); |
| |
| if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) |
| cache->UpdateCacheAfterNodeIsAttached(this); |
| } |
| |
| void Node::DetachLayoutTree(const AttachContext& context) { |
| DCHECK(GetDocument().Lifecycle().StateAllowsDetach()); |
| DocumentLifecycle::DetachScope will_detach(GetDocument().Lifecycle()); |
| if (GetLayoutObject()) |
| GetLayoutObject()->DestroyAndCleanupAnonymousWrappers(); |
| SetLayoutObject(nullptr); |
| } |
| |
| const ComputedStyle* Node::VirtualEnsureComputedStyle( |
| PseudoId pseudo_element_specifier) { |
| return ParentOrShadowHostNode() |
| ? ParentOrShadowHostNode()->EnsureComputedStyle( |
| pseudo_element_specifier) |
| : nullptr; |
| } |
| |
| void Node::LazyReattachIfAttached() { |
| if (!InActiveDocument()) |
| return; |
| if (!IsContainerNode() && !IsTextNode()) |
| return; |
| |
| AttachContext context; |
| context.performing_reattach = true; |
| DetachLayoutTree(context); |
| |
| if (GetDocument().GetStyleEngine().InRebuildLayoutTree()) |
| return; |
| |
| SetFlag(kForceReattachLayoutTree); |
| SetNeedsStyleRecalc( |
| kSubtreeStyleChange, |
| StyleChangeReasonForTracing::Create(style_change_reason::kLazyReattach)); |
| } |
| |
| void Node::SetForceReattachLayoutTree() { |
| DCHECK(!GetDocument().GetStyleEngine().InRebuildLayoutTree()); |
| if (GetForceReattachLayoutTree()) |
| return; |
| if (!InActiveDocument()) |
| return; |
| if (!IsContainerNode() && !IsTextNode()) |
| return; |
| SetFlag(kForceReattachLayoutTree); |
| if (!NeedsStyleRecalc()) { |
| // Make sure we traverse down to this node during style recalc. |
| MarkAncestorsWithChildNeedsStyleRecalc(); |
| } |
| } |
| |
| // FIXME: Shouldn't these functions be in the editing code? Code that asks |
| // questions about HTML in the core DOM class is obviously misplaced. |
| bool Node::CanStartSelection() const { |
| if (HasEditableStyle(*this)) |
| return true; |
| |
| if (GetLayoutObject()) { |
| const ComputedStyle& style = GetLayoutObject()->StyleRef(); |
| if (style.UserSelect() == EUserSelect::kNone) |
| return false; |
| // We allow selections to begin within |user-select: text/all| sub trees |
| // but not if the element is draggable. |
| if (style.UserDrag() != EUserDrag::kElement && |
| (style.UserSelect() == EUserSelect::kText || |
| style.UserSelect() == EUserSelect::kAll)) |
| return true; |
| } |
| ContainerNode* parent = FlatTreeTraversal::Parent(*this); |
| return parent ? parent->CanStartSelection() : true; |
| } |
| |
| // StyledElements allow inline style (style="border: 1px"), presentational |
| // attributes (ex. color), class names (ex. class="foo bar") and other non-basic |
| // styling features. They also control if this element can participate in style |
| // sharing. |
| // |
| // FIXME: The only things that ever go through StyleResolver that aren't |
| // StyledElements are PseudoElements and VTTElements. It's possible we can just |
| // eliminate all the checks since those elements will never have class names, |
| // inline style, or other things that this apparently guards against. |
| bool Node::IsStyledElement() const { |
| return IsHTMLElement() || IsSVGElement() || |
| (IsElementNode() && |
| ToElement(this)->namespaceURI() == mathml_names::kNamespaceURI); |
| } |
| |
| bool Node::CanParticipateInFlatTree() const { |
| // TODO(hayato): Return false for pseudo elements. |
| return !IsShadowRoot() && !IsActiveV0InsertionPoint(*this); |
| } |
| |
| bool Node::IsActiveSlotOrActiveV0InsertionPoint() const { |
| return ToHTMLSlotElementIfSupportsAssignmentOrNull(*this) || |
| IsActiveV0InsertionPoint(*this); |
| } |
| |
| AtomicString Node::SlotName() const { |
| DCHECK(IsSlotable()); |
| if (IsElementNode()) { |
| return HTMLSlotElement::NormalizeSlotName( |
| ToElement(*this).FastGetAttribute(html_names::kSlotAttr)); |
| } |
| DCHECK(IsTextNode()); |
| return g_empty_atom; |
| } |
| |
| bool Node::IsInV1ShadowTree() const { |
| ShadowRoot* shadow_root = ContainingShadowRoot(); |
| return shadow_root && shadow_root->IsV1(); |
| } |
| |
| bool Node::IsInV0ShadowTree() const { |
| ShadowRoot* shadow_root = ContainingShadowRoot(); |
| return shadow_root && !shadow_root->IsV1(); |
| } |
| |
| ShadowRoot* Node::ParentElementShadowRoot() const { |
| Element* parent = parentElement(); |
| return parent ? parent->GetShadowRoot() : nullptr; |
| } |
| |
| bool Node::IsChildOfV1ShadowHost() const { |
| ShadowRoot* parent_shadow_root = ParentElementShadowRoot(); |
| return parent_shadow_root && parent_shadow_root->IsV1(); |
| } |
| |
| bool Node::IsChildOfV0ShadowHost() const { |
| ShadowRoot* parent_shadow_root = ParentElementShadowRoot(); |
| return parent_shadow_root && !parent_shadow_root->IsV1(); |
| } |
| |
| ShadowRoot* Node::V1ShadowRootOfParent() const { |
| if (Element* parent = parentElement()) |
| return parent->ShadowRootIfV1(); |
| return nullptr; |
| } |
| |
| Element* Node::OwnerShadowHost() const { |
| if (ShadowRoot* root = ContainingShadowRoot()) |
| return &root->host(); |
| return nullptr; |
| } |
| |
| ShadowRoot* Node::ContainingShadowRoot() const { |
| Node& root = GetTreeScope().RootNode(); |
| return DynamicTo<ShadowRoot>(root); |
| } |
| |
| Node* Node::NonBoundaryShadowTreeRootNode() { |
| DCHECK(!IsShadowRoot()); |
| Node* root = this; |
| while (root) { |
| if (root->IsShadowRoot()) |
| return root; |
| Node* parent = root->ParentOrShadowHostNode(); |
| if (parent && parent->IsShadowRoot()) |
| return root; |
| root = parent; |
| } |
| return nullptr; |
| } |
| |
| ContainerNode* Node::NonShadowBoundaryParentNode() const { |
| ContainerNode* parent = parentNode(); |
| return parent && !parent->IsShadowRoot() ? parent : nullptr; |
| } |
| |
| Element* Node::ParentOrShadowHostElement() const { |
| ContainerNode* parent = ParentOrShadowHostNode(); |
| if (!parent) |
| return nullptr; |
| |
| if (auto* shadow_root = DynamicTo<ShadowRoot>(parent)) |
| return &shadow_root->host(); |
| |
| if (!parent->IsElementNode()) |
| return nullptr; |
| |
| return ToElement(parent); |
| } |
| |
| ContainerNode* Node::ParentOrShadowHostOrTemplateHostNode() const { |
| if (IsDocumentFragment() && ToDocumentFragment(this)->IsTemplateContent()) |
| return static_cast<const TemplateContentDocumentFragment*>(this)->Host(); |
| return ParentOrShadowHostNode(); |
| } |
| |
| Document* Node::ownerDocument() const { |
| Document* doc = &GetDocument(); |
| return doc == this ? nullptr : doc; |
| } |
| |
| const KURL& Node::baseURI() const { |
| return GetDocument().BaseURL(); |
| } |
| |
| bool Node::isEqualNode(Node* other) const { |
| if (!other) |
| return false; |
| |
| NodeType node_type = getNodeType(); |
| if (node_type != other->getNodeType()) |
| return false; |
| |
| if (nodeValue() != other->nodeValue()) |
| return false; |
| |
| if (IsAttributeNode()) { |
| if (ToAttr(this)->localName() != ToAttr(other)->localName()) |
| return false; |
| |
| if (ToAttr(this)->namespaceURI() != ToAttr(other)->namespaceURI()) |
| return false; |
| } else if (IsElementNode()) { |
| if (ToElement(this)->TagQName() != ToElement(other)->TagQName()) |
| return false; |
| |
| if (!ToElement(this)->HasEquivalentAttributes(ToElement(*other))) |
| return false; |
| } else if (nodeName() != other->nodeName()) { |
| return false; |
| } |
| |
| Node* child = firstChild(); |
| Node* other_child = other->firstChild(); |
| |
| while (child) { |
| if (!child->isEqualNode(other_child)) |
| return false; |
| |
| child = child->nextSibling(); |
| other_child = other_child->nextSibling(); |
| } |
| |
| if (other_child) |
| return false; |
| |
| if (IsDocumentTypeNode()) { |
| const DocumentType* document_type_this = ToDocumentType(this); |
| const DocumentType* document_type_other = ToDocumentType(other); |
| |
| if (document_type_this->publicId() != document_type_other->publicId()) |
| return false; |
| |
| if (document_type_this->systemId() != document_type_other->systemId()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool Node::isDefaultNamespace( |
| const AtomicString& namespace_uri_maybe_empty) const { |
| // https://dom.spec.whatwg.org/#dom-node-isdefaultnamespace |
| |
| // 1. If namespace is the empty string, then set it to null. |
| const AtomicString& namespace_uri = namespace_uri_maybe_empty.IsEmpty() |
| ? g_null_atom |
| : namespace_uri_maybe_empty; |
| |
| // 2. Let defaultNamespace be the result of running locate a namespace for |
| // context object using null. |
| const AtomicString& default_namespace = lookupNamespaceURI(String()); |
| |
| // 3. Return true if defaultNamespace is the same as namespace, and false |
| // otherwise. |
| return namespace_uri == default_namespace; |
| } |
| |
| const AtomicString& Node::lookupPrefix( |
| const AtomicString& namespace_uri) const { |
| // Implemented according to |
| // https://dom.spec.whatwg.org/#dom-node-lookupprefix |
| |
| if (namespace_uri.IsEmpty() || namespace_uri.IsNull()) |
| return g_null_atom; |
| |
| const Element* context; |
| |
| switch (getNodeType()) { |
| case kElementNode: |
| context = ToElement(this); |
| break; |
| case kDocumentNode: |
| context = To<Document>(this)->documentElement(); |
| break; |
| case kDocumentFragmentNode: |
| case kDocumentTypeNode: |
| context = nullptr; |
| break; |
| case kAttributeNode: |
| context = ToAttr(this)->ownerElement(); |
| break; |
| default: |
| context = parentElement(); |
| break; |
| } |
| |
| if (!context) |
| return g_null_atom; |
| |
| return context->LocateNamespacePrefix(namespace_uri); |
| } |
| |
| const AtomicString& Node::lookupNamespaceURI( |
| const String& specified_prefix) const { |
| // Implemented according to |
| // https://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri |
| |
| // 1. If prefix is the empty string, then set it to null. |
| String prefix = specified_prefix; |
| if (!specified_prefix.IsNull() && specified_prefix.IsEmpty()) |
| prefix = String(); |
| |
| // 2. Return the result of running locate a namespace for the context object |
| // using prefix. |
| |
| // https://dom.spec.whatwg.org/#locate-a-namespace |
| switch (getNodeType()) { |
| case kElementNode: { |
| const Element& element = ToElement(*this); |
| |
| // 1. If its namespace is not null and its namespace prefix is prefix, |
| // then return namespace. |
| if (!element.namespaceURI().IsNull() && element.prefix() == prefix) |
| return element.namespaceURI(); |
| |
| // 2. If it has an attribute whose namespace is the XMLNS namespace, |
| // namespace prefix is "xmlns", and local name is prefix, or if prefix is |
| // null and it has an attribute whose namespace is the XMLNS namespace, |
| // namespace prefix is null, and local name is "xmlns", then return its |
| // value if it is not the empty string, and null otherwise. |
| AttributeCollection attributes = element.Attributes(); |
| for (const Attribute& attr : attributes) { |
| if (attr.Prefix() == g_xmlns_atom && attr.LocalName() == prefix) { |
| if (!attr.Value().IsEmpty()) |
| return attr.Value(); |
| return g_null_atom; |
| } |
| if (attr.LocalName() == g_xmlns_atom && prefix.IsNull()) { |
| if (!attr.Value().IsEmpty()) |
| return attr.Value(); |
| return g_null_atom; |
| } |
| } |
| |
| // 3. If its parent element is null, then return null. |
| // 4. Return the result of running locate a namespace on its parent |
| // element using prefix. |
| if (Element* parent = parentElement()) |
| return parent->lookupNamespaceURI(prefix); |
| return g_null_atom; |
| } |
| case kDocumentNode: |
| if (Element* de = To<Document>(this)->documentElement()) |
| return de->lookupNamespaceURI(prefix); |
| return g_null_atom; |
| case kDocumentTypeNode: |
| case kDocumentFragmentNode: |
| return g_null_atom; |
| case kAttributeNode: { |
| const Attr* attr = ToAttr(this); |
| if (attr->ownerElement()) |
| return attr->ownerElement()->lookupNamespaceURI(prefix); |
| return g_null_atom; |
| } |
| default: |
| if (Element* parent = parentElement()) |
| return parent->lookupNamespaceURI(prefix); |
| return g_null_atom; |
| } |
| } |
| |
| String Node::textContent(bool convert_brs_to_newlines) const { |
| // This covers ProcessingInstruction and Comment that should return their |
| // value when .textContent is accessed on them, but should be ignored when |
| // iterated over as a descendant of a ContainerNode. |
| if (IsCharacterDataNode()) |
| return ToCharacterData(this)->data(); |
| |
| // Attribute nodes have their attribute values as textContent. |
| if (IsAttributeNode()) |
| return ToAttr(this)->value(); |
| |
| // Documents and non-container nodes (that are not CharacterData) |
| // have null textContent. |
| if (IsDocumentNode() || !IsContainerNode()) |
| return String(); |
| |
| StringBuilder content; |
| for (const Node& node : NodeTraversal::InclusiveDescendantsOf(*this)) { |
| if (IsHTMLBRElement(node) && convert_brs_to_newlines) { |
| content.Append('\n'); |
| } else if (node.IsTextNode()) { |
| content.Append(ToText(node).data()); |
| } |
| } |
| return content.ToString(); |
| } |
| |
| void Node::setTextContent(const StringOrTrustedScript& string_or_trusted_script, |
| ExceptionState& exception_state) { |
| String value = |
| string_or_trusted_script.IsString() |
| ? string_or_trusted_script.GetAsString() |
| : string_or_trusted_script.IsTrustedScript() |
| ? string_or_trusted_script.GetAsTrustedScript()->toString() |
| : g_empty_string; |
| setTextContent(value); |
| } |
| |
| void Node::textContent(StringOrTrustedScript& result) { |
| String value = textContent(); |
| if (!value.IsNull()) |
| result.SetString(value); |
| } |
| |
| void Node::setTextContent(const String& text) { |
| switch (getNodeType()) { |
| case kAttributeNode: |
| case kTextNode: |
| case kCdataSectionNode: |
| case kCommentNode: |
| case kProcessingInstructionNode: |
| setNodeValue(text); |
| return; |
| case kElementNode: |
| case kDocumentFragmentNode: { |
| // FIXME: Merge this logic into replaceChildrenWithText. |
| ContainerNode* container = ToContainerNode(this); |
| |
| // Note: This is an intentional optimization. |
| // See crbug.com/352836 also. |
| // No need to do anything if the text is identical. |
| if (container->HasOneTextChild() && |
| ToText(container->firstChild())->data() == text && !text.IsEmpty()) |
| return; |
| |
| ChildListMutationScope mutation(*this); |
| // Note: This API will not insert empty text nodes: |
| // https://dom.spec.whatwg.org/#dom-node-textcontent |
| if (text.IsEmpty()) { |
| container->RemoveChildren(kDispatchSubtreeModifiedEvent); |
| } else { |
| container->RemoveChildren(kOmitSubtreeModifiedEvent); |
| container->AppendChild(GetDocument().createTextNode(text), |
| ASSERT_NO_EXCEPTION); |
| } |
| return; |
| } |
| case kDocumentNode: |
| case kDocumentTypeNode: |
| // Do nothing. |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| unsigned short Node::compareDocumentPosition( |
| const Node* other_node, |
| ShadowTreesTreatment treatment) const { |
| if (other_node == this) |
| return kDocumentPositionEquivalent; |
| |
| const Attr* attr1 = getNodeType() == kAttributeNode ? ToAttr(this) : nullptr; |
| const Attr* attr2 = other_node->getNodeType() == kAttributeNode |
| ? ToAttr(other_node) |
| : nullptr; |
| |
| const Node* start1 = attr1 ? attr1->ownerElement() : this; |
| const Node* start2 = attr2 ? attr2->ownerElement() : other_node; |
| |
| // If either of start1 or start2 is null, then we are disconnected, since one |
| // of the nodes is an orphaned attribute node. |
| if (!start1 || !start2) { |
| unsigned short direction = (this > other_node) ? kDocumentPositionPreceding |
| : kDocumentPositionFollowing; |
| return kDocumentPositionDisconnected | |
| kDocumentPositionImplementationSpecific | direction; |
| } |
| |
| HeapVector<Member<const Node>, 16> chain1; |
| HeapVector<Member<const Node>, 16> chain2; |
| if (attr1) |
| chain1.push_back(attr1); |
| if (attr2) |
| chain2.push_back(attr2); |
| |
| if (attr1 && attr2 && start1 == start2 && start1) { |
| // We are comparing two attributes on the same node. Crawl our attribute map |
| // and see which one we hit first. |
| const Element* owner1 = attr1->ownerElement(); |
| AttributeCollection attributes = owner1->Attributes(); |
| for (const Attribute& attr : attributes) { |
| // If neither of the two determining nodes is a child node and nodeType is |
| // the same for both determining nodes, then an implementation-dependent |
| // order between the determining nodes is returned. This order is stable |
| // as long as no nodes of the same nodeType are inserted into or removed |
| // from the direct container. This would be the case, for example, when |
| // comparing two attributes of the same element, and inserting or removing |
| // additional attributes might change the order between existing |
| // attributes. |
| if (attr1->GetQualifiedName() == attr.GetName()) |
| return kDocumentPositionImplementationSpecific | |
| kDocumentPositionFollowing; |
| if (attr2->GetQualifiedName() == attr.GetName()) |
| return kDocumentPositionImplementationSpecific | |
| kDocumentPositionPreceding; |
| } |
| |
| NOTREACHED(); |
| return kDocumentPositionDisconnected; |
| } |
| |
| // If one node is in the document and the other is not, we must be |
| // disconnected. If the nodes have different owning documents, they must be |
| // disconnected. Note that we avoid comparing Attr nodes here, since they |
| // return false from isConnected() all the time (which seems like a bug). |
| if (start1->isConnected() != start2->isConnected() || |
| (treatment == kTreatShadowTreesAsDisconnected && |
| start1->GetTreeScope() != start2->GetTreeScope())) { |
| unsigned short direction = (this > other_node) ? kDocumentPositionPreceding |
| : kDocumentPositionFollowing; |
| return kDocumentPositionDisconnected | |
| kDocumentPositionImplementationSpecific | direction; |
| } |
| |
| // We need to find a common ancestor container, and then compare the indices |
| // of the two immediate children. |
| const Node* current; |
| for (current = start1; current; current = current->ParentOrShadowHostNode()) |
| chain1.push_back(current); |
| for (current = start2; current; current = current->ParentOrShadowHostNode()) |
| chain2.push_back(current); |
| |
| unsigned index1 = chain1.size(); |
| unsigned index2 = chain2.size(); |
| |
| // If the two elements don't have a common root, they're not in the same tree. |
| if (chain1[index1 - 1] != chain2[index2 - 1]) { |
| unsigned short direction = (this > other_node) ? kDocumentPositionPreceding |
| : kDocumentPositionFollowing; |
| return kDocumentPositionDisconnected | |
| kDocumentPositionImplementationSpecific | direction; |
| } |
| |
| unsigned connection = start1->GetTreeScope() != start2->GetTreeScope() |
| ? kDocumentPositionDisconnected | |
| kDocumentPositionImplementationSpecific |
| : 0; |
| |
| // Walk the two chains backwards and look for the first difference. |
| for (unsigned i = std::min(index1, index2); i; --i) { |
| const Node* child1 = chain1[--index1]; |
| const Node* child2 = chain2[--index2]; |
| if (child1 != child2) { |
| // If one of the children is an attribute, it wins. |
| if (child1->getNodeType() == kAttributeNode) |
| return kDocumentPositionFollowing | connection; |
| if (child2->getNodeType() == kAttributeNode) |
| return kDocumentPositionPreceding | connection; |
| |
| // If one of the children is a shadow root, |
| if (child1->IsShadowRoot() || child2->IsShadowRoot()) { |
| if (!child2->IsShadowRoot()) |
| return Node::kDocumentPositionFollowing | connection; |
| if (!child1->IsShadowRoot()) |
| return Node::kDocumentPositionPreceding | connection; |
| |
| return Node::kDocumentPositionPreceding | connection; |
| } |
| |
| if (!child2->nextSibling()) |
| return kDocumentPositionFollowing | connection; |
| if (!child1->nextSibling()) |
| return kDocumentPositionPreceding | connection; |
| |
| // Otherwise we need to see which node occurs first. Crawl backwards from |
| // child2 looking for child1. |
| for (const Node* child = child2->previousSibling(); child; |
| child = child->previousSibling()) { |
| if (child == child1) |
| return kDocumentPositionFollowing | connection; |
| } |
| return kDocumentPositionPreceding | connection; |
| } |
| } |
| |
| // There was no difference between the two parent chains, i.e., one was a |
| // subset of the other. The shorter chain is the ancestor. |
| return index1 < index2 ? kDocumentPositionFollowing | |
| kDocumentPositionContainedBy | connection |
| : kDocumentPositionPreceding | |
| kDocumentPositionContains | connection; |
| } |
| |
| String Node::DebugName() const { |
| StringBuilder name; |
| AppendUnsafe(name, DebugNodeName()); |
| if (IsElementNode()) { |
| const Element& this_element = ToElement(*this); |
| if (this_element.HasID()) { |
| name.Append(" id=\'"); |
| AppendUnsafe(name, this_element.GetIdAttribute()); |
| name.Append('\''); |
| } |
| |
| if (this_element.HasClass()) { |
| name.Append(" class=\'"); |
| for (wtf_size_t i = 0; i < this_element.ClassNames().size(); ++i) { |
| if (i > 0) |
| name.Append(' '); |
| AppendUnsafe(name, this_element.ClassNames()[i]); |
| } |
| name.Append('\''); |
| } |
| } |
| return name.ToString(); |
| } |
| |
| String Node::DebugNodeName() const { |
| return nodeName(); |
| } |
| |
| static void DumpAttributeDesc(const Node& node, |
| const QualifiedName& name, |
| StringBuilder& builder) { |
| if (!node.IsElementNode()) |
| return; |
| const AtomicString& value = ToElement(node).getAttribute(name); |
| if (value.IsEmpty()) |
| return; |
| builder.Append(' '); |
| builder.Append(name.ToString()); |
| builder.Append("="); |
| builder.Append(String(value).EncodeForDebugging()); |
| } |
| |
| std::ostream& operator<<(std::ostream& ostream, const Node& node) { |
| return ostream << node.ToString().Utf8().data(); |
| } |
| |
| std::ostream& operator<<(std::ostream& ostream, const Node* node) { |
| if (!node) |
| return ostream << "null"; |
| return ostream << *node; |
| } |
| |
| String Node::ToString() const { |
| if (getNodeType() == Node::kProcessingInstructionNode) |
| return "?" + nodeName(); |
| if (auto* shadow_root = DynamicTo<ShadowRoot>(this)) { |
| // nodeName of ShadowRoot is #document-fragment. It's confused with |
| // DocumentFragment. |
| std::stringstream shadow_root_type; |
| shadow_root_type << shadow_root->GetType(); |
| String shadow_root_type_str(shadow_root_type.str().c_str()); |
| return "#shadow-root(" + shadow_root_type_str + ")"; |
| } |
| if (IsDocumentTypeNode()) |
| return "DOCTYPE " + nodeName(); |
| |
| StringBuilder builder; |
| builder.Append(nodeName()); |
| if (IsTextNode()) { |
| builder.Append(" "); |
| builder.Append(nodeValue().EncodeForDebugging()); |
| return builder.ToString(); |
| } |
| DumpAttributeDesc(*this, html_names::kIdAttr, builder); |
| DumpAttributeDesc(*this, html_names::kClassAttr, builder); |
| DumpAttributeDesc(*this, html_names::kStyleAttr, builder); |
| if (HasEditableStyle(*this)) |
| builder.Append(" (editable)"); |
| if (GetDocument().FocusedElement() == this) |
| builder.Append(" (focused)"); |
| return builder.ToString(); |
| } |
| |
| #ifndef NDEBUG |
| |
| String Node::ToTreeStringForThis() const { |
| return ToMarkedTreeString(this, "*"); |
| } |
| |
| String Node::ToFlatTreeStringForThis() const { |
| return ToMarkedFlatTreeString(this, "*"); |
| } |
| |
| void Node::PrintNodePathTo(std::ostream& stream) const { |
| HeapVector<Member<const Node>, 16> chain; |
| const Node* node = this; |
| while (node->ParentOrShadowHostNode()) { |
| chain.push_back(node); |
| node = node->ParentOrShadowHostNode(); |
| } |
| for (unsigned index = chain.size(); index > 0; --index) { |
| const Node* node = chain[index - 1]; |
| if (node->IsShadowRoot()) { |
| stream << "/#shadow-root"; |
| continue; |
| } |
| |
| switch (node->getNodeType()) { |
| case kElementNode: { |
| stream << "/" << node->nodeName().Utf8().data(); |
| |
| const Element* element = ToElement(node); |
| const AtomicString& idattr = element->GetIdAttribute(); |
| bool has_id_attr = !idattr.IsNull() && !idattr.IsEmpty(); |
| if (node->previousSibling() || node->nextSibling()) { |
| int count = 0; |
| for (const Node* previous = node->previousSibling(); previous; |
| previous = previous->previousSibling()) { |
| if (previous->nodeName() == node->nodeName()) { |
| ++count; |
| } |
| } |
| if (has_id_attr) |
| stream << "[@id=\"" << idattr.Utf8().data() |
| << "\" and position()=" << count << "]"; |
| else |
| stream << "[" << count << "]"; |
| } else if (has_id_attr) { |
| stream << "[@id=\"" << idattr.Utf8().data() << "\"]"; |
| } |
| break; |
| } |
| case kTextNode: |
| stream << "/text()"; |
| break; |
| case kAttributeNode: |
| stream << "/@" << node->nodeName().Utf8().data(); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| static void AppendMarkedTree(const String& base_indent, |
| const Node* root_node, |
| const Node* marked_node1, |
| const char* marked_label1, |
| const Node* marked_node2, |
| const char* marked_label2, |
| StringBuilder& builder) { |
| for (const Node& node : NodeTraversal::InclusiveDescendantsOf(*root_node)) { |
| StringBuilder indent; |
| if (node == marked_node1) |
| indent.Append(marked_label1); |
| if (node == marked_node2) |
| indent.Append(marked_label2); |
| indent.Append(base_indent); |
| for (const Node* tmp_node = &node; tmp_node && tmp_node != root_node; |
| tmp_node = tmp_node->ParentOrShadowHostNode()) |
| indent.Append('\t'); |
| builder.Append(indent); |
| builder.Append(node.ToString()); |
| builder.Append("\n"); |
| indent.Append('\t'); |
| |
| if (node.IsElementNode()) { |
| const Element& element = ToElement(node); |
| if (Element* pseudo = element.GetPseudoElement(kPseudoIdBefore)) |
| AppendMarkedTree(indent.ToString(), pseudo, marked_node1, marked_label1, |
| marked_node2, marked_label2, builder); |
| if (Element* pseudo = element.GetPseudoElement(kPseudoIdAfter)) |
| AppendMarkedTree(indent.ToString(), pseudo, marked_node1, marked_label1, |
| marked_node2, marked_label2, builder); |
| if (Element* pseudo = element.GetPseudoElement(kPseudoIdFirstLetter)) |
| AppendMarkedTree(indent.ToString(), pseudo, marked_node1, marked_label1, |
| marked_node2, marked_label2, builder); |
| if (Element* pseudo = element.GetPseudoElement(kPseudoIdBackdrop)) |
| AppendMarkedTree(indent.ToString(), pseudo, marked_node1, marked_label1, |
| marked_node2, marked_label2, builder); |
| } |
| |
| if (ShadowRoot* shadow_root = node.GetShadowRoot()) { |
| AppendMarkedTree(indent.ToString(), shadow_root, marked_node1, |
| marked_label1, marked_node2, marked_label2, builder); |
| } |
| } |
| } |
| |
| static void AppendMarkedFlatTree(const String& base_indent, |
| const Node* root_node, |
| const Node* marked_node1, |
| const char* marked_label1, |
| const Node* marked_node2, |
| const char* marked_label2, |
| StringBuilder& builder) { |
| for (const Node* node = root_node; node; |
| node = FlatTreeTraversal::NextSibling(*node)) { |
| StringBuilder indent; |
| if (node == marked_node1) |
| indent.Append(marked_label1); |
| if (node == marked_node2) |
| indent.Append(marked_label2); |
| indent.Append(base_indent); |
| builder.Append(indent); |
| builder.Append(node->ToString()); |
| builder.Append("\n"); |
| indent.Append('\t'); |
| |
| if (Node* child = FlatTreeTraversal::FirstChild(*node)) |
| AppendMarkedFlatTree(indent.ToString(), child, marked_node1, |
| marked_label1, marked_node2, marked_label2, builder); |
| } |
| } |
| |
| String Node::ToMarkedTreeString(const Node* marked_node1, |
| const char* marked_label1, |
| const Node* marked_node2, |
| const char* marked_label2) const { |
| const Node* root_node; |
| const Node* node = this; |
| while (node->ParentOrShadowHostNode() && !IsHTMLBodyElement(*node)) |
| node = node->ParentOrShadowHostNode(); |
| root_node = node; |
| |
| StringBuilder builder; |
| String starting_indent; |
| AppendMarkedTree(starting_indent, root_node, marked_node1, marked_label1, |
| marked_node2, marked_label2, builder); |
| return builder.ToString(); |
| } |
| |
| String Node::ToMarkedFlatTreeString(const Node* marked_node1, |
| const char* marked_label1, |
| const Node* marked_node2, |
| const char* marked_label2) const { |
| const Node* root_node; |
| const Node* node = this; |
| while (node->ParentOrShadowHostNode() && !IsHTMLBodyElement(*node)) |
| node = node->ParentOrShadowHostNode(); |
| root_node = node; |
| |
| StringBuilder builder; |
| String starting_indent; |
| AppendMarkedFlatTree(starting_indent, root_node, marked_node1, marked_label1, |
| marked_node2, marked_label2, builder); |
| return builder.ToString(); |
| } |
| |
| static ContainerNode* ParentOrShadowHostOrFrameOwner(const Node* node) { |
| ContainerNode* parent = node->ParentOrShadowHostNode(); |
| if (!parent && node->GetDocument().GetFrame()) |
| parent = node->GetDocument().GetFrame()->DeprecatedLocalOwner(); |
| return parent; |
| } |
| |
| static void PrintSubTreeAcrossFrame(const Node* node, |
| const Node* marked_node, |
| const String& indent, |
| std::ostream& stream) { |
| if (node == marked_node) |
| stream << "*"; |
| stream << indent.Utf8().data() << *node << "\n"; |
| if (node->IsFrameOwnerElement()) { |
| PrintSubTreeAcrossFrame(ToHTMLFrameOwnerElement(node)->contentDocument(), |
| marked_node, indent + "\t", stream); |
| } |
| if (ShadowRoot* shadow_root = node->GetShadowRoot()) |
| PrintSubTreeAcrossFrame(shadow_root, marked_node, indent + "\t", stream); |
| for (const Node* child = node->firstChild(); child; |
| child = child->nextSibling()) |
| PrintSubTreeAcrossFrame(child, marked_node, indent + "\t", stream); |
| } |
| |
| void Node::ShowTreeForThisAcrossFrame() const { |
| const Node* root_node = this; |
| while (ParentOrShadowHostOrFrameOwner(root_node)) |
| root_node = ParentOrShadowHostOrFrameOwner(root_node); |
| std::stringstream stream; |
| PrintSubTreeAcrossFrame(root_node, this, "", stream); |
| LOG(INFO) << "\n" << stream.str(); |
| } |
| |
| #endif |
| |
| // -------- |
| |
| Element* Node::EnclosingLinkEventParentOrSelf() const { |
| // https://crbug.com/784492 |
| DCHECK(this); |
| |
| for (const Node* node = this; node; node = FlatTreeTraversal::Parent(*node)) { |
| // For imagemaps, the enclosing link node is the associated area element not |
| // the image itself. So we don't let images be the enclosingLinkNode, even |
| // though isLink sometimes returns true for them. |
| if (node->IsLink() && !IsHTMLImageElement(*node)) { |
| // Casting to Element is safe because only HTMLAnchorElement, |
| // HTMLImageElement and SVGAElement can return true for isLink(). |
| return ToElement(const_cast<Node*>(node)); |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| const AtomicString& Node::InterfaceName() const { |
| return event_target_names::kNode; |
| } |
| |
| ExecutionContext* Node::GetExecutionContext() const { |
| return GetDocument().ContextDocument(); |
| } |
| |
| void Node::WillMoveToNewDocument(Document& old_document, |
| Document& new_document) { |
| if (!old_document.GetPage() || |
| old_document.GetPage() == new_document.GetPage()) |
| return; |
| |
| old_document.GetFrame()->GetEventHandlerRegistry().DidMoveOutOfPage(*this); |
| |
| if (IsElementNode()) { |
| StylePropertyMapReadOnly* computed_style_map_item = |
| old_document.RemoveComputedStyleMapItem(ToElement(this)); |
| if (computed_style_map_item) { |
| new_document.AddComputedStyleMapItem(ToElement(this), |
| computed_style_map_item); |
| } |
| } |
| } |
| |
| void Node::DidMoveToNewDocument(Document& old_document) { |
| TreeScopeAdopter::EnsureDidMoveToNewDocumentWasCalled(old_document); |
| |
| if (const EventTargetData* event_target_data = GetEventTargetData()) { |
| const EventListenerMap& listener_map = |
| event_target_data->event_listener_map; |
| if (!listener_map.IsEmpty()) { |
| for (const auto& type : listener_map.EventTypes()) |
| GetDocument().AddListenerTypeIfNeeded(type, *this); |
| } |
| } |
| if (IsTextNode()) |
| old_document.Markers().RemoveMarkersForNode(*ToText(this)); |
| if (GetDocument().GetPage() && |
| GetDocument().GetPage() != old_document.GetPage()) { |
| GetDocument().GetFrame()->GetEventHandlerRegistry().DidMoveIntoPage(*this); |
| } |
| |
| if (const HeapVector<TraceWrapperMember<MutationObserverRegistration>>* |
| registry = MutationObserverRegistry()) { |
| for (const auto& registration : *registry) { |
| GetDocument().AddMutationObserverTypes(registration->MutationTypes()); |
| } |
| } |
| |
| if (TransientMutationObserverRegistry()) { |
| for (MutationObserverRegistration* registration : |
| *TransientMutationObserverRegistry()) |
| GetDocument().AddMutationObserverTypes(registration->MutationTypes()); |
| } |
| } |
| |
| void Node::AddedEventListener(const AtomicString& event_type, |
| RegisteredEventListener& registered_listener) { |
| EventTarget::AddedEventListener(event_type, registered_listener); |
| GetDocument().AddListenerTypeIfNeeded(event_type, *this); |
| if (auto* frame = GetDocument().GetFrame()) { |
| frame->GetEventHandlerRegistry().DidAddEventHandler( |
| *this, event_type, registered_listener.Options()); |
| } |
| } |
| |
| void Node::RemovedEventListener( |
| const AtomicString& event_type, |
| const RegisteredEventListener& registered_listener) { |
| EventTarget::RemovedEventListener(event_type, registered_listener); |
| // FIXME: Notify Document that the listener has vanished. We need to keep |
| // track of a number of listeners for each type, not just a bool - see |
| // https://bugs.webkit.org/show_bug.cgi?id=33861 |
| if (auto* frame = GetDocument().GetFrame()) { |
| frame->GetEventHandlerRegistry().DidRemoveEventHandler( |
| *this, event_type, registered_listener.Options()); |
| } |
| } |
| |
| void Node::RemoveAllEventListeners() { |
| if (HasEventListeners() && GetDocument().GetPage()) |
| GetDocument() |
| .GetFrame() |
| ->GetEventHandlerRegistry() |
| .DidRemoveAllEventHandlers(*this); |
| EventTarget::RemoveAllEventListeners(); |
| } |
| |
| void Node::RemoveAllEventListenersRecursively() { |
| ScriptForbiddenScope forbid_script_during_raw_iteration; |
| for (Node& node : NodeTraversal::StartsAt(*this)) { |
| node.RemoveAllEventListeners(); |
| if (ShadowRoot* root = node.GetShadowRoot()) |
| root->RemoveAllEventListenersRecursively(); |
| } |
| } |
| |
| using EventTargetDataMap = |
| HeapHashMap<WeakMember<Node>, TraceWrapperMember<EventTargetData>>; |
| static EventTargetDataMap& GetEventTargetDataMap() { |
| DEFINE_STATIC_LOCAL(Persistent<EventTargetDataMap>, map, |
| (MakeGarbageCollected<EventTargetDataMap>())); |
| return *map; |
| } |
| |
| EventTargetData* Node::GetEventTargetData() { |
| return HasEventTargetData() ? GetEventTargetDataMap().at(this) : nullptr; |
| } |
| |
| EventTargetData& Node::EnsureEventTargetData() { |
| if (HasEventTargetData()) |
| return *GetEventTargetDataMap().at(this); |
| DCHECK(!GetEventTargetDataMap().Contains(this)); |
| SetHasEventTargetData(true); |
| EventTargetData* data = MakeGarbageCollected<EventTargetData>(); |
| GetEventTargetDataMap().Set(this, data); |
| return *data; |
| } |
| |
| const HeapVector<TraceWrapperMember<MutationObserverRegistration>>* |
| Node::MutationObserverRegistry() { |
| if (!HasRareData()) |
| return nullptr; |
| NodeMutationObserverData* data = RareData()->MutationObserverData(); |
| if (!data) |
| return nullptr; |
| return &data->Registry(); |
| } |
| |
| const HeapHashSet<TraceWrapperMember<MutationObserverRegistration>>* |
| Node::TransientMutationObserverRegistry() { |
| if (!HasRareData()) |
| return nullptr; |
| NodeMutationObserverData* data = RareData()->MutationObserverData(); |
| if (!data) |
| return nullptr; |
| return &data->TransientRegistry(); |
| } |
| |
| template <typename Registry> |
| static inline void CollectMatchingObserversForMutation( |
| HeapHashMap<Member<MutationObserver>, MutationRecordDeliveryOptions>& |
| observers, |
| Registry* registry, |
| Node& target, |
| MutationType type, |
| const QualifiedName* attribute_name) { |
| if (!registry) |
| return; |
| |
| for (const auto& registration : *registry) { |
| if (registration->ShouldReceiveMutationFrom(target, type, attribute_name)) { |
| MutationRecordDeliveryOptions delivery_options = |
| registration->DeliveryOptions(); |
| HeapHashMap<Member<MutationObserver>, |
| MutationRecordDeliveryOptions>::AddResult result = |
| observers.insert(®istration->Observer(), delivery_options); |
| if (!result.is_new_entry) |
| result.stored_value->value |= delivery_options; |
| } |
| } |
| } |
| |
| void Node::GetRegisteredMutationObserversOfType( |
| HeapHashMap<Member<MutationObserver>, MutationRecordDeliveryOptions>& |
| observers, |
| MutationType type, |
| const QualifiedName* attribute_name) { |
| DCHECK((type == kMutationTypeAttributes && attribute_name) || |
| !attribute_name); |
| CollectMatchingObserversForMutation(observers, MutationObserverRegistry(), |
| *this, type, attribute_name); |
| CollectMatchingObserversForMutation(observers, |
| TransientMutationObserverRegistry(), |
| *this, type, attribute_name); |
| ScriptForbiddenScope forbid_script_during_raw_iteration; |
| for (Node* node = parentNode(); node; node = node->parentNode()) { |
| CollectMatchingObserversForMutation(observers, |
| node->MutationObserverRegistry(), *this, |
| type, attribute_name); |
| CollectMatchingObserversForMutation( |
| observers, node->TransientMutationObserverRegistry(), *this, type, |
| attribute_name); |
| } |
| } |
| |
| void Node::RegisterMutationObserver( |
| MutationObserver& observer, |
| MutationObserverOptions options, |
| const HashSet<AtomicString>& attribute_filter) { |
| MutationObserverRegistration* registration = nullptr; |
| for (const auto& item : |
| EnsureRareData().EnsureMutationObserverData().Registry()) { |
| if (&item->Observer() == &observer) { |
| registration = item.Get(); |
| registration->ResetObservation(options, attribute_filter); |
| } |
| } |
| |
| if (!registration) { |
| registration = MutationObserverRegistration::Create(observer, this, options, |
| attribute_filter); |
| EnsureRareData().EnsureMutationObserverData().AddRegistration(registration); |
| } |
| |
| GetDocument().AddMutationObserverTypes(registration->MutationTypes()); |
| } |
| |
| void Node::UnregisterMutationObserver( |
| MutationObserverRegistration* registration) { |
| const HeapVector<TraceWrapperMember<MutationObserverRegistration>>* registry = |
| MutationObserverRegistry(); |
| DCHECK(registry); |
| if (!registry) |
| return; |
| |
| // FIXME: Simplify the registration/transient registration logic to make this |
| // understandable by humans. The explicit dispose() is needed to have the |
| // registration object unregister itself promptly. |
| registration->Dispose(); |
| EnsureRareData().EnsureMutationObserverData().RemoveRegistration( |
| registration); |
| } |
| |
| void Node::RegisterTransientMutationObserver( |
| MutationObserverRegistration* registration) { |
| EnsureRareData().EnsureMutationObserverData().AddTransientRegistration( |
| registration); |
| } |
| |
| void Node::UnregisterTransientMutationObserver( |
| MutationObserverRegistration* registration) { |
| const HeapHashSet<TraceWrapperMember<MutationObserverRegistration>>* |
| transient_registry = TransientMutationObserverRegistry(); |
| DCHECK(transient_registry); |
| if (!transient_registry) |
| return; |
| |
| EnsureRareData().EnsureMutationObserverData().RemoveTransientRegistration( |
| registration); |
| } |
| |
| void Node::NotifyMutationObserversNodeWillDetach() { |
| if (!GetDocument().HasMutationObservers()) |
| return; |
| |
| ScriptForbiddenScope forbid_script_during_raw_iteration; |
| for (Node* node = parentNode(); node; node = node->parentNode()) { |
| if (const HeapVector<TraceWrapperMember<MutationObserverRegistration>>* |
| registry = node->MutationObserverRegistry()) { |
| for (const auto& registration : *registry) |
| registration->ObservedSubtreeNodeWillDetach(*this); |
| } |
| |
| if (const HeapHashSet<TraceWrapperMember<MutationObserverRegistration>>* |
| transient_registry = node->TransientMutationObserverRegistry()) { |
| for (auto& registration : *transient_registry) |
| registration->ObservedSubtreeNodeWillDetach(*this); |
| } |
| } |
| } |
| |
| void Node::HandleLocalEvents(Event& event) { |
| if (!HasEventTargetData()) |
| return; |
| |
| if (IsDisabledFormControl(this) && event.IsMouseEvent() && |
| !RuntimeEnabledFeatures::SendMouseEventsDisabledFormControlsEnabled()) { |
| if (HasEventListeners(event.type())) { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kDispatchMouseEventOnDisabledFormControl); |
| if (event.type() == event_type_names::kMousedown || |
| event.type() == event_type_names::kMouseup) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature::kDispatchMouseUpDownEventOnDisabledFormControl); |
| } |
| } |
| return; |
| } |
| |
| FireEventListeners(event); |
| } |
| |
| void Node::DispatchScopedEvent(Event& event) { |
| event.SetTrusted(true); |
| EventDispatcher::DispatchScopedEvent(*this, event); |
| } |
| |
| DispatchEventResult Node::DispatchEventInternal(Event& event) { |
| return EventDispatcher::DispatchEvent(*this, event); |
| } |
| |
| void Node::DispatchSubtreeModifiedEvent() { |
| if (IsInShadowTree()) |
| return; |
| |
| #if DCHECK_IS_ON() |
| DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden()); |
| #endif |
| |
| if (!GetDocument().HasListenerType(Document::kDOMSubtreeModifiedListener)) |
| return; |
| |
| DispatchScopedEvent(*MutationEvent::Create( |
| event_type_names::kDOMSubtreeModified, Event::Bubbles::kYes)); |
| } |
| |
| DispatchEventResult Node::DispatchDOMActivateEvent(int detail, |
| Event& underlying_event) { |
| #if DCHECK_IS_ON() |
| DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden()); |
| #endif |
| UIEvent& event = *UIEvent::Create(); |
| event.initUIEvent(event_type_names::kDOMActivate, true, true, |
| GetDocument().domWindow(), detail); |
| event.SetUnderlyingEvent(&underlying_event); |
| event.SetComposed(underlying_event.composed()); |
| DispatchScopedEvent(event); |
| |
| // TODO(dtapuska): Dispatching scoped events shouldn't check the return |
| // type because the scoped event could get put off in the delayed queue. |
| return EventTarget::GetDispatchEventResult(event); |
| } |
| |
| void Node::DispatchSimulatedClick(Event* underlying_event, |
| SimulatedClickMouseEventOptions event_options, |
| SimulatedClickCreationScope scope) { |
| EventDispatcher::DispatchSimulatedClick(*this, underlying_event, |
| event_options, scope); |
| } |
| |
| void Node::DispatchInputEvent() { |
| // Legacy 'input' event for forms set value and checked. |
| Event* event = Event::CreateBubble(event_type_names::kInput); |
| event->SetComposed(true); |
| DispatchScopedEvent(*event); |
| } |
| |
| void Node::DefaultEventHandler(Event& event) { |
| if (event.target() != this) |
| return; |
| const AtomicString& event_type = event.type(); |
| if (event_type == event_type_names::kKeydown || |
| event_type == event_type_names::kKeypress) { |
| if (event.IsKeyboardEvent()) { |
| if (LocalFrame* frame = GetDocument().GetFrame()) { |
| frame->GetEventHandler().DefaultKeyboardEventHandler( |
| ToKeyboardEvent(&event)); |
| } |
| } |
| } else if (event_type == event_type_names::kClick) { |
| int detail = event.IsUIEvent() ? ToUIEvent(event).detail() : 0; |
| if (DispatchDOMActivateEvent(detail, event) != |
| DispatchEventResult::kNotCanceled) |
| event.SetDefaultHandled(); |
| } else if (event_type == event_type_names::kContextmenu && |
| event.IsMouseEvent()) { |
| if (Page* page = GetDocument().GetPage()) { |
| page->GetContextMenuController().HandleContextMenuEvent( |
| ToMouseEvent(&event)); |
| } |
| } else if (event_type == event_type_names::kTextInput) { |
| if (event.HasInterface(event_interface_names::kTextEvent)) { |
| if (LocalFrame* frame = GetDocument().GetFrame()) { |
| frame->GetEventHandler().DefaultTextInputEventHandler( |
| ToTextEvent(&event)); |
| } |
| } |
| } else if (RuntimeEnabledFeatures::MiddleClickAutoscrollEnabled() && |
| event_type == event_type_names::kMousedown && |
| event.IsMouseEvent()) { |
| auto& mouse_event = ToMouseEvent(event); |
| if (mouse_event.button() == |
| static_cast<short>(WebPointerProperties::Button::kMiddle)) { |
| if (EnclosingLinkEventParentOrSelf()) |
| return; |
| |
| // Avoid that canBeScrolledAndHasScrollableArea changes layout tree |
| // structure. |
| // FIXME: We should avoid synchronous layout if possible. We can |
| // remove this synchronous layout if we avoid synchronous layout in |
| // LayoutTextControlSingleLine::scrollHeight |
| GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| LayoutObject* layout_object = GetLayoutObject(); |
| while ( |
| layout_object && |
| (!layout_object->IsBox() || |
| !ToLayoutBox(layout_object)->CanBeScrolledAndHasScrollableArea())) { |
| if (auto* document = DynamicTo<Document>(layout_object->GetNode())) { |
| Element* owner = document->LocalOwner(); |
| layout_object = owner ? owner->GetLayoutObject() : nullptr; |
| } else { |
| layout_object = layout_object->Parent(); |
| } |
| } |
| if (layout_object) { |
| if (LocalFrame* frame = GetDocument().GetFrame()) |
| frame->GetEventHandler().StartMiddleClickAutoscroll(layout_object); |
| } |
| } |
| } else if (event_type == event_type_names::kMouseup && event.IsMouseEvent()) { |
| auto& mouse_event = ToMouseEvent(event); |
| if (mouse_event.button() == |
| static_cast<short>(WebPointerProperties::Button::kBack)) { |
| if (LocalFrame* frame = GetDocument().GetFrame()) { |
| if (frame->Client()->NavigateBackForward(-1)) |
| event.SetDefaultHandled(); |
| } |
| } else if (mouse_event.button() == |
| static_cast<short>(WebPointerProperties::Button::kForward)) { |
| if (LocalFrame* frame = GetDocument().GetFrame()) { |
| if (frame->Client()->NavigateBackForward(1)) |
| event.SetDefaultHandled(); |
| } |
| } |
| } |
| } |
| |
| void Node::WillCallDefaultEventHandler(const Event& event) { |
| if (!event.IsKeyboardEvent()) |
| return; |
| |
| if (!IsFocused() || GetDocument().LastFocusType() != kWebFocusTypeMouse) |
| return; |
| |
| if (event.type() != event_type_names::kKeydown || |
| GetDocument().HadKeyboardEvent()) |
| return; |
| |
| GetDocument().SetHadKeyboardEvent(true); |
| |
| // Changes to HadKeyboardEvent may affect :focus-visible matching, |
| // ShouldHaveFocusAppearance and LayoutTheme::IsFocused(). |
| // Inform LayoutTheme if HadKeyboardEvent changes. |
| if (GetLayoutObject()) { |
| GetLayoutObject()->InvalidateIfControlStateChanged(kFocusControlState); |
| |
| if (RuntimeEnabledFeatures::CSSFocusVisibleEnabled() && IsContainerNode()) |
| ToContainerNode(*this).FocusVisibleStateChanged(); |
| } |
| } |
| |
| bool Node::HasActivationBehavior() const { |
| return false; |
| } |
| |
| bool Node::WillRespondToMouseMoveEvents() { |
| if (IsDisabledFormControl(this)) |
| return false; |
| return HasEventListeners(event_type_names::kMousemove) || |
| HasEventListeners(event_type_names::kMouseover) || |
| HasEventListeners(event_type_names::kMouseout); |
| } |
| |
| bool Node::WillRespondToMouseClickEvents() { |
| if (IsDisabledFormControl(this)) |
| return false; |
| GetDocument().UpdateStyleAndLayoutTree(); |
| return HasEditableStyle(*this) || |
| HasEventListeners(event_type_names::kMouseup) || |
| HasEventListeners(event_type_names::kMousedown) || |
| HasEventListeners(event_type_names::kClick) || |
| HasEventListeners(event_type_names::kDOMActivate); |
| } |
| |
| bool Node::WillRespondToTouchEvents() { |
| if (IsDisabledFormControl(this)) |
| return false; |
| return HasEventListeners(event_type_names::kTouchstart) || |
| HasEventListeners(event_type_names::kTouchmove) || |
| HasEventListeners(event_type_names::kTouchcancel) || |
| HasEventListeners(event_type_names::kTouchend); |
| } |
| |
| unsigned Node::ConnectedSubframeCount() const { |
| return HasRareData() ? RareData()->ConnectedSubframeCount() : 0; |
| } |
| |
| void Node::IncrementConnectedSubframeCount() { |
| DCHECK(IsContainerNode()); |
| EnsureRareData().IncrementConnectedSubframeCount(); |
| } |
| |
| void Node::DecrementConnectedSubframeCount() { |
| RareData()->DecrementConnectedSubframeCount(); |
| } |
| |
| StaticNodeList* Node::getDestinationInsertionPoints() { |
| UpdateDistributionForLegacyDistributedNodes(); |
| HeapVector<Member<V0InsertionPoint>, 8> insertion_points; |
| CollectDestinationInsertionPoints(*this, insertion_points); |
| HeapVector<Member<Node>> filtered_insertion_points; |
| for (const auto& insertion_point : insertion_points) { |
| DCHECK(insertion_point->ContainingShadowRoot()); |
| if (!insertion_point->ContainingShadowRoot()->IsOpenOrV0()) |
| break; |
| filtered_insertion_points.push_back(insertion_point); |
| } |
| return StaticNodeList::Adopt(filtered_insertion_points); |
| } |
| |
| HTMLSlotElement* Node::AssignedSlot() const { |
| // assignedSlot doesn't need to recalc assignment. |
| DCHECK(!IsPseudoElement()); |
| ShadowRoot* root = V1ShadowRootOfParent(); |
| if (!root) |
| return nullptr; |
| if (!RuntimeEnabledFeatures::FastFlatTreeTraversalEnabled()) { |
| return root->AssignedSlotFor(*this); |
| } |
| |
| if (!root->HasSlotAssignment()) |
| return nullptr; |
| |
| // TODO(hayato): Node::AssignedSlot() shouldn't be called while |
| // in executing RecalcAssignment(), however, unfortunately, |
| // that could happen as follows: |
| // |
| // 1. RecalsAssignment() can detach a node |
| // 2. Then, DetachLayoutTree() may use FlatTreeTraversal via the hook of |
| // AXObjectCacheImpl::ChildrenChanged(). |
| // |
| // Note that using FlatTreeTraversal in detaching layout tree should be banned |
| // in the long term. |
| // |
| // If we can remove such code path, we don't need to check |
| // IsInSlotAssignmentRecalc() here. |
| if (root->NeedsSlotAssignmentRecalc() || |
| GetDocument().IsInSlotAssignmentRecalc()) { |
| // FlatTreeNodeData is not realiable here. Entering slow path. |
| return root->AssignedSlotFor(*this); |
| } |
| if (FlatTreeNodeData* data = GetFlatTreeNodeData()) { |
| DCHECK_EQ(root->AssignedSlotFor(*this), data->AssignedSlot()); |
| return data->AssignedSlot(); |
| } |
| return nullptr; |
| } |
| |
| HTMLSlotElement* Node::FinalDestinationSlot() const { |
| HTMLSlotElement* slot = AssignedSlot(); |
| if (!slot) |
| return nullptr; |
| for (HTMLSlotElement* next = slot->AssignedSlot(); next; |
| next = next->AssignedSlot()) { |
| slot = next; |
| } |
| return slot; |
| } |
| |
| HTMLSlotElement* Node::assignedSlotForBinding() { |
| // assignedSlot doesn't need to recalc slot assignment |
| if (ShadowRoot* root = V1ShadowRootOfParent()) { |
| if (root->GetType() == ShadowRootType::kOpen) |
| return AssignedSlot(); |
| } |
| return nullptr; |
| } |
| |
| void Node::SetFocused(bool flag, WebFocusType focus_type) { |
| if (!flag || focus_type == kWebFocusTypeMouse) |
| GetDocument(). |