/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Peter Kelly (pmk@post.com) * (C) 2001 Dirk Mueller (mueller@kde.org) * (C) 2007 David Smith (catfish.man@gmail.com) * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012, 2013 Apple Inc. * All rights reserved. * (C) 2007 Eric Seidel (eric@webkit.org) * * 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/element.h" #include #include "cc/input/snap_selection_strategy.h" #include "third_party/blink/public/platform/web_scroll_into_view_params.h" #include "third_party/blink/renderer/bindings/core/v8/dictionary.h" #include "third_party/blink/renderer/bindings/core/v8/scroll_into_view_options_or_boolean.h" #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_html.h" #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_html_or_trusted_script_or_trusted_script_url_or_trusted_url.h" #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_script.h" #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_script_url.h" #include "third_party/blink/renderer/bindings/core/v8/usv_string_or_trusted_url.h" #include "third_party/blink/renderer/bindings/core/v8/v8_display_lock_options.h" #include "third_party/blink/renderer/core/accessibility/ax_context.h" #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h" #include "third_party/blink/renderer/core/animation/css/css_animations.h" #include "third_party/blink/renderer/core/aom/computed_accessible_node.h" #include "third_party/blink/renderer/core/css/css_identifier_value.h" #include "third_party/blink/renderer/core/css/css_primitive_value.h" #include "third_party/blink/renderer/core/css/css_property_value_set.h" #include "third_party/blink/renderer/core/css/css_selector_watch.h" #include "third_party/blink/renderer/core/css/css_style_sheet.h" #include "third_party/blink/renderer/core/css/css_value.h" #include "third_party/blink/renderer/core/css/parser/css_parser.h" #include "third_party/blink/renderer/core/css/property_set_css_style_declaration.h" #include "third_party/blink/renderer/core/css/resolver/selector_filter_parent_scope.h" #include "third_party/blink/renderer/core/css/resolver/style_resolver.h" #include "third_party/blink/renderer/core/css/resolver/style_resolver_stats.h" #include "third_party/blink/renderer/core/css/selector_query.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/css_value_keywords.h" #include "third_party/blink/renderer/core/display_lock/before_activate_event.h" #include "third_party/blink/renderer/core/display_lock/display_lock_context.h" #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h" #include "third_party/blink/renderer/core/dom/attr.h" #include "third_party/blink/renderer/core/dom/dataset_dom_string_map.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/dom_token_list.h" #include "third_party/blink/renderer/core/dom/element_data_cache.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_dispatch_forbidden_scope.h" #include "third_party/blink/renderer/core/dom/events/event_dispatcher.h" #include "third_party/blink/renderer/core/dom/first_letter_pseudo_element.h" #include "third_party/blink/renderer/core/dom/layout_tree_builder.h" #include "third_party/blink/renderer/core/dom/mutation_observer_interest_group.h" #include "third_party/blink/renderer/core/dom/mutation_record.h" #include "third_party/blink/renderer/core/dom/named_node_map.h" #include "third_party/blink/renderer/core/dom/node_computed_style.h" #include "third_party/blink/renderer/core/dom/presentation_attribute_style.h" #include "third_party/blink/renderer/core/dom/pseudo_element.h" #include "third_party/blink/renderer/core/dom/scriptable_document_parser.h" #include "third_party/blink/renderer/core/dom/shadow_root.h" #include "third_party/blink/renderer/core/dom/shadow_root_init.h" #include "third_party/blink/renderer/core/dom/shadow_root_v0.h" #include "third_party/blink/renderer/core/dom/slot_assignment.h" #include "third_party/blink/renderer/core/dom/space_split_string.h" #include "third_party/blink/renderer/core/dom/text.h" #include "third_party/blink/renderer/core/dom/v0_insertion_point.h" #include "third_party/blink/renderer/core/dom/whitespace_attacher.h" #include "third_party/blink/renderer/core/editing/editing_utilities.h" #include "third_party/blink/renderer/core/editing/ephemeral_range.h" #include "third_party/blink/renderer/core/editing/frame_selection.h" #include "third_party/blink/renderer/core/editing/selection_template.h" #include "third_party/blink/renderer/core/editing/serializers/serialization.h" #include "third_party/blink/renderer/core/editing/set_selection_options.h" #include "third_party/blink/renderer/core/editing/visible_selection.h" #include "third_party/blink/renderer/core/events/focus_event.h" #include "third_party/blink/renderer/core/frame/csp/content_security_policy.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_view.h" #include "third_party/blink/renderer/core/frame/scroll_into_view_options.h" #include "third_party/blink/renderer/core/frame/scroll_to_options.h" #include "third_party/blink/renderer/core/frame/settings.h" #include "third_party/blink/renderer/core/frame/use_counter.h" #include "third_party/blink/renderer/core/frame/visual_viewport.h" #include "third_party/blink/renderer/core/fullscreen/fullscreen.h" #include "third_party/blink/renderer/core/geometry/dom_rect.h" #include "third_party/blink/renderer/core/geometry/dom_rect_list.h" #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h" #include "third_party/blink/renderer/core/html/custom/custom_element.h" #include "third_party/blink/renderer/core/html/custom/custom_element_registry.h" #include "third_party/blink/renderer/core/html/custom/v0_custom_element.h" #include "third_party/blink/renderer/core/html/custom/v0_custom_element_registration_context.h" #include "third_party/blink/renderer/core/html/forms/html_form_controls_collection.h" #include "third_party/blink/renderer/core/html/forms/html_options_collection.h" #include "third_party/blink/renderer/core/html/html_collection.h" #include "third_party/blink/renderer/core/html/html_document.h" #include "third_party/blink/renderer/core/html/html_element.h" #include "third_party/blink/renderer/core/html/html_frame_element_base.h" #include "third_party/blink/renderer/core/html/html_frame_owner_element.h" #include "third_party/blink/renderer/core/html/html_plugin_element.h" #include "third_party/blink/renderer/core/html/html_slot_element.h" #include "third_party/blink/renderer/core/html/html_table_rows_collection.h" #include "third_party/blink/renderer/core/html/html_template_element.h" #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" #include "third_party/blink/renderer/core/html/parser/nesting_level_incrementer.h" #include "third_party/blink/renderer/core/input/event_handler.h" #include "third_party/blink/renderer/core/intersection_observer/element_intersection_observer_data.h" #include "third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.h" #include "third_party/blink/renderer/core/invisible_dom/activate_invisible_event.h" #include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h" #include "third_party/blink/renderer/core/layout/layout_text_fragment.h" #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/page/chrome_client.h" #include "third_party/blink/renderer/core/page/focus_controller.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/page/pointer_lock_controller.h" #include "third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h" #include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h" #include "third_party/blink/renderer/core/page/spatial_navigation.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/probe/core_probes.h" #include "third_party/blink/renderer/core/resize_observer/resize_observation.h" #include "third_party/blink/renderer/core/scroll/scrollable_area.h" #include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h" #include "third_party/blink/renderer/core/svg/svg_a_element.h" #include "third_party/blink/renderer/core/svg/svg_element.h" #include "third_party/blink/renderer/core/svg_names.h" #include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h" #include "third_party/blink/renderer/core/xml_names.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/v8_dom_activity_logger.h" #include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h" #include "third_party/blink/renderer/platform/bindings/v8_per_context_data.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/wtf/bit_vector.h" #include "third_party/blink/renderer/platform/wtf/hash_functions.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/text/text_position.h" namespace blink { using namespace html_names; enum class ClassStringContent { kEmpty, kWhiteSpaceOnly, kHasClasses }; namespace { class DisplayLockStyleScope { public: DisplayLockStyleScope(DisplayLockContext* context) : context_(context), should_update_self_(!context || context->ShouldStyle(DisplayLockContext::kSelf)), should_update_children_( !context || context->ShouldStyle(DisplayLockContext::kChildren)) {} ~DisplayLockStyleScope() { if (!context_) return; if (did_update_children_) context_->DidStyle(DisplayLockContext::kChildren); // There is no other condition to block us form updating self, so if we // "should" update, then we "did" update. if (should_update_self_) context_->DidStyle(DisplayLockContext::kSelf); } bool ShouldUpdateSelfStyle() const { return should_update_self_; } bool ShouldUpdateChildStyle() const { return should_update_children_; } void DidUpdateChildStyle() { did_update_children_ = true; } void NotifyUpdateWasBlocked(DisplayLockContext::StyleType style) { DCHECK(!ShouldUpdateChildStyle()); context_->NotifyStyleRecalcWasBlocked(style); } private: UntracedMember context_; const bool should_update_self_; const bool should_update_children_; bool did_update_children_ = false; }; bool IsRootEditableElementWithCounting(const Element& element) { bool is_editable = IsRootEditableElement(element); Document& doc = element.GetDocument(); if (!doc.IsActive()) return is_editable; // -webkit-user-modify doesn't affect text control elements. if (element.IsTextControl()) return is_editable; const auto* style = element.GetComputedStyle(); if (!style) return is_editable; auto user_modify = style->UserModify(); const AtomicString& ce_value = element.FastGetAttribute(kContenteditableAttr); if (ce_value.IsNull() || DeprecatedEqualIgnoringCase(ce_value, "false")) { if (user_modify == EUserModify::kReadWritePlaintextOnly) { UseCounter::Count(doc, WebFeature::kPlainTextEditingEffective); UseCounter::Count(doc, WebFeature::kWebKitUserModifyPlainTextEffective); UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); } else if (user_modify == EUserModify::kReadWrite) { UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadWriteEffective); UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); } } else if (ce_value.IsEmpty() || DeprecatedEqualIgnoringCase(ce_value, "true")) { if (user_modify == EUserModify::kReadWritePlaintextOnly) { UseCounter::Count(doc, WebFeature::kPlainTextEditingEffective); UseCounter::Count(doc, WebFeature::kWebKitUserModifyPlainTextEffective); UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); } else if (user_modify == EUserModify::kReadOnly) { UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadOnlyEffective); UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); } } else if (DeprecatedEqualIgnoringCase(ce_value, "plaintext-only")) { UseCounter::Count(doc, WebFeature::kPlainTextEditingEffective); if (user_modify == EUserModify::kReadWrite) { UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadWriteEffective); UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); } else if (user_modify == EUserModify::kReadOnly) { UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadOnlyEffective); UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective); } } return is_editable; } // Return true if we're absolutely sure that this node is going to establish a // new formatting context. Whether or not it establishes a new formatting // context cannot be accurately determined until we have actually created the // object (see LayoutBlockFlow::CreatesNewFormattingContext()), so this function // may (and is allowed to) return false negatives, but NEVER false positives. bool DefinitelyNewFormattingContext(const Node& node, const ComputedStyle& style) { auto display = style.Display(); if (display == EDisplay::kInline || display == EDisplay::kContents) return false; // The only block-container display types that potentially don't establish a // new formatting context, are 'block' and 'list-item'. if (display != EDisplay::kBlock && display != EDisplay::kListItem) return true; if (!style.IsOverflowVisible()) return node.GetDocument().ViewportDefiningElement() != &node; if (style.HasOutOfFlowPosition() || style.IsFloating() || style.ContainsPaint() || style.ContainsLayout() || style.SpecifiesColumns()) return true; if (node.GetDocument().documentElement() == &node) return true; if (const Element* element = ToElementOrNull(&node)) { if (IsHTMLImageElement(element) || element->IsFormControlElement() || element->IsMediaElement() || element->IsFrameOwnerElement()) return true; } if (const Node* parent = LayoutTreeBuilderTraversal::LayoutParent(node)) return parent->ComputedStyleRef().IsDisplayFlexibleOrGridBox(); return false; } bool CalculateStyleShouldForceLegacyLayout(const Element& element, const ComputedStyle& style) { const Document& document = element.GetDocument(); // TODO(layout-dev): Once LayoutNG handles inline content editable, we // should get rid of following code fragment. if (!RuntimeEnabledFeatures::EditingNGEnabled()) { if (style.UserModify() != EUserModify::kReadOnly || document.InDesignMode()) return true; } if (style.Display() == EDisplay::kWebkitBox || style.Display() == EDisplay::kWebkitInlineBox) return true; if (!RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled()) { // Disable NG for the entire subtree if we're establishing a block // fragmentation context. if (style.SpecifiesColumns()) return true; if (document.Printing() && element == document.documentElement()) return true; } // The custom container is laid out by the legacy engine. Its children may // not establish new formatting contexts, so we need to protect against // re-entering LayoutNG there. if (style.Display() == EDisplay::kLayoutCustom || style.Display() == EDisplay::kInlineLayoutCustom) return true; // 'text-combine-upright' property is not supported yet. if (style.HasTextCombine() && !style.IsHorizontalWritingMode()) return true; return false; } } // namespace Element* Element::Create(const QualifiedName& tag_name, Document* document) { return MakeGarbageCollected(tag_name, document, kCreateElement); } Element::Element(const QualifiedName& tag_name, Document* document, ConstructionType type) : ContainerNode(document, type), tag_name_(tag_name) {} inline ElementRareData* Element::GetElementRareData() const { DCHECK(HasRareData()); return static_cast(RareData()); } inline ElementRareData& Element::EnsureElementRareData() { return static_cast(EnsureRareData()); } bool Element::HasElementFlagInternal(ElementFlags mask) const { return GetElementRareData()->HasElementFlag(mask); } void Element::SetElementFlag(ElementFlags mask, bool value) { if (!HasRareData() && !value) return; EnsureElementRareData().SetElementFlag(mask, value); } void Element::ClearElementFlag(ElementFlags mask) { if (!HasRareData()) return; GetElementRareData()->ClearElementFlag(mask); } void Element::ClearTabIndexExplicitlyIfNeeded() { if (HasRareData()) GetElementRareData()->ClearTabIndexExplicitly(); } void Element::SetTabIndexExplicitly() { EnsureElementRareData().SetTabIndexExplicitly(); } void Element::setTabIndex(int value) { SetIntegralAttribute(kTabindexAttr, value); } int Element::tabIndex() const { return HasElementFlag(ElementFlags::kTabIndexWasSetExplicitly) ? GetIntegralAttribute(kTabindexAttr) : 0; } bool Element::IsFocusableStyle() const { // Elements in canvas fallback content are not rendered, but they are allowed // to be focusable as long as their canvas is displayed and visible. if (IsInCanvasSubtree()) { const HTMLCanvasElement* canvas = Traversal::FirstAncestorOrSelf(*this); DCHECK(canvas); return canvas->GetLayoutObject() && canvas->GetLayoutObject()->Style()->Visibility() == EVisibility::kVisible; } if (IsInsideInvisibleSubtree()) { const ComputedStyle* style = const_cast(this)->EnsureComputedStyle(); return style->Visibility() == EVisibility::kVisible && style->Display() != EDisplay::kNone; } // FIXME: Even if we are not visible, we might have a child that is visible. // Hyatt wants to fix that some day with a "has visible content" flag or the // like. return GetLayoutObject() && GetLayoutObject()->Style()->Visibility() == EVisibility::kVisible; } Node* Element::Clone(Document& factory, CloneChildrenFlag flag) const { return flag == CloneChildrenFlag::kClone ? &CloneWithChildren(&factory) : &CloneWithoutChildren(&factory); } Element& Element::CloneWithChildren(Document* nullable_factory) const { Element& clone = CloneWithoutAttributesAndChildren( nullable_factory ? *nullable_factory : GetDocument()); // This will catch HTML elements in the wrong namespace that are not correctly // copied. This is a sanity check as HTML overloads some of the DOM methods. DCHECK_EQ(IsHTMLElement(), clone.IsHTMLElement()); clone.CloneAttributesFrom(*this); clone.CloneNonAttributePropertiesFrom(*this, CloneChildrenFlag::kClone); clone.CloneChildNodesFrom(*this); return clone; } Element& Element::CloneWithoutChildren(Document* nullable_factory) const { Element& clone = CloneWithoutAttributesAndChildren( nullable_factory ? *nullable_factory : GetDocument()); // This will catch HTML elements in the wrong namespace that are not correctly // copied. This is a sanity check as HTML overloads some of the DOM methods. DCHECK_EQ(IsHTMLElement(), clone.IsHTMLElement()); clone.CloneAttributesFrom(*this); clone.CloneNonAttributePropertiesFrom(*this, CloneChildrenFlag::kSkip); return clone; } Element& Element::CloneWithoutAttributesAndChildren(Document& factory) const { return *factory.CreateElement(TagQName(), CreateElementFlags::ByCloneNode(), IsValue()); } Attr* Element::DetachAttribute(wtf_size_t index) { DCHECK(GetElementData()); const Attribute& attribute = GetElementData()->Attributes().at(index); Attr* attr_node = AttrIfExists(attribute.GetName()); if (attr_node) { DetachAttrNodeAtIndex(attr_node, index); } else { attr_node = MakeGarbageCollected(GetDocument(), attribute.GetName(), attribute.Value()); RemoveAttributeInternal(index, kNotInSynchronizationOfLazyAttribute); } return attr_node; } void Element::DetachAttrNodeAtIndex(Attr* attr, wtf_size_t index) { DCHECK(attr); DCHECK(GetElementData()); const Attribute& attribute = GetElementData()->Attributes().at(index); DCHECK(attribute.GetName() == attr->GetQualifiedName()); DetachAttrNodeFromElementWithValue(attr, attribute.Value()); RemoveAttributeInternal(index, kNotInSynchronizationOfLazyAttribute); } void Element::removeAttribute(const QualifiedName& name) { if (!GetElementData()) return; wtf_size_t index = GetElementData()->Attributes().FindIndex(name); if (index == kNotFound) return; RemoveAttributeInternal(index, kNotInSynchronizationOfLazyAttribute); } void Element::SetBooleanAttribute(const QualifiedName& name, bool value) { if (value) setAttribute(name, g_empty_atom); else removeAttribute(name); } NamedNodeMap* Element::attributesForBindings() const { ElementRareData& rare_data = const_cast(this)->EnsureElementRareData(); if (NamedNodeMap* attribute_map = rare_data.AttributeMap()) return attribute_map; rare_data.SetAttributeMap( MakeGarbageCollected(const_cast(this))); return rare_data.AttributeMap(); } Vector Element::getAttributeNames() const { Vector attributesVector; if (!hasAttributes()) return attributesVector; AttributeCollection attributes = element_data_->Attributes(); attributesVector.ReserveInitialCapacity(attributes.size()); for (const Attribute& attr : attributes) attributesVector.UncheckedAppend(attr.GetName().ToString()); return attributesVector; } ElementAnimations* Element::GetElementAnimations() const { if (HasRareData()) return GetElementRareData()->GetElementAnimations(); return nullptr; } ElementAnimations& Element::EnsureElementAnimations() { ElementRareData& rare_data = EnsureElementRareData(); if (!rare_data.GetElementAnimations()) rare_data.SetElementAnimations(MakeGarbageCollected()); return *rare_data.GetElementAnimations(); } bool Element::HasAnimations() const { if (!HasRareData()) return false; ElementAnimations* element_animations = GetElementRareData()->GetElementAnimations(); return element_animations && !element_animations->IsEmpty(); } Node::NodeType Element::getNodeType() const { return kElementNode; } bool Element::hasAttribute(const QualifiedName& name) const { return hasAttributeNS(name.NamespaceURI(), name.LocalName()); } bool Element::HasAttributeIgnoringNamespace( const AtomicString& local_name) const { if (!GetElementData()) return false; SynchronizeAttribute(local_name); AtomicString name = LowercaseIfNecessary(local_name); for (const Attribute& attribute : GetElementData()->Attributes()) { if (attribute.LocalName() == name) return true; } return false; } void Element::SynchronizeAllAttributes() const { if (!GetElementData()) return; // NOTE: AnyAttributeMatches in selector_checker.cc currently assumes that all // lazy attributes have a null namespace. If that ever changes we'll need to // fix that code. if (GetElementData()->style_attribute_is_dirty_) { DCHECK(IsStyledElement()); SynchronizeStyleAttributeInternal(); } if (GetElementData()->animated_svg_attributes_are_dirty_) ToSVGElement(this)->SynchronizeAnimatedSVGAttribute(AnyQName()); } inline void Element::SynchronizeAttribute(const QualifiedName& name) const { if (!GetElementData()) return; if (UNLIKELY(name == kStyleAttr && GetElementData()->style_attribute_is_dirty_)) { DCHECK(IsStyledElement()); SynchronizeStyleAttributeInternal(); return; } if (UNLIKELY(GetElementData()->animated_svg_attributes_are_dirty_)) { // See comment in the AtomicString version of SynchronizeAttribute() // also. ToSVGElement(this)->SynchronizeAnimatedSVGAttribute(name); } } void Element::SynchronizeAttribute(const AtomicString& local_name) const { // This version of synchronizeAttribute() is streamlined for the case where // you don't have a full QualifiedName, e.g when called from DOM API. if (!GetElementData()) return; if (GetElementData()->style_attribute_is_dirty_ && LowercaseIfNecessary(local_name) == kStyleAttr.LocalName()) { DCHECK(IsStyledElement()); SynchronizeStyleAttributeInternal(); return; } if (GetElementData()->animated_svg_attributes_are_dirty_) { // We're not passing a namespace argument on purpose. SVGNames::*Attr are // defined w/o namespaces as well. // FIXME: this code is called regardless of whether name is an // animated SVG Attribute. It would seem we should only call this method // if SVGElement::isAnimatableAttribute is true, but the list of // animatable attributes in isAnimatableAttribute does not suffice to // pass all web tests. Also, animated_svg_attributes_are_dirty_ stays // dirty unless SynchronizeAnimatedSVGAttribute is called with // AnyQName(). This means that even if Element::SynchronizeAttribute() // is called on all attributes, animated_svg_attributes_are_dirty_ remains // true. ToSVGElement(this)->SynchronizeAnimatedSVGAttribute( QualifiedName(g_null_atom, local_name, g_null_atom)); } } const AtomicString& Element::getAttribute(const QualifiedName& name) const { if (!GetElementData()) return g_null_atom; SynchronizeAttribute(name); if (const Attribute* attribute = GetElementData()->Attributes().Find(name)) return attribute->Value(); return g_null_atom; } AtomicString Element::LowercaseIfNecessary(const AtomicString& name) const { return IsHTMLElement() && GetDocument().IsHTMLDocument() ? name.LowerASCII() : name; } const AtomicString& Element::nonce() const { return HasRareData() ? GetElementRareData()->GetNonce() : g_null_atom; } void Element::setNonce(const AtomicString& nonce) { EnsureElementRareData().SetNonce(nonce); } void Element::scrollIntoView(ScrollIntoViewOptionsOrBoolean arg) { ScrollIntoViewOptions* options = ScrollIntoViewOptions::Create(); if (arg.IsBoolean()) { if (arg.GetAsBoolean()) options->setBlock("start"); else options->setBlock("end"); options->setInlinePosition("nearest"); } else if (arg.IsScrollIntoViewOptions()) { options = arg.GetAsScrollIntoViewOptions(); } scrollIntoViewWithOptions(options); } void Element::scrollIntoView(bool align_to_top) { ScrollIntoViewOptionsOrBoolean arg; arg.SetBoolean(align_to_top); scrollIntoView(arg); } static ScrollAlignment ToPhysicalAlignment(const ScrollIntoViewOptions* options, ScrollOrientation axis, bool is_horizontal_writing_mode, bool is_flipped_blocks_mode) { String alignment = ((axis == kHorizontalScroll && is_horizontal_writing_mode) || (axis == kVerticalScroll && !is_horizontal_writing_mode)) ? options->inlinePosition() : options->block(); if (alignment == "center") return ScrollAlignment::kAlignCenterAlways; if (alignment == "nearest") return ScrollAlignment::kAlignToEdgeIfNeeded; if (alignment == "start") { return (axis == kHorizontalScroll) ? is_flipped_blocks_mode ? ScrollAlignment::kAlignRightAlways : ScrollAlignment::kAlignLeftAlways : ScrollAlignment::kAlignTopAlways; } if (alignment == "end") { return (axis == kHorizontalScroll) ? is_flipped_blocks_mode ? ScrollAlignment::kAlignLeftAlways : ScrollAlignment::kAlignRightAlways : ScrollAlignment::kAlignBottomAlways; } // Default values if (is_horizontal_writing_mode) { return (axis == kHorizontalScroll) ? ScrollAlignment::kAlignToEdgeIfNeeded : ScrollAlignment::kAlignTopAlways; } return (axis == kHorizontalScroll) ? ScrollAlignment::kAlignLeftAlways : ScrollAlignment::kAlignToEdgeIfNeeded; } void Element::scrollIntoViewWithOptions(const ScrollIntoViewOptions* options) { ActivateDisplayLockIfNeeded(); GetDocument().EnsurePaintLocationDataValidForNode(this); ScrollIntoViewNoVisualUpdate(options); } void Element::ScrollIntoViewNoVisualUpdate( const ScrollIntoViewOptions* options) { if (!GetLayoutObject() || !GetDocument().GetPage()) return; if (DisplayLockPreventsActivation()) return; ScrollBehavior behavior = (options->behavior() == "smooth") ? kScrollBehaviorSmooth : kScrollBehaviorAuto; bool is_horizontal_writing_mode = GetComputedStyle()->IsHorizontalWritingMode(); bool is_flipped_blocks_mode = GetComputedStyle()->IsFlippedBlocksWritingMode(); ScrollAlignment align_x = ToPhysicalAlignment(options, kHorizontalScroll, is_horizontal_writing_mode, is_flipped_blocks_mode); ScrollAlignment align_y = ToPhysicalAlignment(options, kVerticalScroll, is_horizontal_writing_mode, is_flipped_blocks_mode); LayoutRect bounds = BoundingBoxForScrollIntoView(); GetLayoutObject()->ScrollRectToVisible( bounds, {align_x, align_y, kProgrammaticScroll, /*make_visible_in_visual_viewport=*/true, behavior}); GetDocument().SetSequentialFocusNavigationStartingPoint(this); } void Element::scrollIntoViewIfNeeded(bool center_if_needed) { GetDocument().EnsurePaintLocationDataValidForNode(this); if (!GetLayoutObject()) return; LayoutRect bounds = BoundingBoxForScrollIntoView(); if (center_if_needed) { GetLayoutObject()->ScrollRectToVisible( bounds, {ScrollAlignment::kAlignCenterIfNeeded, ScrollAlignment::kAlignCenterIfNeeded}); } else { GetLayoutObject()->ScrollRectToVisible( bounds, {ScrollAlignment::kAlignToEdgeIfNeeded, ScrollAlignment::kAlignToEdgeIfNeeded}); } } int Element::OffsetLeft() { GetDocument().EnsurePaintLocationDataValidForNode(this); if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject()) return AdjustForAbsoluteZoom::AdjustLayoutUnit( LayoutUnit( layout_object->PixelSnappedOffsetLeft(OffsetParent())), layout_object->StyleRef()) .Round(); return 0; } int Element::OffsetTop() { GetDocument().EnsurePaintLocationDataValidForNode(this); if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject()) return AdjustForAbsoluteZoom::AdjustLayoutUnit( LayoutUnit(layout_object->PixelSnappedOffsetTop(OffsetParent())), layout_object->StyleRef()) .Round(); return 0; } int Element::OffsetWidth() { GetDocument().EnsurePaintLocationDataValidForNode(this); if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject()) return AdjustForAbsoluteZoom::AdjustLayoutUnit( LayoutUnit( layout_object->PixelSnappedOffsetWidth(OffsetParent())), layout_object->StyleRef()) .Round(); return 0; } int Element::OffsetHeight() { GetDocument().EnsurePaintLocationDataValidForNode(this); if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject()) return AdjustForAbsoluteZoom::AdjustLayoutUnit( LayoutUnit( layout_object->PixelSnappedOffsetHeight(OffsetParent())), layout_object->StyleRef()) .Round(); return 0; } Element* Element::OffsetParent() { GetDocument().UpdateStyleAndLayoutForNode(this); LayoutObject* layout_object = GetLayoutObject(); return layout_object ? layout_object->OffsetParent() : nullptr; } int Element::clientLeft() { GetDocument().UpdateStyleAndLayoutForNode(this); if (LayoutBox* layout_object = GetLayoutBox()) return AdjustForAbsoluteZoom::AdjustLayoutUnit(layout_object->ClientLeft(), layout_object->StyleRef()) .Round(); return 0; } int Element::clientTop() { GetDocument().UpdateStyleAndLayoutForNode(this); if (LayoutBox* layout_object = GetLayoutBox()) return AdjustForAbsoluteZoom::AdjustLayoutUnit(layout_object->ClientTop(), layout_object->StyleRef()) .Round(); return 0; } int Element::clientWidth() { // When in strict mode, clientWidth for the document element should return the // width of the containing frame. // When in quirks mode, clientWidth for the body element should return the // width of the containing frame. bool in_quirks_mode = GetDocument().InQuirksMode(); if ((!in_quirks_mode && GetDocument().documentElement() == this) || (in_quirks_mode && IsHTMLElement() && GetDocument().body() == this)) { auto* layout_view = GetDocument().GetLayoutView(); if (layout_view) { if (!RuntimeEnabledFeatures::OverlayScrollbarsEnabled() || !GetDocument().GetFrame()->IsLocalRoot()) GetDocument().UpdateStyleAndLayoutForNode(this); if (GetDocument().GetPage()->GetSettings().GetForceZeroLayoutHeight()) return AdjustForAbsoluteZoom::AdjustLayoutUnit( layout_view->OverflowClipRect(LayoutPoint()).Width(), layout_view->StyleRef()) .Round(); return AdjustForAbsoluteZoom::AdjustLayoutUnit( LayoutUnit(layout_view->GetLayoutSize().Width()), layout_view->StyleRef()) .Round(); } } GetDocument().UpdateStyleAndLayoutForNode(this); if (LayoutBox* layout_object = GetLayoutBox()) return AdjustForAbsoluteZoom::AdjustLayoutUnit( LayoutUnit(layout_object->PixelSnappedClientWidth()), layout_object->StyleRef()) .Round(); return 0; } int Element::clientHeight() { // When in strict mode, clientHeight for the document element should return // the height of the containing frame. // When in quirks mode, clientHeight for the body element should return the // height of the containing frame. bool in_quirks_mode = GetDocument().InQuirksMode(); if ((!in_quirks_mode && GetDocument().documentElement() == this) || (in_quirks_mode && IsHTMLElement() && GetDocument().body() == this)) { auto* layout_view = GetDocument().GetLayoutView(); if (layout_view) { if (!RuntimeEnabledFeatures::OverlayScrollbarsEnabled() || !GetDocument().GetFrame()->IsLocalRoot()) GetDocument().UpdateStyleAndLayoutForNode(this); if (GetDocument().GetPage()->GetSettings().GetForceZeroLayoutHeight()) return AdjustForAbsoluteZoom::AdjustLayoutUnit( layout_view->OverflowClipRect(LayoutPoint()).Height(), layout_view->StyleRef()) .Round(); return AdjustForAbsoluteZoom::AdjustLayoutUnit( LayoutUnit(layout_view->GetLayoutSize().Height()), layout_view->StyleRef()) .Round(); } } GetDocument().UpdateStyleAndLayoutForNode(this); if (LayoutBox* layout_object = GetLayoutBox()) return AdjustForAbsoluteZoom::AdjustLayoutUnit( LayoutUnit(layout_object->PixelSnappedClientHeight()), layout_object->StyleRef()) .Round(); return 0; } double Element::scrollLeft() { if (!InActiveDocument()) return 0; GetDocument().UpdateStyleAndLayoutForNode(this); if (GetDocument().ScrollingElementNoLayout() == this) { if (GetDocument().domWindow()) return GetDocument().domWindow()->scrollX(); return 0; } if (LayoutBox* box = GetLayoutBox()) { return AdjustForAbsoluteZoom::AdjustScroll(box->ScrollLeft(), *box); } return 0; } double Element::scrollTop() { if (!InActiveDocument()) return 0; GetDocument().UpdateStyleAndLayoutForNode(this); if (GetDocument().ScrollingElementNoLayout() == this) { if (GetDocument().domWindow()) return GetDocument().domWindow()->scrollY(); return 0; } if (LayoutBox* box = GetLayoutBox()) { return AdjustForAbsoluteZoom::AdjustScroll(box->ScrollTop(), *box); } return 0; } void Element::setScrollLeft(double new_left) { if (!InActiveDocument()) return; GetDocument().UpdateStyleAndLayoutForNode(this); new_left = ScrollableArea::NormalizeNonFiniteScroll(new_left); if (GetDocument().ScrollingElementNoLayout() == this) { if (LocalDOMWindow* window = GetDocument().domWindow()) { ScrollToOptions* options = ScrollToOptions::Create(); options->setLeft(new_left); window->scrollTo(options); } } else { LayoutBox* box = GetLayoutBox(); if (!box) return; FloatPoint end_point(new_left * box->Style()->EffectiveZoom(), box->ScrollTop().ToFloat()); std::unique_ptr strategy = cc::SnapSelectionStrategy::CreateForEndPosition( gfx::ScrollOffset(end_point), true, false); end_point = GetDocument() .GetSnapCoordinator() ->GetSnapPosition(*box, *strategy) .value_or(end_point); box->SetScrollLeft(LayoutUnit::FromFloatRound(end_point.X())); } } void Element::setScrollTop(double new_top) { if (!InActiveDocument()) return; GetDocument().UpdateStyleAndLayoutForNode(this); new_top = ScrollableArea::NormalizeNonFiniteScroll(new_top); if (GetDocument().ScrollingElementNoLayout() == this) { if (LocalDOMWindow* window = GetDocument().domWindow()) { ScrollToOptions* options = ScrollToOptions::Create(); options->setTop(new_top); window->scrollTo(options); } } else { LayoutBox* box = GetLayoutBox(); if (!box) return; FloatPoint end_point(box->ScrollLeft().ToFloat(), new_top * box->Style()->EffectiveZoom()); std::unique_ptr strategy = cc::SnapSelectionStrategy::CreateForEndPosition( gfx::ScrollOffset(end_point), false, true); end_point = GetDocument() .GetSnapCoordinator() ->GetSnapPosition(*box, *strategy) .value_or(end_point); box->SetScrollTop(LayoutUnit::FromFloatRound(end_point.Y())); } } int Element::scrollWidth() { if (!InActiveDocument()) return 0; GetDocument().UpdateStyleAndLayoutForNode(this); if (GetDocument().ScrollingElementNoLayout() == this) { if (GetDocument().View()) { return AdjustForAbsoluteZoom::AdjustInt( GetDocument().View()->LayoutViewport()->ContentsSize().Width(), GetDocument().GetFrame()->PageZoomFactor()); } return 0; } if (LayoutBox* box = GetLayoutBox()) { return AdjustForAbsoluteZoom::AdjustInt(box->PixelSnappedScrollWidth(), box); } return 0; } int Element::scrollHeight() { if (!InActiveDocument()) return 0; GetDocument().UpdateStyleAndLayoutForNode(this); if (GetDocument().ScrollingElementNoLayout() == this) { if (GetDocument().View()) { return AdjustForAbsoluteZoom::AdjustInt( GetDocument().View()->LayoutViewport()->ContentsSize().Height(), GetDocument().GetFrame()->PageZoomFactor()); } return 0; } if (LayoutBox* box = GetLayoutBox()) { return AdjustForAbsoluteZoom::AdjustInt(box->PixelSnappedScrollHeight(), box); } return 0; } void Element::scrollBy(double x, double y) { ScrollToOptions* scroll_to_options = ScrollToOptions::Create(); scroll_to_options->setLeft(x); scroll_to_options->setTop(y); scrollBy(scroll_to_options); } void Element::scrollBy(const ScrollToOptions* scroll_to_options) { if (!InActiveDocument()) return; // FIXME: This should be removed once scroll updates are processed only after // the compositing update. See http://crbug.com/420741. GetDocument().UpdateStyleAndLayoutForNode(this); if (GetDocument().ScrollingElementNoLayout() == this) { ScrollFrameBy(scroll_to_options); } else { ScrollLayoutBoxBy(scroll_to_options); } } void Element::scrollTo(double x, double y) { ScrollToOptions* scroll_to_options = ScrollToOptions::Create(); scroll_to_options->setLeft(x); scroll_to_options->setTop(y); scrollTo(scroll_to_options); } void Element::scrollTo(const ScrollToOptions* scroll_to_options) { if (!InActiveDocument()) return; // FIXME: This should be removed once scroll updates are processed only after // the compositing update. See http://crbug.com/420741. GetDocument().UpdateStyleAndLayoutForNode(this); if (GetDocument().ScrollingElementNoLayout() == this) { ScrollFrameTo(scroll_to_options); } else { ScrollLayoutBoxTo(scroll_to_options); } } void Element::ScrollLayoutBoxBy(const ScrollToOptions* scroll_to_options) { gfx::ScrollOffset displacement; if (scroll_to_options->hasLeft()) { displacement.set_x( ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->left())); } if (scroll_to_options->hasTop()) { displacement.set_y( ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->top())); } ScrollBehavior scroll_behavior = kScrollBehaviorAuto; ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(), scroll_behavior); LayoutBox* box = GetLayoutBox(); if (box) { gfx::ScrollOffset current_position(box->ScrollLeft().ToFloat(), box->ScrollTop().ToFloat()); displacement.Scale(box->Style()->EffectiveZoom()); gfx::ScrollOffset new_offset(current_position + displacement); FloatPoint new_position(new_offset.x(), new_offset.y()); std::unique_ptr strategy = cc::SnapSelectionStrategy::CreateForEndAndDirection(current_position, displacement); new_position = GetDocument() .GetSnapCoordinator() ->GetSnapPosition(*box, *strategy) .value_or(new_position); box->ScrollToPosition(new_position, scroll_behavior); } } void Element::ScrollLayoutBoxTo(const ScrollToOptions* scroll_to_options) { ScrollBehavior scroll_behavior = kScrollBehaviorAuto; ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(), scroll_behavior); LayoutBox* box = GetLayoutBox(); if (box) { FloatPoint new_position(box->ScrollLeft().ToFloat(), box->ScrollTop().ToFloat()); if (scroll_to_options->hasLeft()) { new_position.SetX( ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->left()) * box->Style()->EffectiveZoom()); } if (scroll_to_options->hasTop()) { new_position.SetY( ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->top()) * box->Style()->EffectiveZoom()); } std::unique_ptr strategy = cc::SnapSelectionStrategy::CreateForEndPosition( gfx::ScrollOffset(new_position), scroll_to_options->hasLeft(), scroll_to_options->hasTop()); new_position = GetDocument() .GetSnapCoordinator() ->GetSnapPosition(*box, *strategy) .value_or(new_position); box->ScrollToPosition(new_position, scroll_behavior); } } void Element::ScrollFrameBy(const ScrollToOptions* scroll_to_options) { gfx::ScrollOffset displacement; if (scroll_to_options->hasLeft()) { displacement.set_x( ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->left())); } if (scroll_to_options->hasTop()) { displacement.set_y( ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->top())); } ScrollBehavior scroll_behavior = kScrollBehaviorAuto; ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(), scroll_behavior); LocalFrame* frame = GetDocument().GetFrame(); if (!frame || !frame->View() || !GetDocument().GetPage()) return; ScrollableArea* viewport = frame->View()->LayoutViewport(); if (!viewport) return; displacement.Scale(frame->PageZoomFactor()); FloatPoint new_position = viewport->ScrollPosition() + FloatPoint(displacement.x(), displacement.y()); gfx::ScrollOffset current_position(viewport->ScrollPosition()); std::unique_ptr strategy = cc::SnapSelectionStrategy::CreateForEndAndDirection(current_position, displacement); new_position = GetDocument() .GetSnapCoordinator() ->GetSnapPosition(*GetDocument().GetLayoutView(), *strategy) .value_or(new_position); viewport->SetScrollOffset(viewport->ScrollPositionToOffset(new_position), kProgrammaticScroll, scroll_behavior); } void Element::ScrollFrameTo(const ScrollToOptions* scroll_to_options) { ScrollBehavior scroll_behavior = kScrollBehaviorAuto; ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(), scroll_behavior); LocalFrame* frame = GetDocument().GetFrame(); if (!frame || !frame->View() || !GetDocument().GetPage()) return; ScrollableArea* viewport = frame->View()->LayoutViewport(); if (!viewport) return; ScrollOffset new_offset = viewport->GetScrollOffset(); if (scroll_to_options->hasLeft()) { new_offset.SetWidth( ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->left()) * frame->PageZoomFactor()); } if (scroll_to_options->hasTop()) { new_offset.SetHeight( ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->top()) * frame->PageZoomFactor()); } FloatPoint new_position = viewport->ScrollOffsetToPosition(new_offset); std::unique_ptr strategy = cc::SnapSelectionStrategy::CreateForEndPosition( gfx::ScrollOffset(new_position), scroll_to_options->hasLeft(), scroll_to_options->hasTop()); new_position = GetDocument() .GetSnapCoordinator() ->GetSnapPosition(*GetDocument().GetLayoutView(), *strategy) .value_or(new_position); new_offset = viewport->ScrollPositionToOffset(new_position); viewport->SetScrollOffset(new_offset, kProgrammaticScroll, scroll_behavior); } IntRect Element::BoundsInViewport() const { GetDocument().EnsurePaintLocationDataValidForNode(this); LocalFrameView* view = GetDocument().View(); if (!view) return IntRect(); Vector quads; // TODO(pdr): Unify the quad/bounds code with Element::ClientQuads. // Foreign objects need to convert between SVG and HTML coordinate spaces and // cannot use LocalToAbsoluteQuad directly with ObjectBoundingBox which is // SVG coordinates and not HTML coordinates. Instead, use the AbsoluteQuads // codepath below. if (IsSVGElement() && GetLayoutObject() && !GetLayoutObject()->IsSVGForeignObject()) { // Get the bounding rectangle from the SVG model. // TODO(pdr): This should include stroke. if (ToSVGElement(this)->IsSVGGraphicsElement()) quads.push_back(GetLayoutObject()->LocalToAbsoluteQuad( GetLayoutObject()->ObjectBoundingBox())); } else { // Get the bounding rectangle from the box model. if (GetLayoutBoxModelObject()) GetLayoutBoxModelObject()->AbsoluteQuads(quads); } if (quads.IsEmpty()) return IntRect(); IntRect result = quads[0].EnclosingBoundingBox(); for (wtf_size_t i = 1; i < quads.size(); ++i) result.Unite(quads[i].EnclosingBoundingBox()); return view->FrameToViewport(result); } IntRect Element::VisibleBoundsInVisualViewport() const { if (!GetLayoutObject() || !GetDocument().GetPage() || !GetDocument().GetFrame()) return IntRect(); // We don't use absoluteBoundingBoxRect() because it can return an IntRect // larger the actual size by 1px. crbug.com/470503 LayoutRect rect( RoundedIntRect(GetLayoutObject()->AbsoluteBoundingBoxFloatRect())); LayoutRect frame_clip_rect = GetDocument().View()->GetLayoutView()->ClippingRect(LayoutPoint()); rect.Intersect(frame_clip_rect); // MapToVisualRectInAncestorSpace, called with a null ancestor argument, // returns the viewport-visible rect in the root frame's coordinate space. // MapToVisualRectInAncestorSpace applies ancestors' frame's clipping but does // not apply (overflow) element clipping. GetDocument().View()->GetLayoutView()->MapToVisualRectInAncestorSpace( nullptr, rect, kUseTransforms | kTraverseDocumentBoundaries, kDefaultVisualRectFlags); IntRect visible_rect = PixelSnappedIntRect(rect); // If the rect is in the coordinates of the main frame, then it should // also be clipped to the viewport to account for page scale. For OOPIFs, // local frame root -> viewport coordinate conversion is done in the // browser process. if (GetDocument().GetFrame()->LocalFrameRoot().IsMainFrame()) { IntSize viewport_size = GetDocument().GetPage()->GetVisualViewport().Size(); visible_rect = GetDocument().GetPage()->GetVisualViewport().RootFrameToViewport( visible_rect); visible_rect.Intersect(IntRect(IntPoint(), viewport_size)); } return visible_rect; } void Element::ClientQuads(Vector& quads) { GetDocument().EnsurePaintLocationDataValidForNode(this); LayoutObject* element_layout_object = GetLayoutObject(); if (!element_layout_object) return; // Foreign objects need to convert between SVG and HTML coordinate spaces and // cannot use LocalToAbsoluteQuad directly with ObjectBoundingBox which is // SVG coordinates and not HTML coordinates. Instead, use the AbsoluteQuads // codepath below. if (IsSVGElement() && !element_layout_object->IsSVGRoot() && !element_layout_object->IsSVGForeignObject()) { // Get the bounding rectangle from the SVG model. // TODO(pdr): ObjectBoundingBox does not include stroke and the spec is not // clear (see: https://github.com/w3c/svgwg/issues/339, crbug.com/529734). // If stroke is desired, we can update this to use AbsoluteQuads, below. if (ToSVGElement(this)->IsSVGGraphicsElement()) quads.push_back(element_layout_object->LocalToAbsoluteQuad( element_layout_object->ObjectBoundingBox())); return; } // FIXME: Handle table/inline-table with a caption. if (element_layout_object->IsBoxModelObject() || element_layout_object->IsBR()) element_layout_object->AbsoluteQuads(quads, kUseTransforms); } DOMRectList* Element::getClientRects() { Vector quads; ClientQuads(quads); if (quads.IsEmpty()) return DOMRectList::Create(); LayoutObject* element_layout_object = GetLayoutObject(); DCHECK(element_layout_object); GetDocument().AdjustFloatQuadsForScrollAndAbsoluteZoom( quads, *element_layout_object); return DOMRectList::Create(quads); } DOMRect* Element::getBoundingClientRect() { Vector quads; ClientQuads(quads); if (quads.IsEmpty()) return DOMRect::Create(); FloatRect result = quads[0].BoundingBox(); for (wtf_size_t i = 1; i < quads.size(); ++i) result.Unite(quads[i].BoundingBox()); LayoutObject* element_layout_object = GetLayoutObject(); DCHECK(element_layout_object); GetDocument().AdjustFloatRectForScrollAndAbsoluteZoom(result, *element_layout_object); return DOMRect::FromFloatRect(result); } const AtomicString& Element::computedRole() { Document& document = GetDocument(); if (!document.IsActive()) return g_null_atom; document.UpdateStyleAndLayoutForNode(this); AXContext ax_context(document); return ax_context.GetAXObjectCache().ComputedRoleForNode(this); } String Element::computedName() { Document& document = GetDocument(); if (!document.IsActive()) return String(); document.UpdateStyleAndLayoutForNode(this); AXContext ax_context(document); return ax_context.GetAXObjectCache().ComputedNameForNode(this); } AccessibleNode* Element::ExistingAccessibleNode() const { if (!RuntimeEnabledFeatures::AccessibilityObjectModelEnabled()) return nullptr; if (!HasRareData()) return nullptr; return GetElementRareData()->GetAccessibleNode(); } AccessibleNode* Element::accessibleNode() { if (!RuntimeEnabledFeatures::AccessibilityObjectModelEnabled()) return nullptr; ElementRareData& rare_data = EnsureElementRareData(); return rare_data.EnsureAccessibleNode(this); } InvisibleState Element::Invisible() const { const AtomicString& value = FastGetAttribute(kInvisibleAttr); if (value.IsNull()) return InvisibleState::kMissing; if (EqualIgnoringASCIICase(value, "static")) return InvisibleState::kStatic; return InvisibleState::kInvisible; } bool Element::HasInvisibleAttribute() const { return Invisible() != InvisibleState::kMissing; } void Element::DispatchActivateInvisibleEventIfNeeded() { if (!RuntimeEnabledFeatures::InvisibleDOMEnabled()) return; // Traverse all inclusive flat-tree ancestor and send activateinvisible // on the ones that have the invisible attribute. Default event handler // will remove invisible attribute of all invisible element if the event is // not canceled, making this element and all ancestors visible again. // We're saving them and the retargeted activated element as DOM structure // may change due to event handlers. HeapVector> invisible_ancestors; HeapVector> activated_elements; for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*this)) { if (ancestor.IsElementNode() && ToElement(ancestor).Invisible() != InvisibleState::kMissing) { invisible_ancestors.push_back(ToElement(ancestor)); activated_elements.push_back(ancestor.GetTreeScope().Retarget(*this)); } } auto* activated_element_iterator = activated_elements.begin(); for (Element* ancestor : invisible_ancestors) { DCHECK(activated_element_iterator != activated_elements.end()); ancestor->DispatchEvent(*MakeGarbageCollected( *activated_element_iterator)); ++activated_element_iterator; } } bool Element::IsInsideInvisibleStaticSubtree() const { if (!RuntimeEnabledFeatures::InvisibleDOMEnabled()) return false; for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*this)) { if (ancestor.IsElementNode() && ToElement(ancestor).Invisible() == InvisibleState::kStatic) return true; } return false; } bool Element::IsInsideInvisibleSubtree() const { if (!RuntimeEnabledFeatures::InvisibleDOMEnabled() || !CanParticipateInFlatTree()) return false; for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*this)) { if (ancestor.IsElementNode() && ToElement(ancestor).Invisible() != InvisibleState::kMissing) return true; } return false; } void Element::InvisibleAttributeChanged(const AtomicString& old_value, const AtomicString& new_value) { if (old_value.IsNull() != new_value.IsNull()) { SetNeedsStyleRecalc(kLocalStyleChange, StyleChangeReasonForTracing::Create( style_change_reason::kInvisibleChange)); } if (EqualIgnoringASCIICase(old_value, "static") && !IsInsideInvisibleStaticSubtree()) { // This element and its descendants are not in an invisible="static" tree // anymore. CustomElement::Registry(*this)->upgrade(this); } } void Element::DefaultEventHandler(Event& event) { if (RuntimeEnabledFeatures::InvisibleDOMEnabled() && event.type() == event_type_names::kActivateinvisible && event.target() == this) { removeAttribute(kInvisibleAttr); event.SetDefaultHandled(); return; } ContainerNode::DefaultEventHandler(event); } bool Element::toggleAttribute(const AtomicString& qualified_name, ExceptionState& exception_state) { // https://dom.spec.whatwg.org/#dom-element-toggleattribute // 1. If qualifiedName does not match the Name production in XML, then throw // an "InvalidCharacterError" DOMException. if (!Document::IsValidName(qualified_name)) { exception_state.ThrowDOMException( DOMExceptionCode::kInvalidCharacterError, "'" + qualified_name + "' is not a valid attribute name."); return false; } // 2. If the context object is in the HTML namespace and its node document is // an HTML document, then set qualifiedName to qualifiedName in ASCII // lowercase. AtomicString lower_case_name = LowercaseIfNecessary(qualified_name); // 3. Let attribute be the first attribute in the context object’s attribute // list whose qualified name is qualifiedName, and null otherwise. // 4. If attribute is null, then if (!getAttribute(lower_case_name)) { // 4. 1. If force is not given or is true, create an attribute whose local // name is qualifiedName, value is the empty string, and node document is // the context object’s node document, then append this attribute to the // context object, and then return true. setAttribute(lower_case_name, g_empty_atom); return true; } // 5. Otherwise, if force is not given or is false, remove an attribute given // qualifiedName and the context object, and then return false. removeAttribute(lower_case_name); return false; } bool Element::toggleAttribute(const AtomicString& qualified_name, bool force, ExceptionState& exception_state) { // https://dom.spec.whatwg.org/#dom-element-toggleattribute // 1. If qualifiedName does not match the Name production in XML, then throw // an "InvalidCharacterError" DOMException. if (!Document::IsValidName(qualified_name)) { exception_state.ThrowDOMException( DOMExceptionCode::kInvalidCharacterError, "'" + qualified_name + "' is not a valid attribute name."); return false; } // 2. If the context object is in the HTML namespace and its node document is // an HTML document, then set qualifiedName to qualifiedName in ASCII // lowercase. AtomicString lower_case_name = LowercaseIfNecessary(qualified_name); // 3. Let attribute be the first attribute in the context object’s attribute // list whose qualified name is qualifiedName, and null otherwise. // 4. If attribute is null, then if (!getAttribute(lower_case_name)) { // 4. 1. If force is not given or is true, create an attribute whose local // name is qualifiedName, value is the empty string, and node document is // the context object’s node document, then append this attribute to the // context object, and then return true. if (force) { setAttribute(lower_case_name, g_empty_atom); return true; } // 4. 2. Return false. return false; } // 5. Otherwise, if force is not given or is false, remove an attribute given // qualifiedName and the context object, and then return false. if (!force) { removeAttribute(lower_case_name); return false; } // 6. Return true. return true; } const AtomicString& Element::getAttribute( const AtomicString& local_name) const { if (!GetElementData()) return g_null_atom; SynchronizeAttribute(local_name); if (const Attribute* attribute = GetElementData()->Attributes().Find(LowercaseIfNecessary(local_name))) return attribute->Value(); return g_null_atom; } const AtomicString& Element::getAttributeNS( const AtomicString& namespace_uri, const AtomicString& local_name) const { return getAttribute(QualifiedName(g_null_atom, local_name, namespace_uri)); } void Element::setAttribute(const AtomicString& local_name, const AtomicString& value, ExceptionState& exception_state) { if (!Document::IsValidName(local_name)) { exception_state.ThrowDOMException( DOMExceptionCode::kInvalidCharacterError, "'" + local_name + "' is not a valid attribute name."); return; } SynchronizeAttribute(local_name); AtomicString case_adjusted_local_name = LowercaseIfNecessary(local_name); if (!GetElementData()) { SetAttributeInternal( kNotFound, QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom), value, kNotInSynchronizationOfLazyAttribute); return; } AttributeCollection attributes = GetElementData()->Attributes(); wtf_size_t index = attributes.FindIndex(case_adjusted_local_name); const QualifiedName& q_name = index != kNotFound ? attributes[index].GetName() : QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom); SetAttributeInternal(index, q_name, value, kNotInSynchronizationOfLazyAttribute); } void Element::setAttribute(const AtomicString& name, const AtomicString& value) { setAttribute(name, value, ASSERT_NO_EXCEPTION); } void Element::setAttribute(const QualifiedName& name, const AtomicString& value) { SynchronizeAttribute(name); wtf_size_t index = GetElementData() ? GetElementData()->Attributes().FindIndex(name) : kNotFound; SetAttributeInternal(index, name, value, kNotInSynchronizationOfLazyAttribute); } void Element::SetSynchronizedLazyAttribute(const QualifiedName& name, const AtomicString& value) { wtf_size_t index = GetElementData() ? GetElementData()->Attributes().FindIndex(name) : kNotFound; SetAttributeInternal(index, name, value, kInSynchronizationOfLazyAttribute); } void Element::setAttribute( const AtomicString& name, const StringOrTrustedHTMLOrTrustedScriptOrTrustedScriptURLOrTrustedURL& string_or_TT, ExceptionState& exception_state) { // TODO(vogelheim): Check whether this applies to non-HTML documents, too. AtomicString name_lowercase = LowercaseIfNecessary(name); const AttrNameToTrustedType* attribute_types = &GetCheckedAttributeTypes(); AttrNameToTrustedType::const_iterator it = attribute_types->find(name_lowercase); if (it != attribute_types->end()) { String attr_value = GetStringFromSpecificTrustedType( string_or_TT, it->value, &GetDocument(), exception_state); if (!exception_state.HadException()) setAttribute(name_lowercase, AtomicString(attr_value), exception_state); return; } else if (name_lowercase.StartsWith("on")) { // TODO(jakubvrana): This requires TrustedScript in all attributes starting // with "on", including e.g. "one". We use this pattern elsewhere (e.g. in // IsEventHandlerAttribute) but it's not ideal. Consider using the event // attribute of the resulting AttributeTriggers. String attr_value = GetStringFromSpecificTrustedType( string_or_TT, SpecificTrustedType::kTrustedScript, &GetDocument(), exception_state); if (!exception_state.HadException()) setAttribute(name_lowercase, AtomicString(attr_value), exception_state); return; } AtomicString value_string = AtomicString(GetStringFromTrustedTypeWithoutCheck(string_or_TT)); setAttribute(name_lowercase, value_string, exception_state); } const AttrNameToTrustedType& Element::GetCheckedAttributeTypes() const { DEFINE_STATIC_LOCAL(AttrNameToTrustedType, attribute_map, ({})); return attribute_map; } void Element::setAttribute(const QualifiedName& name, const StringOrTrustedHTML& stringOrHTML, ExceptionState& exception_state) { String valueString = GetStringFromTrustedHTML(stringOrHTML, &GetDocument(), exception_state); if (!exception_state.HadException()) { setAttribute(name, AtomicString(valueString)); } } void Element::setAttribute(const QualifiedName& name, const StringOrTrustedScript& stringOrScript, ExceptionState& exception_state) { String valueString = GetStringFromTrustedScript( stringOrScript, &GetDocument(), exception_state); if (!exception_state.HadException()) { setAttribute(name, AtomicString(valueString)); } } void Element::setAttribute(const QualifiedName& name, const StringOrTrustedScriptURL& stringOrURL, ExceptionState& exception_state) { String valueString = GetStringFromTrustedScriptURL( stringOrURL, &GetDocument(), exception_state); if (!exception_state.HadException()) { setAttribute(name, AtomicString(valueString)); } } void Element::setAttribute(const QualifiedName& name, const USVStringOrTrustedURL& stringOrURL, ExceptionState& exception_state) { String valueString = GetStringFromTrustedURL(stringOrURL, &GetDocument(), exception_state); if (!exception_state.HadException()) { setAttribute(name, AtomicString(valueString)); } } ALWAYS_INLINE void Element::SetAttributeInternal( wtf_size_t index, const QualifiedName& name, const AtomicString& new_value, SynchronizationOfLazyAttribute in_synchronization_of_lazy_attribute) { if (new_value.IsNull()) { if (index != kNotFound) RemoveAttributeInternal(index, in_synchronization_of_lazy_attribute); return; } if (index == kNotFound) { AppendAttributeInternal(name, new_value, in_synchronization_of_lazy_attribute); return; } const Attribute& existing_attribute = GetElementData()->Attributes().at(index); AtomicString existing_attribute_value = existing_attribute.Value(); QualifiedName existing_attribute_name = existing_attribute.GetName(); if (!in_synchronization_of_lazy_attribute) WillModifyAttribute(existing_attribute_name, existing_attribute_value, new_value); if (new_value != existing_attribute_value) EnsureUniqueElementData().Attributes().at(index).SetValue(new_value); if (!in_synchronization_of_lazy_attribute) DidModifyAttribute(existing_attribute_name, existing_attribute_value, new_value); } static inline AtomicString MakeIdForStyleResolution(const AtomicString& value, bool in_quirks_mode) { if (in_quirks_mode) return value.LowerASCII(); return value; } DISABLE_CFI_PERF void Element::AttributeChanged(const AttributeModificationParams& params) { const QualifiedName& name = params.name; if (ShadowRoot* parent_shadow_root = ShadowRootWhereNodeCanBeDistributedForV0(*this)) { if (ShouldInvalidateDistributionWhenAttributeChanged( *parent_shadow_root, name, params.new_value)) parent_shadow_root->SetNeedsDistributionRecalc(); } if (name == html_names::kSlotAttr && params.old_value != params.new_value) { if (ShadowRoot* root = V1ShadowRootOfParent()) root->DidChangeHostChildSlotName(params.old_value, params.new_value); } ParseAttribute(params); GetDocument().IncDOMTreeVersion(); if (name == html_names::kIdAttr) { AtomicString old_id = GetElementData()->IdForStyleResolution(); AtomicString new_id = MakeIdForStyleResolution( params.new_value, GetDocument().InQuirksMode()); if (new_id != old_id) { GetElementData()->SetIdForStyleResolution(new_id); GetDocument().GetStyleEngine().IdChangedForElement(old_id, new_id, *this); } } else if (name == kClassAttr) { ClassAttributeChanged(params.new_value); if (HasRareData() && GetElementRareData()->GetClassList()) { GetElementRareData()->GetClassList()->DidUpdateAttributeValue( params.old_value, params.new_value); } } else if (name == html_names::kNameAttr) { SetHasName(!params.new_value.IsNull()); } else if (name == html_names::kPartAttr) { if (RuntimeEnabledFeatures::CSSPartPseudoElementEnabled()) { part().DidUpdateAttributeValue(params.old_value, params.new_value); GetDocument().GetStyleEngine().PartChangedForElement(*this); } } else if (name == html_names::kExportpartsAttr) { if (RuntimeEnabledFeatures::CSSPartPseudoElementEnabled()) { EnsureElementRareData().SetPartNamesMap(params.new_value); GetDocument().GetStyleEngine().ExportpartsChangedForElement(*this); } } else if (IsStyledElement()) { if (name == kStyleAttr) { StyleAttributeChanged(params.new_value, params.reason); } else if (IsPresentationAttribute(name)) { GetElementData()->presentation_attribute_style_is_dirty_ = true; SetNeedsStyleRecalc(kLocalStyleChange, StyleChangeReasonForTracing::FromAttribute(name)); } else if (RuntimeEnabledFeatures::InvisibleDOMEnabled() && name == html_names::kInvisibleAttr && params.old_value != params.new_value) { InvisibleAttributeChanged(params.old_value, params.new_value); } } InvalidateNodeListCachesInAncestors(&name, this, nullptr); if (isConnected()) { if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache()) { if (params.old_value != params.new_value) { auto* page = GetDocument().GetPage(); auto* view = GetDocument().View(); // If this attribute is interesting for accessibility (e.g. `role` or // `alt`), but doesn't trigger a lifecycle update on its own // (e.g. because it doesn't make layout dirty), make sure we run // lifecycle phases to update the computed accessibility tree. if (cache->HandleAttributeChanged(name, this) && page && view) { if (!view->CanThrottleRendering()) page->Animator().ScheduleVisualUpdate(GetDocument().GetFrame()); // TODO(aboxhall): add a lifecycle phase for accessibility updates. GetDocument().Lifecycle().EnsureStateAtMost( DocumentLifecycle::kVisualUpdatePending); } } } } if (params.reason == AttributeModificationReason::kDirectly && name == kTabindexAttr && AdjustedFocusedElementInTreeScope() == this) { // The attribute change may cause supportsFocus() to return false // for the element which had focus. // // TODO(tkent): We should avoid updating style. We'd like to check only // DOM-level focusability here. GetDocument().UpdateStyleAndLayoutTreeForNode(this); if (!SupportsFocus()) blur(); } } bool Element::HasLegalLinkAttribute(const QualifiedName&) const { return false; } const QualifiedName& Element::SubResourceAttributeName() const { return QualifiedName::Null(); } template static inline ClassStringContent ClassStringHasClassName( const CharacterType* characters, unsigned length) { DCHECK_GT(length, 0u); unsigned i = 0; do { if (IsNotHTMLSpace(characters[i])) break; ++i; } while (i < length); if (i == length && length >= 1) return ClassStringContent::kWhiteSpaceOnly; return ClassStringContent::kHasClasses; } static inline ClassStringContent ClassStringHasClassName( const AtomicString& new_class_string) { unsigned length = new_class_string.length(); if (!length) return ClassStringContent::kEmpty; if (new_class_string.Is8Bit()) return ClassStringHasClassName(new_class_string.Characters8(), length); return ClassStringHasClassName(new_class_string.Characters16(), length); } void Element::ClassAttributeChanged(const AtomicString& new_class_string) { DCHECK(GetElementData()); ClassStringContent class_string_content_type = ClassStringHasClassName(new_class_string); const bool should_fold_case = GetDocument().InQuirksMode(); if (class_string_content_type == ClassStringContent::kHasClasses) { const SpaceSplitString old_classes = GetElementData()->ClassNames(); GetElementData()->SetClass(new_class_string, should_fold_case); const SpaceSplitString& new_classes = GetElementData()->ClassNames(); GetDocument().GetStyleEngine().ClassChangedForElement(old_classes, new_classes, *this); } else { const SpaceSplitString& old_classes = GetElementData()->ClassNames(); GetDocument().GetStyleEngine().ClassChangedForElement(old_classes, *this); if (class_string_content_type == ClassStringContent::kWhiteSpaceOnly) GetElementData()->SetClass(new_class_string, should_fold_case); else GetElementData()->ClearClass(); } } void Element::PictureInPicturePseudoStateChanged() { if (!RuntimeEnabledFeatures::CSSPictureInPictureEnabled()) return; // Recurse up author shadow trees to mark shadow hosts if it matches pseudo // class ":picture-in-picture". if (ShadowRoot* root = ContainingShadowRoot()) { if (!root->IsUserAgent()) OwnerShadowHost()->PictureInPicturePseudoStateChanged(); } PseudoStateChanged(CSSSelector::kPseudoPictureInPicture); } bool Element::ShouldInvalidateDistributionWhenAttributeChanged( ShadowRoot& shadow_root, const QualifiedName& name, const AtomicString& new_value) { if (shadow_root.IsV1()) return false; const SelectRuleFeatureSet& feature_set = shadow_root.V0().EnsureSelectFeatureSet(); if (name == html_names::kIdAttr) { AtomicString old_id = GetElementData()->IdForStyleResolution(); AtomicString new_id = MakeIdForStyleResolution(new_value, GetDocument().InQuirksMode()); if (new_id != old_id) { if (!old_id.IsEmpty() && feature_set.HasSelectorForId(old_id)) return true; if (!new_id.IsEmpty() && feature_set.HasSelectorForId(new_id)) return true; } } if (name == html_names::kClassAttr) { const AtomicString& new_class_string = new_value; if (ClassStringHasClassName(new_class_string) == ClassStringContent::kHasClasses) { const SpaceSplitString& old_classes = GetElementData()->ClassNames(); const SpaceSplitString new_classes(GetDocument().InQuirksMode() ? new_class_string.LowerASCII() : new_class_string); if (feature_set.CheckSelectorsForClassChange(old_classes, new_classes)) return true; } else { const SpaceSplitString& old_classes = GetElementData()->ClassNames(); if (feature_set.CheckSelectorsForClassChange(old_classes)) return true; } } return feature_set.HasSelectorForAttribute(name.LocalName()); } // Returns true if the given attribute is an event handler. // We consider an event handler any attribute that begins with "on". // It is a simple solution that has the advantage of not requiring any // code or configuration change if a new event handler is defined. static inline bool IsEventHandlerAttribute(const Attribute& attribute) { return attribute.GetName().NamespaceURI().IsNull() && attribute.GetName().LocalName().StartsWith("on"); } bool Element::AttributeValueIsJavaScriptURL(const Attribute& attribute) { return ProtocolIsJavaScript( StripLeadingAndTrailingHTMLSpaces(attribute.Value())); } bool Element::IsJavaScriptURLAttribute(const Attribute& attribute) const { return IsURLAttribute(attribute) && AttributeValueIsJavaScriptURL(attribute); } bool Element::IsScriptingAttribute(const Attribute& attribute) const { return IsEventHandlerAttribute(attribute) || IsJavaScriptURLAttribute(attribute) || IsHTMLContentAttribute(attribute) || IsSVGAnimationAttributeSettingJavaScriptURL(attribute); } void Element::StripScriptingAttributes( Vector& attribute_vector) const { wtf_size_t destination = 0; for (wtf_size_t source = 0; source < attribute_vector.size(); ++source) { if (IsScriptingAttribute(attribute_vector[source])) continue; if (source != destination) attribute_vector[destination] = attribute_vector[source]; ++destination; } attribute_vector.Shrink(destination); } void Element::ParserSetAttributes(const Vector& attribute_vector) { DCHECK(!isConnected()); DCHECK(!parentNode()); DCHECK(!element_data_); if (!attribute_vector.IsEmpty()) { if (GetDocument().GetElementDataCache()) element_data_ = GetDocument() .GetElementDataCache() ->CachedShareableElementDataWithAttributes(attribute_vector); else element_data_ = ShareableElementData::CreateWithAttributes(attribute_vector); } ParserDidSetAttributes(); // Use attribute_vector instead of element_data_ because AttributeChanged // might modify element_data_. for (const auto& attribute : attribute_vector) { AttributeChanged(AttributeModificationParams( attribute.GetName(), g_null_atom, attribute.Value(), AttributeModificationReason::kByParser)); } } bool Element::HasEquivalentAttributes(const Element& other) const { SynchronizeAllAttributes(); other.SynchronizeAllAttributes(); if (GetElementData() == other.GetElementData()) return true; if (GetElementData()) return GetElementData()->IsEquivalent(other.GetElementData()); if (other.GetElementData()) return other.GetElementData()->IsEquivalent(GetElementData()); return true; } String Element::nodeName() const { return tag_name_.ToString(); } AtomicString Element::LocalNameForSelectorMatching() const { if (IsHTMLElement() || !GetDocument().IsHTMLDocument()) return localName(); return localName().DeprecatedLower(); } const AtomicString& Element::LocateNamespacePrefix( const AtomicString& namespace_to_locate) const { if (!prefix().IsNull() && namespaceURI() == namespace_to_locate) return prefix(); AttributeCollection attributes = Attributes(); for (const Attribute& attr : attributes) { if (attr.Prefix() == g_xmlns_atom && attr.Value() == namespace_to_locate) return attr.LocalName(); } if (Element* parent = parentElement()) return parent->LocateNamespacePrefix(namespace_to_locate); return g_null_atom; } const AtomicString Element::ImageSourceURL() const { return getAttribute(kSrcAttr); } bool Element::LayoutObjectIsNeeded(const ComputedStyle& style) const { return style.Display() != EDisplay::kNone && style.Display() != EDisplay::kContents; } LayoutObject* Element::CreateLayoutObject(const ComputedStyle& style, LegacyLayout legacy) { return LayoutObject::CreateObject(this, style, legacy); } Node::InsertionNotificationRequest Element::InsertedInto( ContainerNode& insertion_point) { // need to do superclass processing first so isConnected() is true // by the time we reach updateId ContainerNode::InsertedInto(insertion_point); DCHECK(!HasRareData() || !GetElementRareData()->HasPseudoElements()); if (!insertion_point.IsInTreeScope()) return kInsertionDone; if (HasRareData()) { ElementRareData* rare_data = GetElementRareData(); if (rare_data->IntersectionObserverData() && rare_data->IntersectionObserverData()->HasObservations()) { GetDocument().EnsureIntersectionObserverController().AddTrackedTarget( *this, rare_data->IntersectionObserverData()->NeedsOcclusionTracking()); if (LocalFrameView* frame_view = GetDocument().View()) frame_view->SetIntersectionObservationState(LocalFrameView::kRequired); } } if (isConnected()) { if (GetCustomElementState() == CustomElementState::kCustom) CustomElement::EnqueueConnectedCallback(*this); else if (IsUpgradedV0CustomElement()) V0CustomElement::DidAttach(this, GetDocument()); else if (GetCustomElementState() == CustomElementState::kUndefined) CustomElement::TryToUpgrade(*this); } TreeScope& scope = insertion_point.GetTreeScope(); if (scope != GetTreeScope()) return kInsertionDone; const AtomicString& id_value = GetIdAttribute(); if (!id_value.IsNull()) UpdateId(scope, g_null_atom, id_value); const AtomicString& name_value = GetNameAttribute(); if (!name_value.IsNull()) UpdateName(g_null_atom, name_value); if (parentElement() && parentElement()->IsInCanvasSubtree()) SetIsInCanvasSubtree(true); return kInsertionDone; } void Element::RemovedFrom(ContainerNode& insertion_point) { bool was_in_document = insertion_point.isConnected(); if (HasRareData()) { // If we detached the layout tree with LazyReattachIfAttached, we might not // have cleared the pseudo elements if we remove the element before calling // AttachLayoutTree again. We don't clear pseudo elements on // DetachLayoutTree() if we intend to attach again to avoid recreating the // pseudo elements. ElementRareData* rare_data = GetElementRareData(); rare_data->ClearPseudoElements(); } SetComputedStyle(nullptr); if (Fullscreen::IsFullscreenElement(*this)) { SetContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); if (insertion_point.IsElementNode()) { ToElement(insertion_point).SetContainsFullScreenElement(false); ToElement(insertion_point) .SetContainsFullScreenElementOnAncestorsCrossingFrameBoundaries( false); } } if (GetDocument().GetPage()) GetDocument().GetPage()->GetPointerLockController().ElementRemoved(this); SetSavedLayerScrollOffset(ScrollOffset()); if (insertion_point.IsInTreeScope() && GetTreeScope() == GetDocument()) { const AtomicString& id_value = GetIdAttribute(); if (!id_value.IsNull()) UpdateId(insertion_point.GetTreeScope(), id_value, g_null_atom); const AtomicString& name_value = GetNameAttribute(); if (!name_value.IsNull()) UpdateName(name_value, g_null_atom); } ContainerNode::RemovedFrom(insertion_point); if (was_in_document) { if (this == GetDocument().CssTarget()) GetDocument().SetCSSTarget(nullptr); if (GetCustomElementState() == CustomElementState::kCustom) CustomElement::EnqueueDisconnectedCallback(*this); else if (IsUpgradedV0CustomElement()) V0CustomElement::DidDetach(this, insertion_point.GetDocument()); } GetDocument().GetRootScrollerController().ElementRemoved(*this); if (IsInTopLayer()) { Fullscreen::ElementRemoved(*this); GetDocument().RemoveFromTopLayer(this); } ClearElementFlag(ElementFlags::kIsInCanvasSubtree); if (HasRareData()) { ElementRareData* data = GetElementRareData(); data->ClearRestyleFlags(); if (ElementAnimations* element_animations = data->GetElementAnimations()) element_animations->CssAnimations().Cancel(); if (data->IntersectionObserverData()) { data->IntersectionObserverData()->ComputeObservations( IntersectionObservation::kExplicitRootObserversNeedUpdate | IntersectionObservation::kImplicitRootObserversNeedUpdate); GetDocument().EnsureIntersectionObserverController().RemoveTrackedTarget( *this); } } if (GetDocument().GetFrame()) GetDocument().GetFrame()->GetEventHandler().ElementRemoved(this); } void Element::AttachLayoutTree(AttachContext& context) { DCHECK(GetDocument().InStyleRecalc()); ComputedStyle* style = MutableComputedStyle(); if ((!style || style->IsEnsuredInDisplayNone()) && !ChildNeedsReattachLayoutTree()) { Node::AttachLayoutTree(context); return; } AttachContext children_context(context); if (style && CanParticipateInFlatTree()) { // If an element requires forced legacy layout, all descendants need it too. if (ShouldForceLegacyLayout()) children_context.force_legacy_layout = true; LegacyLayout legacy = children_context.force_legacy_layout ? LegacyLayout::kForce : LegacyLayout::kAuto; LayoutTreeBuilderForElement builder(*this, style); builder.CreateLayoutObjectIfNeeded(legacy); } LayoutObject* layout_object = GetLayoutObject(); if (layout_object) children_context.previous_in_flow = nullptr; children_context.use_previous_in_flow = true; AttachPseudoElement(kPseudoIdBefore, children_context); if (ShadowRoot* shadow_root = GetShadowRoot()) { // When a shadow root exists, it does the work of attaching the children. shadow_root->AttachLayoutTree(children_context); Node::AttachLayoutTree(context); ClearChildNeedsReattachLayoutTree(); } else { ContainerNode::AttachLayoutTree(children_context); } AttachPseudoElement(kPseudoIdAfter, children_context); AttachPseudoElement(kPseudoIdBackdrop, children_context); UpdateFirstLetterPseudoElement(StyleUpdatePhase::kAttachLayoutTree); AttachPseudoElement(kPseudoIdFirstLetter, children_context); if (layout_object) { if (!layout_object->IsFloatingOrOutOfFlowPositioned()) context.previous_in_flow = layout_object; } else { context.previous_in_flow = children_context.previous_in_flow; } if (auto* display_lock_context = GetDisplayLockContext()) display_lock_context->DidAttachLayoutTree(); } void Element::DetachLayoutTree(const AttachContext& context) { HTMLFrameOwnerElement::PluginDisposeSuspendScope suspend_plugin_dispose; if (HasRareData()) { ElementRareData* data = GetElementRareData(); if (!context.performing_reattach) data->ClearPseudoElements(); if (ElementAnimations* element_animations = data->GetElementAnimations()) { if (context.performing_reattach) { // FIXME: We call detach from within style recalc, so compositingState // is not up to date. // https://code.google.com/p/chromium/issues/detail?id=339847 DisableCompositingQueryAsserts disabler; // FIXME: restart compositor animations rather than pull back to the // main thread element_animations->RestartAnimationOnCompositor(); } else { element_animations->CssAnimations().Cancel(); element_animations->SetAnimationStyleChange(false); } element_animations->ClearBaseComputedStyle(); } } DetachPseudoElement(kPseudoIdBefore, context); if (ChildNeedsReattachLayoutTree() || GetComputedStyle()) { if (ShadowRoot* shadow_root = GetShadowRoot()) { shadow_root->DetachLayoutTree(context); Node::DetachLayoutTree(context); } else { ContainerNode::DetachLayoutTree(context); } } else { Node::DetachLayoutTree(context); } DetachPseudoElement(kPseudoIdAfter, context); DetachPseudoElement(kPseudoIdBackdrop, context); DetachPseudoElement(kPseudoIdFirstLetter, context); if (!context.performing_reattach) { UpdateCallbackSelectors(GetComputedStyle(), nullptr); SetComputedStyle(nullptr); } if (!context.performing_reattach && IsUserActionElement()) { if (IsHovered()) GetDocument().HoveredElementDetached(*this); if (InActiveChain()) GetDocument().ActiveChainNodeDetached(*this); GetDocument().UserActionElements().DidDetach(*this); } SetNeedsResizeObserverUpdate(); } scoped_refptr Element::StyleForLayoutObject( bool calc_invisible) { DCHECK(GetDocument().InStyleRecalc()); // FIXME: Instead of clearing updates that may have been added from calls to // StyleForElement outside RecalcStyle, we should just never set them if we're // not inside RecalcStyle. if (ElementAnimations* element_animations = GetElementAnimations()) element_animations->CssAnimations().ClearPendingUpdate(); if (RuntimeEnabledFeatures::InvisibleDOMEnabled() && hasAttribute(html_names::kInvisibleAttr) && !calc_invisible) { auto style = GetDocument().GetStyleResolver()->InitialStyleForElement(GetDocument()); style->SetDisplay(EDisplay::kNone); return style; } scoped_refptr style = HasCustomStyleCallbacks() ? CustomStyleForLayoutObject() : OriginalStyleForLayoutObject(); if (!style) { DCHECK(IsPseudoElement()); return nullptr; } // StyleForElement() might add active animations so we need to get it again. if (ElementAnimations* element_animations = GetElementAnimations()) { element_animations->CssAnimations().MaybeApplyPendingUpdate(this); element_animations->UpdateAnimationFlags(*style); } style->UpdateIsStackingContext(this == GetDocument().documentElement(), IsInTopLayer(), IsSVGForeignObjectElement(*this)); return style; } scoped_refptr Element::OriginalStyleForLayoutObject() { return GetDocument().EnsureStyleResolver().StyleForElement(this); } void Element::RecalcStyleForTraversalRootAncestor() { if (!ChildNeedsReattachLayoutTree()) UpdateFirstLetterPseudoElement(StyleUpdatePhase::kRecalc); if (HasCustomStyleCallbacks()) DidRecalcStyle({}); } void Element::RecalcStyle(const StyleRecalcChange change) { DCHECK(InActiveDocument()); DCHECK(GetDocument().InStyleRecalc()); DCHECK(!GetDocument().Lifecycle().InDetach()); DisplayLockStyleScope display_lock_style_scope(GetDisplayLockContext()); if (!display_lock_style_scope.ShouldUpdateSelfStyle()) { display_lock_style_scope.NotifyUpdateWasBlocked( change.RecalcChildren() ? (change.RecalcDescendants() ? DisplayLockContext::kStyleUpdateDescendants : DisplayLockContext::kStyleUpdateChildren) : DisplayLockContext::kStyleUpdateSelf); return; } if (HasCustomStyleCallbacks()) WillRecalcStyle(change); StyleRecalcChange child_change = change.ForChildren(); if (change.ShouldRecalcStyleFor(*this)) { child_change = RecalcOwnStyle(change); if (GetStyleChangeType() == kSubtreeStyleChange) child_change = child_change.ForceRecalcDescendants(); ClearNeedsStyleRecalc(); } if (child_change.TraversePseudoElements(*this)) { UpdatePseudoElement(kPseudoIdBackdrop, child_change); UpdatePseudoElement(kPseudoIdBefore, child_change); } const bool should_update_child_style = display_lock_style_scope.ShouldUpdateChildStyle(); if (child_change.TraverseChildren(*this) && should_update_child_style) { SelectorFilterParentScope filter_scope(*this); if (ShadowRoot* root = GetShadowRoot()) { if (child_change.TraverseChild(*root)) root->RecalcStyle(child_change); RecalcDescendantStyles(StyleRecalcChange::kClearEnsured); } else { RecalcDescendantStyles(child_change); } display_lock_style_scope.DidUpdateChildStyle(); } if (child_change.TraversePseudoElements(*this)) { UpdatePseudoElement(kPseudoIdAfter, child_change); // If we are re-attaching us or any of our descendants, we need to attach // the descendants before we know if this element generates a ::first-letter // and which element the ::first-letter inherits style from. if (!child_change.ReattachLayoutTree() && !ChildNeedsReattachLayoutTree()) UpdateFirstLetterPseudoElement(StyleUpdatePhase::kRecalc); } if (should_update_child_style) { ClearChildNeedsStyleRecalc(); } else if (child_change.RecalcChildren()) { // If we should've calculated the style for children but was blocked, // notify so that we'd come back. // Note that the if-clause wouldn't catch cases where // ChildNeedsStyleRecalc() is true but child_change.RecalcChildren() is // false. Since we are retaining the child dirty bit, that case is // automatically handled without needing to notify it here. display_lock_style_scope.NotifyUpdateWasBlocked( DisplayLockContext::kStyleUpdateDescendants); } if (HasCustomStyleCallbacks()) DidRecalcStyle(child_change); } scoped_refptr Element::PropagateInheritedProperties() { if (IsPseudoElement()) return nullptr; if (NeedsStyleRecalc()) return nullptr; if (HasAnimations()) return nullptr; const ComputedStyle* parent_style = ParentComputedStyle(); DCHECK(parent_style); const ComputedStyle* style = GetComputedStyle(); if (!style || style->Animations() || style->Transitions()) return nullptr; scoped_refptr new_style = ComputedStyle::Clone(*style); new_style->PropagateIndependentInheritedProperties(*parent_style); INCREMENT_STYLE_STATS_COUNTER(GetDocument().GetStyleEngine(), independent_inherited_styles_propagated, 1); return new_style; } static const StyleRecalcChange ApplyComputedStyleDiff( const StyleRecalcChange change, ComputedStyle::Difference diff) { if (change.RecalcDescendants() || diff < ComputedStyle::Difference::kPseudoStyle) return change; if (diff == ComputedStyle::Difference::kDisplayAffectingDescendantStyles) return change.ForceRecalcDescendants(); if (diff == ComputedStyle::Difference::kInherited) return change.EnsureAtLeast(StyleRecalcChange::kRecalcChildren); if (diff == ComputedStyle::Difference::kIndependentInherited) return change.EnsureAtLeast(StyleRecalcChange::kIndependentInherit); DCHECK(diff == ComputedStyle::Difference::kPseudoStyle); return change.EnsureAtLeast(StyleRecalcChange::kUpdatePseudoElements); } StyleRecalcChange Element::RecalcOwnStyle(const StyleRecalcChange change) { DCHECK(GetDocument().InStyleRecalc()); if (!CanParticipateInFlatTree()) { // This is a V0InsertionPoint. This whole block can be removed when Shadow // DOM V0 is removed. DCHECK(IsV0InsertionPoint()); if (NeedsStyleRecalc()) SetComputedStyle(nullptr); if (GetForceReattachLayoutTree()) return change.ForceReattachLayoutTree(); // Keep recalculating computed style for fallback children as if they were // children of the insertion point parent. return change; } if (change.RecalcChildren() && HasRareData() && NeedsStyleRecalc()) { // This element needs recalc because its parent changed inherited // properties or there was some style change in the ancestry which needed a // full subtree recalc. In that case we cannot use the BaseComputedStyle // optimization. if (ElementAnimations* element_animations = GetElementRareData()->GetElementAnimations()) element_animations->SetAnimationStyleChange(false); } scoped_refptr new_style; scoped_refptr old_style = GetComputedStyle(); StyleRecalcChange child_change = change.ForChildren(); // If we are on the find-in-page root, we need to calculate style for // invisible nodes in this subtree. if (!child_change.CalcInvisible() && this == GetDocument().FindInPageRoot()) child_change = child_change.ForceCalcInvisible(); if (ParentComputedStyle()) { if (old_style && change.IndependentInherit()) { // When propagating inherited changes, we don't need to do a full style // recalc if the only changed properties are independent. In this case, we // can simply clone the old ComputedStyle and set these directly. new_style = PropagateInheritedProperties(); } if (!new_style) new_style = StyleForLayoutObject(child_change.CalcInvisible()); if (new_style && !ShouldStoreComputedStyle(*new_style)) new_style = nullptr; } ComputedStyle::Difference diff = ComputedStyle::ComputeDifference(old_style.get(), new_style.get()); if (old_style && old_style->IsEnsuredInDisplayNone()) { // Make sure we traverse children for clearing ensured computed styles // further down the tree. child_change = child_change.EnsureAtLeast(StyleRecalcChange::kRecalcChildren); // If the existing style was ensured in a display:none subtree, set it to // null to make sure we don't mark for re-attachment if the new style is // null. old_style = nullptr; } if (!new_style && HasRareData()) { ElementRareData* rare_data = GetElementRareData(); if (ElementAnimations* element_animations = rare_data->GetElementAnimations()) { element_animations->CssAnimations().Cancel(); } rare_data->ClearPseudoElements(); } SetComputedStyle(new_style); if (!child_change.ReattachLayoutTree() && (GetForceReattachLayoutTree() || ComputedStyle::NeedsReattachLayoutTree( old_style.get(), new_style.get()))) { child_change = child_change.ForceReattachLayoutTree(); } if (diff == ComputedStyle::Difference::kEqual) { INCREMENT_STYLE_STATS_COUNTER(GetDocument().GetStyleEngine(), styles_unchanged, 1); if (!new_style) { DCHECK(!old_style); return {}; } } else { INCREMENT_STYLE_STATS_COUNTER(GetDocument().GetStyleEngine(), styles_changed, 1); if (this == GetDocument().documentElement()) { if (GetDocument().GetStyleEngine().UpdateRemUnits(old_style.get(), new_style.get())) { // Trigger a full document recalc on rem unit changes. We could keep // track of which elements depend on rem units like we do for viewport // styles, but we assume root font size changes are rare and just // recalculate everything. child_change = child_change.ForceRecalcDescendants(); } } child_change = ApplyComputedStyleDiff(child_change, diff); UpdateCallbackSelectors(old_style.get(), new_style.get()); } if (new_style) { if (old_style && !child_change.RecalcChildren() && old_style->HasChildDependentFlags()) new_style->CopyChildDependentFlagsFrom(*old_style); if (RuntimeEnabledFeatures::LayoutNGEnabled()) UpdateForceLegacyLayout(*new_style, old_style.get()); } if (child_change.ReattachLayoutTree()) { if (old_style || new_style) SetNeedsReattachLayoutTree(); return child_change; } if (LayoutObject* layout_object = GetLayoutObject()) { DCHECK(new_style); if (IsPseudoElement() && new_style->Display() == EDisplay::kContents) { new_style = ToPseudoElement(this)->LayoutStyleForDisplayContents(*new_style); } // kEqual means that the computed style didn't change, but there are // additional flags in ComputedStyle which may have changed. For instance, // the AffectedBy* flags. We don't need to go through the visual // invalidation diffing in that case, but we replace the old ComputedStyle // object with the new one to ensure the mentioned flags are up to date. LayoutObject::ApplyStyleChanges apply_changes = diff == ComputedStyle::Difference::kEqual ? LayoutObject::ApplyStyleChanges::kNo : LayoutObject::ApplyStyleChanges::kYes; layout_object->SetStyle(new_style.get(), apply_changes); } return child_change; } void Element::RebuildLayoutTree(WhitespaceAttacher& whitespace_attacher) { DCHECK(InActiveDocument()); DCHECK(parentNode()); DCHECK(!StyleRecalcBlockedByDisplayLock(DisplayLockContext::kSelf)); if (NeedsReattachLayoutTree()) { AttachContext reattach_context; if (const Node* parent = LayoutTreeBuilderTraversal::LayoutParent(*this)) { const LayoutObject* parent_object = parent->GetLayoutObject(); if (parent_object && parent_object->ForceLegacyLayout()) reattach_context.force_legacy_layout = true; } ReattachLayoutTree(reattach_context); whitespace_attacher.DidReattachElement(this, reattach_context.previous_in_flow); } else { DCHECK(!StyleRecalcBlockedByDisplayLock(DisplayLockContext::kChildren)); // We create a local WhitespaceAttacher when rebuilding children of an // element with a LayoutObject since whitespace nodes do not rely on layout // objects further up the tree. Also, if this Element's layout object is an // out-of-flow box, in-flow children should not affect whitespace siblings // of the out-of-flow box. However, if this element is a display:contents // element. Continue using the passed in attacher as display:contents // children may affect whitespace nodes further up the tree as they may be // layout tree siblings. WhitespaceAttacher local_attacher; WhitespaceAttacher* child_attacher; if (GetLayoutObject() || (!HasDisplayContentsStyle() && CanParticipateInFlatTree())) { whitespace_attacher.DidVisitElement(this); if (GetDocument().GetStyleEngine().NeedsWhitespaceReattachment(this)) local_attacher.SetReattachAllWhitespaceNodes(); child_attacher = &local_attacher; } else { child_attacher = &whitespace_attacher; } RebuildPseudoElementLayoutTree(kPseudoIdAfter, *child_attacher); if (GetShadowRoot()) RebuildShadowRootLayoutTree(*child_attacher); else RebuildChildrenLayoutTrees(*child_attacher); RebuildPseudoElementLayoutTree(kPseudoIdBefore, *child_attacher); RebuildPseudoElementLayoutTree(kPseudoIdBackdrop, *child_attacher); RebuildFirstLetterLayoutTree(); ClearChildNeedsReattachLayoutTree(); } DCHECK(!NeedsStyleRecalc()); DCHECK(!ChildNeedsStyleRecalc()); DCHECK(!NeedsReattachLayoutTree()); DCHECK(!ChildNeedsReattachLayoutTree()); } void Element::RebuildShadowRootLayoutTree( WhitespaceAttacher& whitespace_attacher) { DCHECK(IsShadowHost(this)); ShadowRoot* root = GetShadowRoot(); root->RebuildLayoutTree(whitespace_attacher); RebuildNonDistributedChildren(); } void Element::RebuildPseudoElementLayoutTree( PseudoId pseudo_id, WhitespaceAttacher& whitespace_attacher) { if (PseudoElement* element = GetPseudoElement(pseudo_id)) { if (element->NeedsRebuildLayoutTree(whitespace_attacher)) element->RebuildLayoutTree(whitespace_attacher); } } void Element::RebuildFirstLetterLayoutTree() { // Need to create a ::first-letter element here for the following case: // // //
// // // The creation of FirstLetterPseudoElement relies on the layout tree of the // block contents. In this case, the ::first-letter element is not created // initially since the #inner div is not displayed. On RecalcStyle it's not // created since the layout tree is still not built, and AttachLayoutTree // for #inner will not update the ::first-letter of outer. However, we end // up here for #outer after AttachLayoutTree is called on #inner at which // point the layout sub-tree is available for deciding on creating the // ::first-letter. UpdateFirstLetterPseudoElement(StyleUpdatePhase::kRebuildLayoutTree); if (PseudoElement* element = GetPseudoElement(kPseudoIdFirstLetter)) { WhitespaceAttacher whitespace_attacher; if (element->NeedsRebuildLayoutTree(whitespace_attacher)) element->RebuildLayoutTree(whitespace_attacher); } } void Element::UpdateCallbackSelectors(const ComputedStyle* old_style, const ComputedStyle* new_style) { Vector empty_vector; const Vector& old_callback_selectors = old_style ? old_style->CallbackSelectors() : empty_vector; const Vector& new_callback_selectors = new_style ? new_style->CallbackSelectors() : empty_vector; if (old_callback_selectors.IsEmpty() && new_callback_selectors.IsEmpty()) return; if (old_callback_selectors != new_callback_selectors) CSSSelectorWatch::From(GetDocument()) .UpdateSelectorMatches(old_callback_selectors, new_callback_selectors); } ShadowRoot& Element::CreateAndAttachShadowRoot(ShadowRootType type) { #if DCHECK_IS_ON() NestingLevelIncrementer slot_assignment_recalc_forbidden_scope( GetDocument().SlotAssignmentRecalcForbiddenRecursionDepth()); #endif EventDispatchForbiddenScope assert_no_event_dispatch; ScriptForbiddenScope forbid_script; DCHECK(!GetShadowRoot()); auto* shadow_root = MakeGarbageCollected(GetDocument(), type); if (type != ShadowRootType::V0 && InActiveDocument()) { // Detach the host's children here for v1 (including UA shadow root), // because we skip SetNeedsDistributionRecalc() in attaching v1 shadow root. // See https://crrev.com/2822113002 for details. // We need to call child.RemovedFromFlatTree() before setting a shadow // root to the element because detach must use the original flat tree // structure before attachShadow happens. We cannot use // FlatTreeParentChanged() because we don't know at this point whether a // slot will be added and the child assigned to a slot on the next slot // assignment update. for (Node& child : NodeTraversal::ChildrenOf(*this)) child.RemovedFromFlatTree(); } EnsureElementRareData().SetShadowRoot(*shadow_root); shadow_root->SetParentOrShadowHostNode(this); shadow_root->SetParentTreeScope(GetTreeScope()); if (type == ShadowRootType::V0) { shadow_root->SetNeedsDistributionRecalc(); } shadow_root->InsertedInto(*this); if (InActiveDocument()) SetChildNeedsStyleRecalc(); SetNeedsStyleRecalc(kSubtreeStyleChange, StyleChangeReasonForTracing::Create( style_change_reason::kShadow)); probe::DidPushShadowRoot(this, shadow_root); return *shadow_root; } ShadowRoot* Element::GetShadowRoot() const { return HasRareData() ? GetElementRareData()->GetShadowRoot() : nullptr; } void Element::PseudoStateChanged(CSSSelector::PseudoType pseudo) { // We can't schedule invaliation sets from inside style recalc otherwise // we'd never process them. // TODO(esprehn): Make this an ASSERT and fix places that call into this // like HTMLSelectElement. if (GetDocument().InStyleRecalc()) return; GetDocument().GetStyleEngine().PseudoStateChangedForElement(pseudo, *this); } void Element::SetAnimationStyleChange(bool animation_style_change) { if (animation_style_change && GetDocument().InStyleRecalc()) return; if (!HasRareData()) return; if (ElementAnimations* element_animations = GetElementRareData()->GetElementAnimations()) element_animations->SetAnimationStyleChange(animation_style_change); } void Element::ClearAnimationStyleChange() { if (!HasRareData()) return; if (ElementAnimations* element_animations = GetElementRareData()->GetElementAnimations()) element_animations->SetAnimationStyleChange(false); } void Element::SetNeedsAnimationStyleRecalc() { if (GetDocument().InStyleRecalc()) return; if (GetStyleChangeType() != kNoStyleChange) return; SetNeedsStyleRecalc(kLocalStyleChange, StyleChangeReasonForTracing::Create( style_change_reason::kAnimation)); SetAnimationStyleChange(true); } void Element::SetNeedsCompositingUpdate() { if (!GetDocument().IsActive()) return; LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject(); if (!layout_object) return; if (!layout_object->HasLayer()) return; layout_object->Layer()->SetNeedsCompositingInputsUpdate(); // Changes in the return value of requiresAcceleratedCompositing change if // the PaintLayer is self-painting. layout_object->Layer()->UpdateSelfPaintingLayer(); } void Element::V0SetCustomElementDefinition( V0CustomElementDefinition* definition) { if (!HasRareData() && !definition) return; DCHECK(!GetV0CustomElementDefinition()); EnsureElementRareData().V0SetCustomElementDefinition(definition); } V0CustomElementDefinition* Element::GetV0CustomElementDefinition() const { if (HasRareData()) return GetElementRareData()->GetV0CustomElementDefinition(); return nullptr; } void Element::SetCustomElementDefinition(CustomElementDefinition* definition) { DCHECK(definition); DCHECK(!GetCustomElementDefinition()); EnsureElementRareData().SetCustomElementDefinition(definition); SetCustomElementState(CustomElementState::kCustom); } CustomElementDefinition* Element::GetCustomElementDefinition() const { if (HasRareData()) return GetElementRareData()->GetCustomElementDefinition(); return nullptr; } void Element::SetIsValue(const AtomicString& is_value) { DCHECK(IsValue().IsNull()) << "SetIsValue() should be called at most once."; EnsureElementRareData().SetIsValue(is_value); } const AtomicString& Element::IsValue() const { if (HasRareData()) return GetElementRareData()->IsValue(); return g_null_atom; } void Element::SetDidAttachInternals() { EnsureElementRareData().SetDidAttachInternals(); } bool Element::DidAttachInternals() const { return HasRareData() && GetElementRareData()->DidAttachInternals(); } ElementInternals& Element::EnsureElementInternals() { return EnsureElementRareData().EnsureElementInternals(ToHTMLElement(*this)); } ShadowRoot* Element::createShadowRoot(ExceptionState& exception_state) { if (ShadowRoot* root = GetShadowRoot()) { if (root->IsUserAgent()) { exception_state.ThrowDOMException( DOMExceptionCode::kInvalidStateError, "Shadow root cannot be created on a host which already hosts a " "user-agent shadow tree."); } else { exception_state.ThrowDOMException( DOMExceptionCode::kInvalidStateError, "Shadow root cannot be created on a host which already hosts a " "shadow tree."); } return nullptr; } if (AlwaysCreateUserAgentShadowRoot()) { exception_state.ThrowDOMException( DOMExceptionCode::kInvalidStateError, "Shadow root cannot be created on a host which already hosts a " "user-agent shadow tree."); return nullptr; } // Some elements make assumptions about what kind of layoutObjects they allow // as children so we can't allow author shadows on them for now. if (!AreAuthorShadowsAllowed()) { exception_state.ThrowDOMException( DOMExceptionCode::kHierarchyRequestError, "Author-created shadow roots are disabled for this element."); return nullptr; } return &CreateShadowRootInternal(); } bool Element::CanAttachShadowRoot() const { const AtomicString& tag_name = localName(); // Checking Is{V0}CustomElement() here is just an optimization // because IsValidName is not cheap. return (IsCustomElement() && CustomElement::IsValidName(tag_name)) || (IsV0CustomElement() && V0CustomElement::IsValidName(tag_name)) || tag_name == html_names::kArticleTag || tag_name == html_names::kAsideTag || tag_name == html_names::kBlockquoteTag || tag_name == html_names::kBodyTag || tag_name == html_names::kDivTag || tag_name == html_names::kFooterTag || tag_name == html_names::kH1Tag || tag_name == html_names::kH2Tag || tag_name == html_names::kH3Tag || tag_name == html_names::kH4Tag || tag_name == html_names::kH5Tag || tag_name == html_names::kH6Tag || tag_name == html_names::kHeaderTag || tag_name == html_names::kNavTag || tag_name == html_names::kMainTag || tag_name == html_names::kPTag || tag_name == html_names::kSectionTag || tag_name == html_names::kSpanTag; } ShadowRoot* Element::attachShadow(const ShadowRootInit* shadow_root_init_dict, ExceptionState& exception_state) { DCHECK(shadow_root_init_dict->hasMode()); if (!CanAttachShadowRoot()) { exception_state.ThrowDOMException( DOMExceptionCode::kNotSupportedError, "This element does not support attachShadow"); return nullptr; } if (GetShadowRoot()) { exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, "Shadow root cannot be created on a host " "which already hosts a shadow tree."); return nullptr; } ShadowRootType type = shadow_root_init_dict->mode() == "open" ? ShadowRootType::kOpen : ShadowRootType::kClosed; if (type == ShadowRootType::kOpen) UseCounter::Count(GetDocument(), WebFeature::kElementAttachShadowOpen); else UseCounter::Count(GetDocument(), WebFeature::kElementAttachShadowClosed); DCHECK(!shadow_root_init_dict->hasMode() || !GetShadowRoot()); bool delegates_focus = shadow_root_init_dict->hasDelegatesFocus() && shadow_root_init_dict->delegatesFocus(); bool manual_slotting = shadow_root_init_dict->slotting() == "manual"; return &AttachShadowRootInternal(type, delegates_focus, manual_slotting); } ShadowRoot& Element::CreateShadowRootInternal() { DCHECK(!ClosedShadowRoot()); DCHECK(AreAuthorShadowsAllowed()); DCHECK(!AlwaysCreateUserAgentShadowRoot()); GetDocument().SetShadowCascadeOrder(ShadowCascadeOrder::kShadowCascadeV0); return CreateAndAttachShadowRoot(ShadowRootType::V0); } ShadowRoot& Element::CreateUserAgentShadowRoot() { DCHECK(!GetShadowRoot()); return CreateAndAttachShadowRoot(ShadowRootType::kUserAgent); } ShadowRoot& Element::AttachShadowRootInternal(ShadowRootType type, bool delegates_focus, bool manual_slotting) { // SVG is a special case for using this API to create a closed shadow // root. DCHECK(CanAttachShadowRoot() || IsSVGUseElement(*this)); DCHECK(type == ShadowRootType::kOpen || type == ShadowRootType::kClosed) << type; DCHECK(!AlwaysCreateUserAgentShadowRoot()); GetDocument().SetShadowCascadeOrder(ShadowCascadeOrder::kShadowCascadeV1); ShadowRoot& shadow_root = CreateAndAttachShadowRoot(type); shadow_root.SetDelegatesFocus(delegates_focus); shadow_root.SetSlotting(manual_slotting ? ShadowRootSlotting::kManual : ShadowRootSlotting::kAuto); return shadow_root; } ShadowRoot* Element::OpenShadowRoot() const { ShadowRoot* root = GetShadowRoot(); if (!root) return nullptr; return root->GetType() == ShadowRootType::V0 || root->GetType() == ShadowRootType::kOpen ? root : nullptr; } ShadowRoot* Element::ClosedShadowRoot() const { ShadowRoot* root = GetShadowRoot(); if (!root) return nullptr; return root->GetType() == ShadowRootType::kClosed ? root : nullptr; } ShadowRoot* Element::AuthorShadowRoot() const { ShadowRoot* root = GetShadowRoot(); if (!root) return nullptr; return !root->IsUserAgent() ? root : nullptr; } ShadowRoot* Element::UserAgentShadowRoot() const { ShadowRoot* root = GetShadowRoot(); DCHECK(!root || root->IsUserAgent()); return root; } ShadowRoot& Element::EnsureUserAgentShadowRoot() { if (ShadowRoot* shadow_root = UserAgentShadowRoot()) { DCHECK(shadow_root->GetType() == ShadowRootType::kUserAgent); return *shadow_root; } ShadowRoot& shadow_root = CreateAndAttachShadowRoot(ShadowRootType::kUserAgent); DidAddUserAgentShadowRoot(shadow_root); return shadow_root; } bool Element::ChildTypeAllowed(NodeType type) const { switch (type) { case kElementNode: case kTextNode: case kCommentNode: case kProcessingInstructionNode: case kCdataSectionNode: return true; default: break; } return false; } namespace { bool HasSiblingsForNonEmpty(const Node* sibling, Node* (*next_func)(const Node&)) { for (; sibling; sibling = next_func(*sibling)) { if (sibling->IsElementNode()) return true; if (sibling->IsTextNode() && !ToText(sibling)->data().IsEmpty()) return true; } return false; } } // namespace void Element::CheckForEmptyStyleChange(const Node* node_before_change, const Node* node_after_change) { if (!InActiveDocument()) return; if (!StyleAffectedByEmpty()) return; if (HasSiblingsForNonEmpty(node_before_change, NodeTraversal::PreviousSibling) || HasSiblingsForNonEmpty(node_after_change, NodeTraversal::NextSibling)) { return; } PseudoStateChanged(CSSSelector::kPseudoEmpty); } void Element::ChildrenChanged(const ChildrenChange& change) { ContainerNode::ChildrenChanged(change); CheckForEmptyStyleChange(change.sibling_before_change, change.sibling_after_change); if (!change.by_parser && change.IsChildElementChange()) CheckForSiblingStyleChanges( change.type == kElementRemoved ? kSiblingElementRemoved : kSiblingElementInserted, ToElement(change.sibling_changed), change.sibling_before_change, change.sibling_after_change); if (ShadowRoot* shadow_root = GetShadowRoot()) shadow_root->SetNeedsDistributionRecalcWillBeSetNeedsAssignmentRecalc(); } void Element::FinishParsingChildren() { SetIsFinishedParsingChildren(true); CheckForEmptyStyleChange(this, this); CheckForSiblingStyleChanges(kFinishedParsingChildren, nullptr, lastChild(), nullptr); } AttrNodeList* Element::GetAttrNodeList() { return HasRareData() ? GetElementRareData()->GetAttrNodeList() : nullptr; } void Element::RemoveAttrNodeList() { DCHECK(GetAttrNodeList()); if (HasRareData()) GetElementRareData()->RemoveAttrNodeList(); } Attr* Element::setAttributeNode(Attr* attr_node, ExceptionState& exception_state) { Attr* old_attr_node = AttrIfExists(attr_node->GetQualifiedName()); if (old_attr_node == attr_node) return attr_node; // This Attr is already attached to the element. // InUseAttributeError: Raised if node is an Attr that is already an attribute // of another Element object. The DOM user must explicitly clone Attr nodes // to re-use them in other elements. if (attr_node->ownerElement()) { exception_state.ThrowDOMException( DOMExceptionCode::kInUseAttributeError, "The node provided is an attribute node that is already an attribute " "of another Element; attribute nodes must be explicitly cloned."); return nullptr; } if (!IsHTMLElement() && attr_node->GetDocument().IsHTMLDocument() && attr_node->name() != attr_node->name().LowerASCII()) UseCounter::Count( GetDocument(), WebFeature:: kNonHTMLElementSetAttributeNodeFromHTMLDocumentNameNotLowercase); SynchronizeAllAttributes(); const UniqueElementData& element_data = EnsureUniqueElementData(); AttributeCollection attributes = element_data.Attributes(); wtf_size_t index = attributes.FindIndex(attr_node->GetQualifiedName()); AtomicString local_name; if (index != kNotFound) { const Attribute& attr = attributes[index]; // If the name of the ElementData attribute doesn't // (case-sensitively) match that of the Attr node, record it // on the Attr so that it can correctly resolve the value on // the Element. if (!attr.GetName().Matches(attr_node->GetQualifiedName())) local_name = attr.LocalName(); if (old_attr_node) { DetachAttrNodeFromElementWithValue(old_attr_node, attr.Value()); } else { // FIXME: using attrNode's name rather than the // Attribute's for the replaced Attr is compatible with // all but Gecko (and, arguably, the DOM Level1 spec text.) // Consider switching. old_attr_node = MakeGarbageCollected( GetDocument(), attr_node->GetQualifiedName(), attr.Value()); } } SetAttributeInternal(index, attr_node->GetQualifiedName(), attr_node->value(), kNotInSynchronizationOfLazyAttribute); attr_node->AttachToElement(this, local_name); GetTreeScope().AdoptIfNeeded(*attr_node); EnsureElementRareData().AddAttr(attr_node); return old_attr_node; } Attr* Element::setAttributeNodeNS(Attr* attr, ExceptionState& exception_state) { return setAttributeNode(attr, exception_state); } Attr* Element::removeAttributeNode(Attr* attr, ExceptionState& exception_state) { if (attr->ownerElement() != this) { exception_state.ThrowDOMException( DOMExceptionCode::kNotFoundError, "The node provided is owned by another element."); return nullptr; } DCHECK_EQ(GetDocument(), attr->GetDocument()); SynchronizeAttribute(attr->GetQualifiedName()); wtf_size_t index = GetElementData()->Attributes().FindIndex(attr->GetQualifiedName()); if (index == kNotFound) { exception_state.ThrowDOMException( DOMExceptionCode::kNotFoundError, "The attribute was not found on this element."); return nullptr; } DetachAttrNodeAtIndex(attr, index); return attr; } void Element::ParseAttribute(const AttributeModificationParams& params) { if (params.name == kTabindexAttr) { int tabindex = 0; if (params.new_value.IsEmpty() || !ParseHTMLInteger(params.new_value, tabindex)) { ClearTabIndexExplicitlyIfNeeded(); } else { // We only set when value is in integer range. SetTabIndexExplicitly(); } } else if (params.name == xml_names::kLangAttr) { PseudoStateChanged(CSSSelector::kPseudoLang); } } bool Element::ParseAttributeName(QualifiedName& out, const AtomicString& namespace_uri, const AtomicString& qualified_name, ExceptionState& exception_state) { AtomicString prefix, local_name; if (!Document::ParseQualifiedName(qualified_name, prefix, local_name, exception_state)) return false; DCHECK(!exception_state.HadException()); QualifiedName q_name(prefix, local_name, namespace_uri); if (!Document::HasValidNamespaceForAttributes(q_name)) { exception_state.ThrowDOMException( DOMExceptionCode::kNamespaceError, "'" + namespace_uri + "' is an invalid namespace for attributes."); return false; } out = q_name; return true; } void Element::setAttributeNS( const AtomicString& namespace_uri, const AtomicString& qualified_name, const StringOrTrustedHTMLOrTrustedScriptOrTrustedScriptURLOrTrustedURL& string_or_TT, ExceptionState& exception_state) { String value = GetStringFromTrustedType(string_or_TT, &GetDocument(), exception_state); if (exception_state.HadException()) return; QualifiedName parsed_name = g_any_name; if (!ParseAttributeName(parsed_name, namespace_uri, qualified_name, exception_state)) return; setAttribute(parsed_name, AtomicString(value)); } void Element::RemoveAttributeInternal( wtf_size_t index, SynchronizationOfLazyAttribute in_synchronization_of_lazy_attribute) { MutableAttributeCollection attributes = EnsureUniqueElementData().Attributes(); SECURITY_DCHECK(index < attributes.size()); QualifiedName name = attributes[index].GetName(); AtomicString value_being_removed = attributes[index].Value(); if (!in_synchronization_of_lazy_attribute) { if (!value_being_removed.IsNull()) { WillModifyAttribute(name, value_being_removed, g_null_atom); } else if (GetCustomElementState() == CustomElementState::kCustom) { // This would otherwise be enqueued by willModifyAttribute. CustomElement::EnqueueAttributeChangedCallback( *this, name, value_being_removed, g_null_atom); } } if (Attr* attr_node = AttrIfExists(name)) DetachAttrNodeFromElementWithValue(attr_node, attributes[index].Value()); attributes.Remove(index); if (!in_synchronization_of_lazy_attribute) DidRemoveAttribute(name, value_being_removed); } void Element::AppendAttributeInternal( const QualifiedName& name, const AtomicString& value, SynchronizationOfLazyAttribute in_synchronization_of_lazy_attribute) { if (!in_synchronization_of_lazy_attribute) WillModifyAttribute(name, g_null_atom, value); EnsureUniqueElementData().Attributes().Append(name, value); if (!in_synchronization_of_lazy_attribute) DidAddAttribute(name, value); } void Element::removeAttribute(const AtomicString& name) { if (!GetElementData()) return; AtomicString local_name = LowercaseIfNecessary(name); wtf_size_t index = GetElementData()->Attributes().FindIndex(local_name); if (index == kNotFound) { if (UNLIKELY(local_name == kStyleAttr) && GetElementData()->style_attribute_is_dirty_ && IsStyledElement()) RemoveAllInlineStyleProperties(); return; } RemoveAttributeInternal(index, kNotInSynchronizationOfLazyAttribute); } void Element::removeAttributeNS(const AtomicString& namespace_uri, const AtomicString& local_name) { removeAttribute(QualifiedName(g_null_atom, local_name, namespace_uri)); } Attr* Element::getAttributeNode(const AtomicString& local_name) { if (!GetElementData()) return nullptr; SynchronizeAttribute(local_name); const Attribute* attribute = GetElementData()->Attributes().Find(LowercaseIfNecessary(local_name)); if (!attribute) return nullptr; return EnsureAttr(attribute->GetName()); } Attr* Element::getAttributeNodeNS(const AtomicString& namespace_uri, const AtomicString& local_name) { if (!GetElementData()) return nullptr; QualifiedName q_name(g_null_atom, local_name, namespace_uri); SynchronizeAttribute(q_name); const Attribute* attribute = GetElementData()->Attributes().Find(q_name); if (!attribute) return nullptr; return EnsureAttr(attribute->GetName()); } bool Element::hasAttribute(const AtomicString& local_name) const { if (!GetElementData()) return false; SynchronizeAttribute(local_name); return GetElementData()->Attributes().FindIndex( LowercaseIfNecessary(local_name)) != kNotFound; } bool Element::hasAttributeNS(const AtomicString& namespace_uri, const AtomicString& local_name) const { if (!GetElementData()) return false; QualifiedName q_name(g_null_atom, local_name, namespace_uri); SynchronizeAttribute(q_name); return GetElementData()->Attributes().Find(q_name); } void Element::focus(const FocusOptions* options) { focus(FocusParams(SelectionBehaviorOnFocus::kRestore, kWebFocusTypeNone, nullptr, options)); } void Element::focus(const FocusParams& params) { if (!isConnected()) return; if (!GetDocument().IsFocusAllowed()) return; if (GetDocument().FocusedElement() == this) return; if (!GetDocument().IsActive()) return; auto* frame_owner_element = DynamicTo(this); if (frame_owner_element && frame_owner_element->contentDocument() && frame_owner_element->contentDocument()->UnloadStarted()) return; DisplayLockUtilities::ScopedChainForcedUpdate scoped_update_forced(this); GetDocument().UpdateStyleAndLayoutTree(); if (!IsFocusable()) return; if (AuthorShadowRoot() && AuthorShadowRoot()->delegatesFocus()) { if (IsShadowIncludingInclusiveAncestorOf(GetDocument().FocusedElement())) return; // Slide the focus to its inner node. Element* found = GetDocument() .GetPage() ->GetFocusController() .FindFocusableElementInShadowHost(*this); if (found && IsShadowIncludingInclusiveAncestorOf(found)) { found->focus(FocusParams(SelectionBehaviorOnFocus::kReset, kWebFocusTypeForward, nullptr, params.options)); return; } } ActivateDisplayLockIfNeeded(); DispatchActivateInvisibleEventIfNeeded(); if (IsInsideInvisibleSubtree()) { // The element stays invisible because the default event action is // prevented.If this is navigating to the next focusable element, // we need to find a replacement and focus on it instead. Element* found = GetDocument().GetPage()->GetFocusController().FindFocusableElementAfter( *this, params.type); if (found) found->focus(params); return; } if (!GetDocument().GetPage()->GetFocusController().SetFocusedElement( this, GetDocument().GetFrame(), params)) return; if (GetDocument().FocusedElement() == this && GetDocument().GetFrame()->HasBeenActivated()) { // Bring up the keyboard in the context of anything triggered by a user // gesture. Since tracking that across arbitrary boundaries (eg. // animations) is difficult, for now we match IE's heuristic and bring // up the keyboard if there's been any gesture since load. GetDocument() .GetPage() ->GetChromeClient() .ShowVirtualKeyboardOnElementFocus(*GetDocument().GetFrame()); } } void Element::UpdateFocusAppearance( SelectionBehaviorOnFocus selection_behavior) { UpdateFocusAppearanceWithOptions(selection_behavior, FocusOptions::Create()); } void Element::UpdateFocusAppearanceWithOptions( SelectionBehaviorOnFocus selection_behavior, const FocusOptions* options) { if (selection_behavior == SelectionBehaviorOnFocus::kNone) return; if (IsRootEditableElement(*this)) { LocalFrame* frame = GetDocument().GetFrame(); if (!frame) return; // When focusing an editable element in an iframe, don't reset the selection // if it already contains a selection. if (this == frame->Selection() .ComputeVisibleSelectionInDOMTreeDeprecated() .RootEditableElement()) return; // FIXME: We should restore the previous selection if there is one. // Passing DoNotSetFocus as this function is called after // FocusController::setFocusedElement() and we don't want to change the // focus to a new Element. frame->Selection().SetSelection( SelectionInDOMTree::Builder() .Collapse(FirstPositionInOrBeforeNode(*this)) .Build(), SetSelectionOptions::Builder() .SetShouldCloseTyping(true) .SetShouldClearTypingStyle(true) .SetDoNotSetFocus(true) .Build()); if (!options->preventScroll()) frame->Selection().RevealSelection(); } else if (GetLayoutObject() && !GetLayoutObject()->IsLayoutEmbeddedContent()) { if (!options->preventScroll()) { GetLayoutObject()->ScrollRectToVisible(BoundingBoxForScrollIntoView(), WebScrollIntoViewParams()); } } } void Element::blur() { CancelFocusAppearanceUpdate(); if (AdjustedFocusedElementInTreeScope() == this) { Document& doc = GetDocument(); if (doc.GetPage()) { doc.GetPage()->GetFocusController().SetFocusedElement(nullptr, doc.GetFrame()); } else { doc.ClearFocusedElement(); } } } bool Element::SupportsFocus() const { // FIXME: supportsFocus() can be called when layout is not up to date. // Logic that deals with the layoutObject should be moved to // layoutObjectIsFocusable(). // But supportsFocus must return true when the element is editable, or else // it won't be focusable. Furthermore, supportsFocus cannot just return true // always or else tabIndex() will change for all HTML elements. return HasElementFlag(ElementFlags::kTabIndexWasSetExplicitly) || IsRootEditableElementWithCounting(*this) || (IsShadowHost(this) && AuthorShadowRoot() && AuthorShadowRoot()->delegatesFocus()) || SupportsSpatialNavigationFocus(); } bool Element::SupportsSpatialNavigationFocus() const { // This function checks whether the element satisfies the extended criteria // for the element to be focusable, introduced by spatial navigation feature, // i.e. checks if click or keyboard event handler is specified. // This is the way to make it possible to navigate to (focus) elements // which web designer meant for being active (made them respond to click // events). if (!IsSpatialNavigationEnabled(GetDocument().GetFrame())) return false; if (HasEventListeners(event_type_names::kClick) || HasEventListeners(event_type_names::kKeydown) || HasEventListeners(event_type_names::kKeypress) || HasEventListeners(event_type_names::kKeyup)) return true; if (!IsSVGElement()) return false; return (HasEventListeners(event_type_names::kFocus) || HasEventListeners(event_type_names::kBlur) || HasEventListeners(event_type_names::kFocusin) || HasEventListeners(event_type_names::kFocusout)); } bool Element::IsFocusable() const { return Element::IsMouseFocusable() || Element::IsKeyboardFocusable(); } bool Element::IsKeyboardFocusable() const { // No point in checking NeedsLayoutTreeUpdateForNode when the document // isn't active (style can't be invalidated in a non-active document). DCHECK(!GetDocument().IsActive() || !GetDocument().NeedsLayoutTreeUpdateForNode(*this)); return isConnected() && !IsInert() && IsFocusableStyle() && ((SupportsFocus() && tabIndex() >= 0) || (RuntimeEnabledFeatures::KeyboardFocusableScrollersEnabled() && IsScrollableNode(this))) && !DisplayLockPreventsActivation(); } bool Element::IsMouseFocusable() const { // No point in checking NeedsLayoutTreeUpdateForNode when the document // isn't active (style can't be invalidated in a non-active document). DCHECK(!GetDocument().IsActive() || !GetDocument().NeedsLayoutTreeUpdateForNode(*this)); return isConnected() && !IsInert() && IsFocusableStyle() && SupportsFocus() && !DisplayLockPreventsActivation(); } void Element::ActivateDisplayLockIfNeeded() { if (!RuntimeEnabledFeatures::DisplayLockingEnabled()) return; HeapVector, Member>> activatable_targets; for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*this)) { if (!ancestor.IsElementNode()) continue; if (auto* context = ToElement(ancestor).GetDisplayLockContext()) { // If any of the ancestors is not activatable, we can't activate. if (!context->IsActivatable()) return; activatable_targets.push_back(std::make_pair( &ToElement(ancestor), &ancestor.GetTreeScope().Retarget(*this))); } } for (const auto& target : activatable_targets) { // Dispatch event on activatable ancestor (target.first), with // the retargeted element (target.second) as the |activatedElement|. if (auto* context = target.first->GetDisplayLockContext()) { if (context->ShouldCommitForActivation()) { target.first->DispatchEvent( *MakeGarbageCollected(*target.second)); context->CommitForActivation(); } } } } bool Element::DisplayLockPreventsActivation() const { if (!RuntimeEnabledFeatures::DisplayLockingEnabled()) return false; if (GetDocument().ActivationBlockingDisplayLockCount() == 0) return false; // TODO(vmpstr): Similar to Document::EnsurePaintLocationDataValidForNode(), // this iterates up to the ancestor hierarchy looking for locked display // locks. This is inefficient, particularly since it's unlikely that this will // yield any "true" results in practice. We need to come up with a way to // check whether a node is in a locked subtree quickly. // See crbug.com/924550 for more details. for (const Node& current : FlatTreeTraversal::InclusiveAncestorsOf(*this)) { if (!current.IsElementNode()) continue; if (auto* context = ToElement(current).GetDisplayLockContext()) { if (!context->IsActivatable()) return true; } } return false; } bool Element::StyleShouldForceLegacyLayoutInternal() const { return GetElementRareData()->StyleShouldForceLegacyLayout(); } void Element::SetStyleShouldForceLegacyLayoutInternal(bool force) { EnsureElementRareData().SetStyleShouldForceLegacyLayout(force); } bool Element::ShouldForceLegacyLayoutForChildInternal() const { return GetElementRareData()->ShouldForceLegacyLayoutForChild(); } void Element::SetShouldForceLegacyLayoutForChildInternal(bool force) { EnsureElementRareData().SetShouldForceLegacyLayoutForChild(force); } void Element::UpdateForceLegacyLayout(const ComputedStyle& new_style, const ComputedStyle* old_style) { bool old_force = old_style && ShouldForceLegacyLayout(); SetStyleShouldForceLegacyLayout( CalculateStyleShouldForceLegacyLayout(*this, new_style)); if (ShouldForceLegacyLayout()) { if (!old_force) { if (const LayoutObject* layout_object = GetLayoutObject()) { // Forced legacy layout is inherited down the layout tree, so even if we // just decided here on the DOM side that we need forced legacy layout, // check with the LayoutObject whether this is news and that it really // needs to be reattached. if (!layout_object->ForceLegacyLayout()) SetNeedsReattachLayoutTree(); } } // Even if we also previously forced legacy layout, we may need to introduce // forced legacy layout in the ancestry, e.g. if this element no longer // establishes a new formatting context. ForceLegacyLayoutInFormattingContext(new_style); } else if (old_force) { // TODO(mstensho): If we have ancestors that got legacy layout just because // of this child, we should clean it up, and switch the subtree back to NG, // rather than being stuck with legacy forever. SetNeedsReattachLayoutTree(); } } void Element::ForceLegacyLayoutInFormattingContext( const ComputedStyle& new_style) { if (DefinitelyNewFormattingContext(*this, new_style)) return; bool found_bfc = false; for (Element* ancestor = this; !found_bfc;) { ancestor = ToElementOrNull(LayoutTreeBuilderTraversal::Parent(*ancestor)); if (!ancestor || ancestor->ShouldForceLegacyLayout()) break; const ComputedStyle* style = ancestor->GetComputedStyle(); if (style->Display() == EDisplay::kNone) break; found_bfc = DefinitelyNewFormattingContext(*ancestor, *style); ancestor->SetShouldForceLegacyLayoutForChild(true); ancestor->SetNeedsReattachLayoutTree(); } } bool Element::IsFocusedElementInDocument() const { return this == GetDocument().FocusedElement(); } Element* Element::AdjustedFocusedElementInTreeScope() const { return IsInTreeScope() ? ContainingTreeScope().AdjustedFocusedElement() : nullptr; } void Element::DispatchFocusEvent(Element* old_focused_element, WebFocusType type, InputDeviceCapabilities* source_capabilities) { DispatchEvent(*FocusEvent::Create( event_type_names::kFocus, Event::Bubbles::kNo, GetDocument().domWindow(), 0, old_focused_element, source_capabilities)); } void Element::DispatchBlurEvent(Element* new_focused_element, WebFocusType type, InputDeviceCapabilities* source_capabilities) { DispatchEvent(*FocusEvent::Create( event_type_names::kBlur, Event::Bubbles::kNo, GetDocument().domWindow(), 0, new_focused_element, source_capabilities)); } void Element::DispatchFocusInEvent( const AtomicString& event_type, Element* old_focused_element, WebFocusType, InputDeviceCapabilities* source_capabilities) { #if DCHECK_IS_ON() DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden()); #endif DCHECK(event_type == event_type_names::kFocusin || event_type == event_type_names::kDOMFocusIn); DispatchScopedEvent(*FocusEvent::Create( event_type, Event::Bubbles::kYes, GetDocument().domWindow(), 0, old_focused_element, source_capabilities)); } void Element::DispatchFocusOutEvent( const AtomicString& event_type, Element* new_focused_element, InputDeviceCapabilities* source_capabilities) { #if DCHECK_IS_ON() DCHECK(!EventDispatchForbiddenScope::IsEventDispatchForbidden()); #endif DCHECK(event_type == event_type_names::kFocusout || event_type == event_type_names::kDOMFocusOut); DispatchScopedEvent(*FocusEvent::Create( event_type, Event::Bubbles::kYes, GetDocument().domWindow(), 0, new_focused_element, source_capabilities)); } String Element::InnerHTMLAsString() const { return CreateMarkup(this, kChildrenOnly); } String Element::OuterHTMLAsString() const { return CreateMarkup(this); } void Element::innerHTML(StringOrTrustedHTML& result) const { result.SetString(InnerHTMLAsString()); } void Element::outerHTML(StringOrTrustedHTML& result) const { result.SetString(OuterHTMLAsString()); } void Element::SetInnerHTMLFromString(const String& html, ExceptionState& exception_state) { probe::BreakableLocation(&GetDocument(), "Element.setInnerHTML"); if (html.IsEmpty() && !HasNonInBodyInsertionMode()) { setTextContent(html); } else { if (DocumentFragment* fragment = CreateFragmentForInnerOuterHTML( html, this, kAllowScriptingContent, "innerHTML", exception_state)) { ContainerNode* container = this; if (auto* template_element = ToHTMLTemplateElementOrNull(*this)) container = template_element->content(); ReplaceChildrenWithFragment(container, fragment, exception_state); } } } void Element::SetInnerHTMLFromString(const String& html) { SetInnerHTMLFromString(html, ASSERT_NO_EXCEPTION); } void Element::setInnerHTML(const StringOrTrustedHTML& string_or_html, ExceptionState& exception_state) { String html = GetStringFromTrustedHTML(string_or_html, &GetDocument(), exception_state); if (!exception_state.HadException()) { SetInnerHTMLFromString(html, exception_state); } } void Element::setInnerHTML(const StringOrTrustedHTML& string_or_html) { setInnerHTML(string_or_html, ASSERT_NO_EXCEPTION); } void Element::SetOuterHTMLFromString(const String& html, ExceptionState& exception_state) { Node* p = parentNode(); if (!p) { exception_state.ThrowDOMException( DOMExceptionCode::kNoModificationAllowedError, "This element has no parent node."); return; } if (!p->IsElementNode()) { exception_state.ThrowDOMException( DOMExceptionCode::kNoModificationAllowedError, "This element's parent is of type '" + p->nodeName() + "', which is not an element node."); return; } Element* parent = ToElement(p); Node* prev = previousSibling(); Node* next = nextSibling(); DocumentFragment* fragment = CreateFragmentForInnerOuterHTML( html, parent, kAllowScriptingContent, "outerHTML", exception_state); if (exception_state.HadException()) return; parent->ReplaceChild(fragment, this, exception_state); Node* node = next ? next->previousSibling() : nullptr; if (!exception_state.HadException() && node && node->IsTextNode()) MergeWithNextTextNode(ToText(node), exception_state); if (!exception_state.HadException() && prev && prev->IsTextNode()) MergeWithNextTextNode(ToText(prev), exception_state); } void Element::setOuterHTML(const StringOrTrustedHTML& string_or_html, ExceptionState& exception_state) { String html = GetStringFromTrustedHTML(string_or_html, &GetDocument(), exception_state); if (!exception_state.HadException()) { SetOuterHTMLFromString(html, exception_state); } } Node* Element::InsertAdjacent(const String& where, Node* new_child, ExceptionState& exception_state) { if (DeprecatedEqualIgnoringCase(where, "beforeBegin")) { if (ContainerNode* parent = parentNode()) { parent->InsertBefore(new_child, this, exception_state); if (!exception_state.HadException()) return new_child; } return nullptr; } if (DeprecatedEqualIgnoringCase(where, "afterBegin")) { InsertBefore(new_child, firstChild(), exception_state); return exception_state.HadException() ? nullptr : new_child; } if (DeprecatedEqualIgnoringCase(where, "beforeEnd")) { AppendChild(new_child, exception_state); return exception_state.HadException() ? nullptr : new_child; } if (DeprecatedEqualIgnoringCase(where, "afterEnd")) { if (ContainerNode* parent = parentNode()) { parent->InsertBefore(new_child, nextSibling(), exception_state); if (!exception_state.HadException()) return new_child; } return nullptr; } exception_state.ThrowDOMException( DOMExceptionCode::kSyntaxError, "The value provided ('" + where + "') is not one of 'beforeBegin', 'afterBegin', " "'beforeEnd', or 'afterEnd'."); return nullptr; } ElementIntersectionObserverData* Element::IntersectionObserverData() const { if (HasRareData()) return GetElementRareData()->IntersectionObserverData(); return nullptr; } ElementIntersectionObserverData& Element::EnsureIntersectionObserverData() { return EnsureElementRareData().EnsureIntersectionObserverData(); } bool Element::ComputeIntersectionObservations(unsigned flags) { if (ElementIntersectionObserverData* data = IntersectionObserverData()) return data->ComputeObservations(flags); return false; } bool Element::NeedsOcclusionTracking() const { if (ElementIntersectionObserverData* data = IntersectionObserverData()) return data->NeedsOcclusionTracking(); return false; } HeapHashMap, Member>* Element::ResizeObserverData() const { if (HasRareData()) return GetElementRareData()->ResizeObserverData(); return nullptr; } HeapHashMap, Member>& Element::EnsureResizeObserverData() { return EnsureElementRareData().EnsureResizeObserverData(); } void Element::SetNeedsResizeObserverUpdate() { if (auto* data = ResizeObserverData()) { for (auto& observation : data->Values()) observation->ElementSizeChanged(); } } DisplayLockContext* Element::getDisplayLockForBindings() { return EnsureElementRareData().EnsureDisplayLockContext( this, GetExecutionContext()); } DisplayLockContext* Element::GetDisplayLockContext() const { if (!RuntimeEnabledFeatures::DisplayLockingEnabled()) return nullptr; return HasRareData() ? GetElementRareData()->GetDisplayLockContext() : nullptr; } // Step 1 of http://domparsing.spec.whatwg.org/#insertadjacenthtml() static Element* ContextElementForInsertion(const String& where, Element* element, ExceptionState& exception_state) { if (DeprecatedEqualIgnoringCase(where, "beforeBegin") || DeprecatedEqualIgnoringCase(where, "afterEnd")) { Element* parent = element->parentElement(); if (!parent) { exception_state.ThrowDOMException( DOMExceptionCode::kNoModificationAllowedError, "The element has no parent."); return nullptr; } return parent; } if (DeprecatedEqualIgnoringCase(where, "afterBegin") || DeprecatedEqualIgnoringCase(where, "beforeEnd")) return element; exception_state.ThrowDOMException( DOMExceptionCode::kSyntaxError, "The value provided ('" + where + "') is not one of 'beforeBegin', 'afterBegin', " "'beforeEnd', or 'afterEnd'."); return nullptr; } Element* Element::insertAdjacentElement(const String& where, Element* new_child, ExceptionState& exception_state) { Node* return_value = InsertAdjacent(where, new_child, exception_state); return ToElement(return_value); } void Element::insertAdjacentText(const String& where, const String& text, ExceptionState& exception_state) { InsertAdjacent(where, GetDocument().createTextNode(text), exception_state); } void Element::insertAdjacentHTML(const String& where, const String& markup, ExceptionState& exception_state) { Element* context_element = ContextElementForInsertion(where, this, exception_state); if (!context_element) return; DocumentFragment* fragment = CreateFragmentForInnerOuterHTML( markup, context_element, kAllowScriptingContent, "insertAdjacentHTML", exception_state); if (!fragment) return; InsertAdjacent(where, fragment, exception_state); } void Element::insertAdjacentHTML(const String& where, const StringOrTrustedHTML& string_or_html, ExceptionState& exception_state) { String markup = GetStringFromTrustedHTML(string_or_html, &GetDocument(), exception_state); if (!exception_state.HadException()) { insertAdjacentHTML(where, markup, exception_state); } } void Element::setPointerCapture(PointerId pointer_id, ExceptionState& exception_state) { if (GetDocument().GetFrame()) { if (!GetDocument().GetFrame()->GetEventHandler().IsPointerEventActive( pointer_id)) { exception_state.ThrowDOMException( DOMExceptionCode::kNotFoundError, "No active pointer with the given id is found."); } else if (!isConnected() || (GetDocument().GetPage() && GetDocument() .GetPage() ->GetPointerLockController() .GetElement())) { exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, "InvalidStateError"); } else { GetDocument().GetFrame()->GetEventHandler().SetPointerCapture(pointer_id, this); } } } void Element::releasePointerCapture(PointerId pointer_id, ExceptionState& exception_state) { if (GetDocument().GetFrame()) { if (!GetDocument().GetFrame()->GetEventHandler().IsPointerEventActive( pointer_id)) { exception_state.ThrowDOMException( DOMExceptionCode::kNotFoundError, "No active pointer with the given id is found."); } else { GetDocument().GetFrame()->GetEventHandler().ReleasePointerCapture( pointer_id, this); } } } bool Element::hasPointerCapture(PointerId pointer_id) const { return GetDocument().GetFrame() && GetDocument().GetFrame()->GetEventHandler().HasPointerCapture( pointer_id, this); } String Element::outerText() { // Getting outerText is the same as getting innerText, only // setting is different. You would think this should get the plain // text for the outer range, but this is wrong,
for instance // would return different values for inner and outer text by such // a rule, but it doesn't in WinIE, and we want to match that. return innerText(); } String Element::TextFromChildren() { Text* first_text_node = nullptr; bool found_multiple_text_nodes = false; unsigned total_length = 0; for (Node* child = firstChild(); child; child = child->nextSibling()) { if (!child->IsTextNode()) continue; Text* text = ToText(child); if (!first_text_node) first_text_node = text; else found_multiple_text_nodes = true; unsigned length = text->data().length(); if (length > std::numeric_limits::max() - total_length) return g_empty_string; total_length += length; } if (!first_text_node) return g_empty_string; if (first_text_node && !found_multiple_text_nodes) { first_text_node->MakeParkableOrAtomize(); return first_text_node->data(); } StringBuilder content; content.ReserveCapacity(total_length); for (Node* child = first_text_node; child; child = child->nextSibling()) { if (!child->IsTextNode()) continue; content.Append(ToText(child)->data()); } DCHECK_EQ(content.length(), total_length); return content.ToString(); } const AtomicString& Element::ShadowPseudoId() const { if (ShadowRoot* root = ContainingShadowRoot()) { if (root->IsUserAgent()) return FastGetAttribute(kPseudoAttr); } return g_null_atom; } void Element::SetShadowPseudoId(const AtomicString& id) { DCHECK(CSSSelector::ParsePseudoType(id, false) == CSSSelector::kPseudoWebKitCustomElement || CSSSelector::ParsePseudoType(id, false) == CSSSelector::kPseudoBlinkInternalElement); setAttribute(kPseudoAttr, id); } bool Element::IsInDescendantTreeOf(const Element* shadow_host) const { DCHECK(shadow_host); DCHECK(IsShadowHost(shadow_host)); for (const Element* ancestor_shadow_host = OwnerShadowHost(); ancestor_shadow_host; ancestor_shadow_host = ancestor_shadow_host->OwnerShadowHost()) { if (ancestor_shadow_host == shadow_host) return true; } return false; } const ComputedStyle* Element::EnsureComputedStyle( PseudoId pseudo_element_specifier) { if (PseudoElement* element = GetPseudoElement(pseudo_element_specifier)) return element->EnsureComputedStyle(); if (!InActiveDocument()) { // FIXME: Try to do better than this. Ensure that styleForElement() works // for elements that are not in the document tree and figure out when to // destroy the computed style for such elements. return nullptr; } // EnsureComputedStyle is expected to be called to forcibly compute style for // elements in display:none subtrees on otherwise style-clean documents. If // you hit this DCHECK, consider if you really need ComputedStyle for // display:none elements. If not, use GetComputedStyle() instead. // Regardlessly, you need to UpdateStyleAndLayoutTree() before calling // EnsureComputedStyle. In some cases you might be fine using GetComputedStyle // without updating the style, but in most cases you want a clean tree for // that as well. // // Adjacent styling bits may be set and affect NeedsLayoutTreeUpdateForNode as // part of EnsureComputedStyle in an ancestor chain. // (see CSSComputedStyleDeclarationTest::NeedsAdjacentStyleRecalc). It is OK // that it happens, but we need to ignore the effect on // NeedsLayoutTreeUpdateForNode here. DCHECK(!GetDocument().NeedsLayoutTreeUpdateForNode( *this, true /* ignore_adjacent_style */)); // FIXME: Find and use the layoutObject from the pseudo element instead of the // actual element so that the 'length' properties, which are only known by the // layoutObject because it did the layout, will be correct and so that the // values returned for the ":selection" pseudo-element will be correct. ComputedStyle* element_style = MutableComputedStyle(); if (!element_style) { if (CanParticipateInFlatTree()) { ContainerNode* parent = LayoutTreeBuilderTraversal::Parent(*this); if (parent) parent->EnsureComputedStyle(); ContainerNode* layout_parent = parent ? LayoutTreeBuilderTraversal::LayoutParent(*this) : nullptr; if (layout_parent) layout_parent->EnsureComputedStyle(); } scoped_refptr new_style = HasCustomStyleCallbacks() ? CustomStyleForLayoutObject() : OriginalStyleForLayoutObject(); element_style = new_style.get(); element_style->SetIsEnsuredInDisplayNone(); SetComputedStyle(std::move(new_style)); } if (!pseudo_element_specifier) return element_style; if (const ComputedStyle* pseudo_element_style = element_style->GetCachedPseudoStyle(pseudo_element_specifier)) return pseudo_element_style; const ComputedStyle* layout_parent_style = element_style; if (HasDisplayContentsStyle()) { LayoutObject* parent_layout_object = LayoutTreeBuilderTraversal::ParentLayoutObject(*this); if (parent_layout_object) layout_parent_style = parent_layout_object->Style(); } scoped_refptr result = GetDocument().EnsureStyleResolver().PseudoStyleForElement( this, PseudoStyleRequest(pseudo_element_specifier, PseudoStyleRequest::kForComputedStyle), element_style, layout_parent_style); DCHECK(result); result->SetIsEnsuredInDisplayNone(); return element_style->AddCachedPseudoStyle(std::move(result)); } bool Element::HasDisplayContentsStyle() const { if (const ComputedStyle* style = GetComputedStyle()) return style->Display() == EDisplay::kContents; return false; } bool Element::ShouldStoreComputedStyle(const ComputedStyle& style) const { if (LayoutObjectIsNeeded(style)) return true; if (IsSVGElement()) { if (!ToSVGElement(*this).HasSVGParent()) return false; if (IsSVGStopElement(*this)) return true; } return style.Display() == EDisplay::kContents; } AtomicString Element::ComputeInheritedLanguage() const { const Node* n = this; AtomicString value; // The language property is inherited, so we iterate over the parents to find // the first language. do { if (n->IsElementNode()) { if (const ElementData* element_data = ToElement(n)->GetElementData()) { AttributeCollection attributes = element_data->Attributes(); // Spec: xml:lang takes precedence -- http://www.w3.org/TR/xhtml1/#C_7 if (const Attribute* attribute = attributes.Find(xml_names::kLangAttr)) { value = attribute->Value(); } else { attribute = attributes.Find(html_names::kLangAttr); if (attribute) value = attribute->Value(); } } } else if (auto* document = DynamicTo(n)) { // checking the MIME content-language value = document->ContentLanguage(); } n = n->ParentOrShadowHostNode(); } while (n && value.IsNull()); return value; } Locale& Element::GetLocale() const { return GetDocument().GetCachedLocale(ComputeInheritedLanguage()); } void Element::CancelFocusAppearanceUpdate() { if (GetDocument().FocusedElement() == this) GetDocument().CancelFocusAppearanceUpdate(); } void Element::UpdateFirstLetterPseudoElement(StyleUpdatePhase phase) { // Update the ::first-letter pseudo elements presence and its style. This // method may be called from style recalc or layout tree rebuilding/ // reattachment. In order to know if an element generates a ::first-letter // element, we need to know if: // // * The element generates a block level box to which ::first-letter applies. // * The element's layout subtree generates any first letter text. // * None of the descendant blocks generate a ::first-letter element. // (This is not correct according to spec as all block containers should be // able to generate ::first-letter elements around the first letter of the // first formatted text, but Blink is only supporting a single // ::first-letter element which is the innermost block generating a // ::first-letter). // // We do not always do this at style recalc time as that would have required // us to collect the information about how the layout tree will look like // after the layout tree is attached. So, instead we will wait until we have // an up-to-date layout sub-tree for the element we are considering for // ::first-letter. // // The StyleUpdatePhase tells where we are in the process of updating style // and layout tree. PseudoElement* element = GetPseudoElement(kPseudoIdFirstLetter); if (!element) { element = CreatePseudoElementIfNeeded(kPseudoIdFirstLetter); // If we are in Element::AttachLayoutTree, don't mess up the ancestor flags // for layout tree attachment/rebuilding. We will unconditionally call // AttachLayoutTree for the created pseudo element immediately after this // call. if (element && phase != StyleUpdatePhase::kAttachLayoutTree) element->SetNeedsReattachLayoutTree(); return; } if (!CanGeneratePseudoElement(kPseudoIdFirstLetter)) { GetElementRareData()->SetPseudoElement(kPseudoIdFirstLetter, nullptr); return; } LayoutObject* remaining_text_layout_object = FirstLetterPseudoElement::FirstLetterTextLayoutObject(*element); if (!remaining_text_layout_object) { GetElementRareData()->SetPseudoElement(kPseudoIdFirstLetter, nullptr); return; } if (phase == StyleUpdatePhase::kRebuildLayoutTree && element->NeedsReattachLayoutTree()) { // We were already updated in RecalcStyle and ready for reattach. DCHECK(element->GetComputedStyle()); return; } bool text_node_changed = remaining_text_layout_object != ToFirstLetterPseudoElement(element)->RemainingTextLayoutObject(); if (phase == StyleUpdatePhase::kAttachLayoutTree) { // RemainingTextLayoutObject should have been cleared from DetachLayoutTree. DCHECK(!ToFirstLetterPseudoElement(element)->RemainingTextLayoutObject()); DCHECK(text_node_changed); scoped_refptr pseudo_style = element->StyleForLayoutObject(); if (PseudoElementLayoutObjectIsNeeded(pseudo_style.get())) element->SetComputedStyle(std::move(pseudo_style)); else GetElementRareData()->SetPseudoElement(kPseudoIdFirstLetter, nullptr); return; } StyleRecalcChange change(StyleRecalcChange::kRecalcDescendants); if (text_node_changed) change = change.ForceReattachLayoutTree(); element->RecalcStyle(change); if (element->NeedsReattachLayoutTree() && !PseudoElementLayoutObjectIsNeeded(element->GetComputedStyle())) { GetElementRareData()->SetPseudoElement(kPseudoIdFirstLetter, nullptr); } } void Element::UpdatePseudoElement(PseudoId pseudo_id, const StyleRecalcChange change) { PseudoElement* element = GetPseudoElement(pseudo_id); if (!element) { if ((element = CreatePseudoElementIfNeeded(pseudo_id))) element->SetNeedsReattachLayoutTree(); return; } if (change.ShouldUpdatePseudoElement(*element)) { if (CanGeneratePseudoElement(pseudo_id)) { element->RecalcStyle(change.ForChildren().ForceRecalcDescendants()); if (!element->NeedsReattachLayoutTree()) return; if (PseudoElementLayoutObjectIsNeeded(element->GetComputedStyle())) return; } GetElementRareData()->SetPseudoElement(pseudo_id, nullptr); } } PseudoElement* Element::CreatePseudoElementIfNeeded(PseudoId pseudo_id) { if (IsPseudoElement()) return nullptr; if (!CanGeneratePseudoElement(pseudo_id)) return nullptr; if (pseudo_id == kPseudoIdFirstLetter) { if (!FirstLetterPseudoElement::FirstLetterTextLayoutObject(*this)) return nullptr; } PseudoElement* pseudo_element = PseudoElement::Create(this, pseudo_id); EnsureElementRareData().SetPseudoElement(pseudo_id, pseudo_element); pseudo_element->InsertedInto(*this); scoped_refptr pseudo_style = pseudo_element->StyleForLayoutObject(); if (!PseudoElementLayoutObjectIsNeeded(pseudo_style.get())) { GetElementRareData()->SetPseudoElement(pseudo_id, nullptr); return nullptr; } if (pseudo_id == kPseudoIdBackdrop) GetDocument().AddToTopLayer(pseudo_element, this); pseudo_element->SetComputedStyle(std::move(pseudo_style)); probe::PseudoElementCreated(pseudo_element); return pseudo_element; } void Element::AttachPseudoElement(PseudoId pseudo_id, AttachContext& context) { if (PseudoElement* pseudo_element = GetPseudoElement(pseudo_id)) pseudo_element->AttachLayoutTree(context); } void Element::DetachPseudoElement(PseudoId pseudo_id, const AttachContext& context) { if (PseudoElement* pseudo_element = GetPseudoElement(pseudo_id)) pseudo_element->DetachLayoutTree(context); } PseudoElement* Element::GetPseudoElement(PseudoId pseudo_id) const { return HasRareData() ? GetElementRareData()->GetPseudoElement(pseudo_id) : nullptr; } LayoutObject* Element::PseudoElementLayoutObject(PseudoId pseudo_id) const { if (PseudoElement* element = GetPseudoElement(pseudo_id)) return element->GetLayoutObject(); return nullptr; } const ComputedStyle* Element::CachedStyleForPseudoElement( const PseudoStyleRequest& request, const ComputedStyle* parent_style) { ComputedStyle* style = MutableComputedStyle(); if (!style || (request.pseudo_id < kFirstInternalPseudoId && !style->HasPseudoStyle(request.pseudo_id))) { return nullptr; } if (const ComputedStyle* cached = style->GetCachedPseudoStyle(request.pseudo_id)) return cached; scoped_refptr result = StyleForPseudoElement(request, parent_style); if (result) return style->AddCachedPseudoStyle(std::move(result)); return nullptr; } scoped_refptr Element::StyleForPseudoElement( const PseudoStyleRequest& request, const ComputedStyle* parent_style) { const ComputedStyle* style = GetComputedStyle(); const bool is_before_or_after = request.pseudo_id == kPseudoIdBefore || request.pseudo_id == kPseudoIdAfter; DCHECK(style); DCHECK(!parent_style || !is_before_or_after); if (is_before_or_after) { const ComputedStyle* layout_parent_style = style; if (style->Display() == EDisplay::kContents) { // TODO(futhark@chromium.org): Calling getComputedStyle for elements // outside the flat tree should return empty styles, but currently we do // not. See issue https://crbug.com/831568. We can replace the if-test // with DCHECK(layout_parent) when that issue is fixed. if (Node* layout_parent = LayoutTreeBuilderTraversal::LayoutParent(*this)) { layout_parent_style = layout_parent->GetComputedStyle(); } } return GetDocument().EnsureStyleResolver().PseudoStyleForElement( this, request, style, layout_parent_style); } if (!parent_style) parent_style = style; if (request.pseudo_id == kPseudoIdFirstLineInherited) { scoped_refptr result = GetDocument().EnsureStyleResolver().StyleForElement(this, parent_style, parent_style); result->SetStyleType(kPseudoIdFirstLineInherited); return result; } return GetDocument().EnsureStyleResolver().PseudoStyleForElement( this, request, parent_style, parent_style); } bool Element::CanGeneratePseudoElement(PseudoId pseudo_id) const { if (pseudo_id == kPseudoIdBackdrop && !IsInTopLayer()) return false; if (pseudo_id == kPseudoIdFirstLetter && IsSVGElement()) return false; if (const ComputedStyle* style = GetComputedStyle()) return style->CanGeneratePseudoElement(pseudo_id); return false; } bool Element::MayTriggerVirtualKeyboard() const { return HasEditableStyle(*this); } bool Element::matches(const AtomicString& selectors, ExceptionState& exception_state) { SelectorQuery* selector_query = GetDocument().GetSelectorQueryCache().Add( selectors, GetDocument(), exception_state); if (!selector_query) return false; return selector_query->Matches(*this); } bool Element::matches(const AtomicString& selectors) { return matches(selectors, ASSERT_NO_EXCEPTION); } Element* Element::closest(const AtomicString& selectors, ExceptionState& exception_state) { SelectorQuery* selector_query = GetDocument().GetSelectorQueryCache().Add( selectors, GetDocument(), exception_state); if (!selector_query) return nullptr; return selector_query->Closest(*this); } Element* Element::closest(const AtomicString& selectors) { return closest(selectors, ASSERT_NO_EXCEPTION); } DOMTokenList& Element::classList() { ElementRareData& rare_data = EnsureElementRareData(); if (!rare_data.GetClassList()) { auto* class_list = MakeGarbageCollected(*this, kClassAttr); class_list->DidUpdateAttributeValue(g_null_atom, getAttribute(kClassAttr)); rare_data.SetClassList(class_list); } return *rare_data.GetClassList(); } DOMStringMap& Element::dataset() { ElementRareData& rare_data = EnsureElementRareData(); if (!rare_data.Dataset()) rare_data.SetDataset(MakeGarbageCollected(this)); return *rare_data.Dataset(); } KURL Element::HrefURL() const { // FIXME: These all have href() or url(), but no common super class. Why // doesn't implement URLUtils? if (IsHTMLAnchorElement(*this) || IsHTMLAreaElement(*this) || IsHTMLLinkElement(*this)) return GetURLAttribute(kHrefAttr); if (auto* svg_a = ToSVGAElementOrNull(*this)) return svg_a->LegacyHrefURL(GetDocument()); return KURL(); } KURL Element::GetURLAttribute(const QualifiedName& name) const { #if DCHECK_IS_ON() if (GetElementData()) { if (const Attribute* attribute = Attributes().Find(name)) DCHECK(IsURLAttribute(*attribute)); } #endif return GetDocument().CompleteURL( StripLeadingAndTrailingHTMLSpaces(getAttribute(name))); } void Element::GetURLAttribute(const QualifiedName& name, StringOrTrustedScriptURL& result) const { KURL url = GetURLAttribute(name); result.SetString(url.GetString()); } void Element::GetURLAttribute(const QualifiedName& name, USVStringOrTrustedURL& result) const { String url = GetURLAttribute(name); result.SetUSVString(url); } void Element::FastGetAttribute(const QualifiedName& name, USVStringOrTrustedURL& result) const { String attr = FastGetAttribute(name); result.SetUSVString(attr); } void Element::FastGetAttribute(const QualifiedName& name, StringOrTrustedHTML& result) const { String html = FastGetAttribute(name); result.SetString(html); } KURL Element::GetNonEmptyURLAttribute(const QualifiedName& name) const { #if DCHECK_IS_ON() if (GetElementData()) { if (const Attribute* attribute = Attributes().Find(name)) DCHECK(IsURLAttribute(*attribute)); } #endif String value = StripLeadingAndTrailingHTMLSpaces(getAttribute(name)); if (value.IsEmpty()) return KURL(); return GetDocument().CompleteURL(value); } int Element::GetIntegralAttribute(const QualifiedName& attribute_name) const { int integral_value = 0; ParseHTMLInteger(getAttribute(attribute_name), integral_value); return integral_value; } void Element::SetIntegralAttribute(const QualifiedName& attribute_name, int value) { setAttribute(attribute_name, AtomicString::Number(value)); } void Element::SetUnsignedIntegralAttribute(const QualifiedName& attribute_name, unsigned value, unsigned default_value) { // Range restrictions are enforced for unsigned IDL attributes that // reflect content attributes, // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes if (value > 0x7fffffffu) value = default_value; setAttribute(attribute_name, AtomicString::Number(value)); } double Element::GetFloatingPointAttribute(const QualifiedName& attribute_name, double fallback_value) const { return ParseToDoubleForNumberType(getAttribute(attribute_name), fallback_value); } void Element::SetFloatingPointAttribute(const QualifiedName& attribute_name, double value) { String serialized_value = SerializeForNumberType(value); setAttribute(attribute_name, AtomicString(serialized_value)); } void Element::SetContainsFullScreenElement(bool flag) { SetElementFlag(ElementFlags::kContainsFullScreenElement, flag); // When exiting fullscreen, the element's document may not be active. if (flag) { DCHECK(GetDocument().IsActive()); GetDocument().GetStyleEngine().EnsureUAStyleForFullscreen(); } PseudoStateChanged(CSSSelector::kPseudoFullScreenAncestor); } // Unlike Node::parentOrShadowHostElement, this can cross frame boundaries. static Element* NextAncestorElement(Element* element) { DCHECK(element); if (element->ParentOrShadowHostElement()) return element->ParentOrShadowHostElement(); Frame* frame = element->GetDocument().GetFrame(); if (!frame || !frame->Owner()) return nullptr; // Find the next LocalFrame on the ancestor chain, and return the // corresponding