blob: 2c19cf3e67b1b6035c2c9541bf29be3afdd232dc [file] [log] [blame]
/*
* 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;
}