| /* |
| * 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 <memory> |
| |
| #include "cc/input/scroll_snap_data.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/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 { |
| |
| 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; |
| } |
| |
| } // namespace |
| |
| Element* Element::Create(const QualifiedName& tag_name, Document* document) { |
| return MakeGarbageCollected<Element>(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<ElementRareData*>(RareData()); |
| } |
| |
| inline ElementRareData& Element::EnsureElementRareData() { |
| return static_cast<ElementRareData&>(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<HTMLCanvasElement>::FirstAncestorOrSelf(*this); |
| DCHECK(canvas); |
| return canvas->GetLayoutObject() && |
| canvas->GetLayoutObject()->Style()->Visibility() == |
| EVisibility::kVisible; |
| } |
| |
| if (IsInsideInvisibleSubtree()) { |
| const ComputedStyle* style = |
| const_cast<Element*>(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 = |
| Attr::Create(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<Element*>(this)->EnsureElementRareData(); |
| if (NamedNodeMap* attribute_map = rare_data.AttributeMap()) |
| return attribute_map; |
| |
| rare_data.SetAttributeMap(NamedNodeMap::Create(const_cast<Element*>(this))); |
| return rare_data.AttributeMap(); |
| } |
| |
| Vector<AtomicString> Element::getAttributeNames() const { |
| Vector<AtomicString> 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<ElementAnimations>()); |
| 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) { |
| 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, false, 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, kProgrammaticScroll, false}); |
| } else { |
| GetLayoutObject()->ScrollRectToVisible( |
| bounds, |
| {ScrollAlignment::kAlignToEdgeIfNeeded, |
| ScrollAlignment::kAlignToEdgeIfNeeded, kProgrammaticScroll, false}); |
| } |
| } |
| |
| 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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this); |
| |
| LayoutObject* layout_object = GetLayoutObject(); |
| return layout_object ? layout_object->OffsetParent() : nullptr; |
| } |
| |
| int Element::clientLeft() { |
| GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this); |
| |
| if (LayoutBox* layout_object = GetLayoutBox()) |
| return AdjustForAbsoluteZoom::AdjustLayoutUnit(layout_object->ClientLeft(), |
| layout_object->StyleRef()) |
| .Round(); |
| return 0; |
| } |
| |
| int Element::clientTop() { |
| GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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<cc::SnapSelectionStrategy> 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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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<cc::SnapSelectionStrategy> 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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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().UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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<cc::SnapSelectionStrategy> 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<cc::SnapSelectionStrategy> 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<cc::SnapSelectionStrategy> 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<cc::SnapSelectionStrategy> 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); |
| } |
| |
| bool Element::HasNonEmptyLayoutSize() const { |
| GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets(); |
| |
| if (LayoutBoxModelObject* box = GetLayoutBoxModelObject()) |
| return box->HasNonEmptyLayoutSize(); |
| return false; |
| } |
| |
| IntRect Element::BoundsInViewport() const { |
| GetDocument().EnsurePaintLocationDataValidForNode(this); |
| |
| LocalFrameView* view = GetDocument().View(); |
| if (!view) |
| return IntRect(); |
| |
| Vector<FloatQuad> 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<FloatQuad>& 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<FloatQuad> 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<FloatQuad> 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.UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(this); |
| AXContext ax_context(document); |
| return ax_context.GetAXObjectCache().ComputedRoleForNode(this); |
| } |
| |
| String Element::computedName() { |
| Document& document = GetDocument(); |
| if (!document.IsActive()) |
| return String(); |
| document.UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(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<Member<Element>> invisible_ancestors; |
| HeapVector<Member<Element>> 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( |
| *ActivateInvisibleEvent::Create(*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) |
| cache->HandleAttributeChanged(name, this); |
| } |
| } |
| |
| 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 <typename CharacterType> |
| static inline ClassStringContent ClassStringHasClassName( |
| const CharacterType* characters, |
| unsigned length) { |
| DCHECK_GT(length, 0u); |
| |
| unsigned i = 0; |
| do { |
| if (IsNotHTMLSpace<CharacterType>(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(); |
| } |
| } |
| |
| 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>& 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>& 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) { |
| return LayoutObject::CreateObject(this, style); |
| } |
| |
| 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); |
| 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->Display() == EDisplay::kNone) && |
| !ChildNeedsReattachLayoutTree()) { |
| Node::AttachLayoutTree(context); |
| return; |
| } |
| |
| if (style) { |
| if (CanParticipateInFlatTree()) { |
| LayoutTreeBuilderForElement builder(*this, style); |
| builder.CreateLayoutObjectIfNeeded(); |
| } |
| } |
| |
| AttachContext children_context(context); |
| |
| 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<ComputedStyle> 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<ComputedStyle> 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<ComputedStyle> 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()); |
| |
| if (StyleRecalcBlockedByDisplayLock()) |
| 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); |
| } |
| |
| if (child_change.TraverseChildren(*this)) { |
| SelectorFilterParentScope filter_scope(*this); |
| if (ShadowRoot* root = GetShadowRoot()) { |
| if (child_change.TraverseChild(*root)) |
| root->RecalcStyle(child_change); |
| RecalcDescendantStyles(StyleRecalcChange::kClearEnsured); |
| } else { |
| RecalcDescendantStyles(child_change); |
| } |
| } |
| |
| 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); |
| } |
| |
| ClearChildNeedsStyleRecalc(); |
| |
| if (HasCustomStyleCallbacks()) |
| DidRecalcStyle(child_change); |
| NotifyDisplayLockDidRecalcStyle(); |
| } |
| |
| scoped_refptr<ComputedStyle> 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<ComputedStyle> 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<ComputedStyle> new_style; |
| scoped_refptr<const ComputedStyle> 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 (old_style && new_style && !change.RecalcChildren() && |
| old_style->HasChildDependentFlags()) { |
| new_style->CopyChildDependentFlagsFrom(*old_style); |
| } |
| |
| 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()); |
| |
| if (NeedsReattachLayoutTree()) { |
| AttachContext reattach_context; |
| ReattachLayoutTree(reattach_context); |
| whitespace_attacher.DidReattachElement(this, |
| reattach_context.previous_in_flow); |
| } else { |
| // 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: |
| // |
| // <style>#outer::first-letter {...}</style> |
| // <div id=outer><div id=inner style="display:none">Text</div></div> |
| // <script> outer.offsetTop; inner.style.display = "block" </script> |
| // |
| // 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<String> empty_vector; |
| const Vector<String>& old_callback_selectors = |
| old_style ? old_style->CallbackSelectors() : empty_vector; |
| const Vector<String>& 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()); |
| |
| ShadowRoot* shadow_root = ShadowRoot::Create(GetDocument(), type); |
| |
| if (type != ShadowRootType::V0) { |
| // 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.LazyReattachIfAttached() before setting a shadow |
| // root to the element because detach must use the original flat tree |
| // structure before attachShadow happens. |
| for (Node& child : NodeTraversal::ChildrenOf(*this)) |
| child.LazyReattachIfAttached(); |
| } |
| 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 <use> 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; |
| } |
|