blob: fde71e5bc39bdbe6530cf1f018697d3ed1d27f17 [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 <bitset>
#include <memory>
#include "cc/input/snap_selection_strategy.h"
#include "third_party/blink/public/platform/web_scroll_into_view_params.h"
#include "third_party/blink/renderer/bindings/core/v8/dictionary.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.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.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/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_numeric_literal_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/display_lock_context.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
#include "third_party/blink/renderer/core/display_lock/render_subtree_activation_event.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/pointer_lock_options.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/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_body_element.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_html_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/inspector/console_message.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/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/scrollbar_theme.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/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/hash_functions.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 {
enum class ClassStringContent { kEmpty, kWhiteSpaceOnly, kHasClasses };
namespace {
class DisplayLockStyleScope {
public:
DisplayLockStyleScope(Element* element) : element_(element) {
// Note that we don't store context as a member of this scope, since it may
// get created as part of element self style recalc.
auto* context = element->GetDisplayLockContext();
should_update_self_ =
!context || context->ShouldStyle(DisplayLockLifecycleTarget::kSelf);
}
~DisplayLockStyleScope() {
if (auto* context = element_->GetDisplayLockContext()) {
if (did_update_children_)
context->DidStyle(DisplayLockLifecycleTarget::kChildren);
}
}
bool ShouldUpdateSelfStyle() const { return should_update_self_; }
bool ShouldUpdateChildStyle() const {
// We can't calculate this on construction time, because the element's lock
// state may changes after self-style calculation ShouldStyle(children).
auto* context = element_->GetDisplayLockContext();
return !context ||
context->ShouldStyle(DisplayLockLifecycleTarget::kChildren);
}
void DidUpdateChildStyle() { did_update_children_ = true; }
void DidUpdateSelfStyle() {
DCHECK(should_update_self_);
if (auto* context = element_->GetDisplayLockContext())
context->DidStyle(DisplayLockLifecycleTarget::kSelf);
}
void NotifyUpdateWasBlocked(DisplayLockContext::StyleType style) {
DCHECK(!ShouldUpdateChildStyle());
// The only way to be blocked here is if we have a display lock context.
DCHECK(element_->GetDisplayLockContext());
element_->GetDisplayLockContext()->NotifyStyleRecalcWasBlocked(style);
}
private:
UntracedMember<Element> element_;
bool should_update_self_ = false;
bool did_update_children_ = false;
};
bool IsRootEditableElementWithCounting(const Element& element) {
bool is_editable = IsRootEditableElement(element);
Document& doc = element.GetDocument();
if (!doc.IsActive())
return is_editable;
// -webkit-user-modify doesn't affect text control elements.
if (element.IsTextControl())
return is_editable;
const auto* style = element.GetComputedStyle();
if (!style)
return is_editable;
auto user_modify = style->UserModify();
const AtomicString& ce_value =
element.FastGetAttribute(html_names::kContenteditableAttr);
if (ce_value.IsNull() || DeprecatedEqualIgnoringCase(ce_value, "false")) {
if (user_modify == EUserModify::kReadWritePlaintextOnly) {
UseCounter::Count(doc, WebFeature::kPlainTextEditingEffective);
UseCounter::Count(doc, WebFeature::kWebKitUserModifyPlainTextEffective);
UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective);
} else if (user_modify == EUserModify::kReadWrite) {
UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadWriteEffective);
UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective);
}
} else if (ce_value.IsEmpty() ||
DeprecatedEqualIgnoringCase(ce_value, "true")) {
if (user_modify == EUserModify::kReadWritePlaintextOnly) {
UseCounter::Count(doc, WebFeature::kPlainTextEditingEffective);
UseCounter::Count(doc, WebFeature::kWebKitUserModifyPlainTextEffective);
UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective);
} else if (user_modify == EUserModify::kReadOnly) {
UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadOnlyEffective);
UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective);
}
} else if (DeprecatedEqualIgnoringCase(ce_value, "plaintext-only")) {
UseCounter::Count(doc, WebFeature::kPlainTextEditingEffective);
if (user_modify == EUserModify::kReadWrite) {
UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadWriteEffective);
UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective);
} else if (user_modify == EUserModify::kReadOnly) {
UseCounter::Count(doc, WebFeature::kWebKitUserModifyReadOnlyEffective);
UseCounter::Count(doc, WebFeature::kWebKitUserModifyEffective);
}
}
return is_editable;
}
// Return true if we're absolutely sure that this node is going to establish a
// new formatting context. Whether or not it establishes a new formatting
// context cannot be accurately determined until we have actually created the
// object (see LayoutBlockFlow::CreatesNewFormattingContext()), so this function
// may (and is allowed to) return false negatives, but NEVER false positives.
bool DefinitelyNewFormattingContext(const Node& node,
const ComputedStyle& style) {
auto display = style.Display();
if (display == EDisplay::kInline || display == EDisplay::kContents)
return false;
// The only block-container display types that potentially don't establish a
// new formatting context, are 'block' and 'list-item'.
if (display != EDisplay::kBlock && display != EDisplay::kListItem) {
// DETAILS and SUMMARY elements partially or completely ignore the display
// type, though, and may end up disregarding the display type and just
// create block containers. And those don't necessarily create a formatting
// context.
if (!IsA<HTMLDetailsElement>(node) && !IsHTMLSummaryElement(node))
return true;
}
if (!style.IsOverflowVisible())
return node.GetDocument().ViewportDefiningElement() != &node;
if (style.HasOutOfFlowPosition() || style.IsFloating() ||
style.ContainsPaint() || style.ContainsLayout() ||
style.SpecifiesColumns())
return true;
if (node.GetDocument().documentElement() == &node)
return true;
if (const Element* element = DynamicTo<Element>(&node)) {
// Replaced elements are considered to create a new formatting context, in
// the sense that they can't possibly have children that participate in the
// same formatting context as their parent.
if (IsHTMLObjectElement(element)) {
// OBJECT elements are special, though. If they use fallback content, they
// act as regular elements, and we can't claim that they establish a
// formatting context, just based on element type, since children may very
// well participate in the same formatting context as the parent of the
// OBJECT.
if (!element->ChildrenCanHaveStyle())
return true;
} else if (IsHTMLImageElement(element) || element->IsFormControlElement() ||
element->IsMediaElement() || element->IsFrameOwnerElement()) {
return true;
}
}
if (const Node* parent = LayoutTreeBuilderTraversal::LayoutParent(node))
return parent->ComputedStyleRef().IsDisplayFlexibleOrGridBox();
return false;
}
bool CalculateStyleShouldForceLegacyLayout(const Element& element,
const ComputedStyle& style) {
const Document& document = element.GetDocument();
if (style.Display() == EDisplay::kLayoutCustom ||
style.Display() == EDisplay::kInlineLayoutCustom)
return false;
// TODO(layout-dev): Once LayoutNG handles inline content editable, we
// should get rid of following code fragment.
if (!RuntimeEnabledFeatures::EditingNGEnabled()) {
if (style.UserModify() != EUserModify::kReadOnly || document.InDesignMode())
return true;
}
if (style.IsDeprecatedWebkitBox())
return true;
if (!RuntimeEnabledFeatures::LayoutNGBlockFragmentationEnabled()) {
// Disable NG for the entire subtree if we're establishing a block
// fragmentation context.
if (style.SpecifiesColumns())
return true;
if (document.Printing() && element == document.documentElement())
return true;
// Fall back to legacy layout for frameset documents. The frameset itself
// (and the frames) can only create legacy layout objects anyway (no NG
// counterpart for them yet). However, the layout object for the HTML root
// element would be an NG one. If we'd then print the document, we'd fall
// back to legacy layout (because of the above check), which would re-attach
// all layout objects, which would cause the frameset to lose state of some
// sort, leaving everything blank when printed.
if (document.IsFrameSet())
return true;
}
// 'text-combine-upright' property is not supported yet.
if (style.HasTextCombine() && !style.IsHorizontalWritingMode())
return true;
if (style.InsideNGFragmentationContext()) {
// If we're inside an NG block fragmentation context, all fragmentable boxes
// must be laid out by NG natively. We only allow legacy layout objects if
// they are monolithic (e.g. replaced content, inline-table, and so
// on). Inline display types end up on a line, and are therefore monolithic,
// so we can allow those.
if (!style.IsDisplayInlineType()) {
if (style.IsDisplayTableType() || style.IsDisplayFlexibleOrGridBox())
return true;
}
}
return false;
}
bool HasLeftwardDirection(const Element& element) {
auto* style = element.GetComputedStyle();
if (!style)
return false;
WritingMode writing_mode = style->GetWritingMode();
bool is_rtl = !style->IsLeftToRightDirection();
return (writing_mode == WritingMode::kHorizontalTb && is_rtl) ||
writing_mode == WritingMode::kVerticalRl ||
writing_mode == WritingMode::kSidewaysRl;
}
bool HasUpwardDirection(const Element& element) {
auto* style = element.GetComputedStyle();
if (!style)
return false;
WritingMode writing_mode = style->GetWritingMode();
bool is_rtl = !style->IsLeftToRightDirection();
return (is_rtl && (writing_mode == WritingMode::kVerticalRl ||
writing_mode == WritingMode::kVerticalLr ||
writing_mode == WritingMode::kSidewaysRl)) ||
(!is_rtl && writing_mode == WritingMode::kSidewaysLr);
}
// TODO(meredithl): Automatically generate this method once the IDL compiler has
// been refactored. See http://crbug.com/839389 for details.
bool IsElementReflectionAttribute(const QualifiedName& name) {
if (name == html_names::kAriaActivedescendantAttr)
return true;
if (name == html_names::kAriaControlsAttr)
return true;
if (name == html_names::kAriaDescribedbyAttr)
return true;
if (name == html_names::kAriaDetailsAttr)
return true;
if (name == html_names::kAriaErrormessageAttr)
return true;
if (name == html_names::kAriaFlowtoAttr)
return true;
if (name == html_names::kAriaLabelledbyAttr)
return true;
if (name == html_names::kAriaOwnsAttr)
return true;
return false;
}
HeapVector<Member<Element>>* GetExplicitlySetElementsForAttr(
Element* element,
QualifiedName name) {
ExplicitlySetAttrElementsMap* element_attribute_map =
element->GetDocument().GetExplicitlySetAttrElementsMap(element);
return element_attribute_map->at(name);
}
// Checks that the given element |candidate| is a descendant of
// |attribute_element|'s shadow including ancestors.
bool ElementIsDescendantOfShadowIncludingAncestor(
const Element& attribute_element,
const Element& candidate) {
// TODO(meredithl): Update this to allow setting relationships for elements
// outside of the DOM once the spec is finalized. For consistency and
// simplicity, for now it is disallowed.
if (!attribute_element.IsInTreeScope() || !candidate.IsInTreeScope())
return false;
ShadowRoot* nearest_root = attribute_element.ContainingShadowRoot();
const Element* shadow_host = &attribute_element;
while (nearest_root) {
shadow_host = &nearest_root->host();
if (candidate.IsDescendantOf(nearest_root))
return true;
nearest_root = shadow_host->ContainingShadowRoot();
}
Element* document_element = shadow_host->GetDocument().documentElement();
return candidate.IsDescendantOf(document_element);
}
// The first algorithm in
// https://html.spec.whatwg.org/C/#the-autofocus-attribute
void EnqueueAutofocus(Element& element) {
// When an element with the autofocus attribute specified is inserted into a
// document, run the following steps:
DCHECK(element.isConnected());
if (!element.IsAutofocusable())
return;
// 1. If the user has indicated (for example, by starting to type in a form
// control) that they do not wish focus to be changed, then optionally return.
// We don't implement this optional step. If other browsers have such
// behavior, we should follow it or standardize it.
// 2. Let target be the element's node document.
Document& doc = element.GetDocument();
// 3. If target's browsing context is null, then return.
if (!doc.GetFrame())
return;
// 4. If target's active sandboxing flag set has the sandboxed automatic
// features browsing context flag, then return.
if (doc.IsSandboxed(WebSandboxFlags::kAutomaticFeatures)) {
doc.AddConsoleMessage(ConsoleMessage::Create(
mojom::ConsoleMessageSource::kSecurity,
mojom::ConsoleMessageLevel::kError,
"Blocked autofocusing on a form control because the form's frame is "
"sandboxed and the 'allow-scripts' permission is not set."));
return;
}
// 5. Let topDocument be the active document of target's browsing context's
// top-level browsing context.
// 6. If target's origin is not the same as the origin of topDocument,
// then return.
if (!doc.IsInMainFrame() &&
!doc.TopFrameOrigin()->CanAccess(doc.GetSecurityOrigin())) {
doc.AddConsoleMessage(ConsoleMessage::Create(
mojom::ConsoleMessageSource::kSecurity,
mojom::ConsoleMessageLevel::kError,
"Blocked autofocusing on a form control in a cross-origin subframe."));
return;
}
element.GetDocument().TopDocument().EnqueueAutofocusCandidate(element);
}
} // namespace
Element::Element(const QualifiedName& tag_name,
Document* document,
ConstructionType type)
: ContainerNode(document, type), tag_name_(tag_name) {}
Element* Element::GetAnimationTarget() {
return this;
}
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(html_names::kTabindexAttr, value);
}
int Element::tabIndex() const {
// https://html.spec.whatwg.org/C/#dom-tabindex
// The tabIndex IDL attribute must reflect the value of the tabindex content
// attribute. The default value is 0 if the element is an a, area, button,
// frame, iframe, input, object, select, textarea, or SVG a element, or is a
// summary element that is a summary for its parent details. The default value
// is −1 otherwise.
return GetIntegralAttribute(html_names::kTabindexAttr, DefaultTabIndex());
}
int Element::DefaultTabIndex() const {
return -1;
}
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;
}
// Update style if we're in a display-locked subtree, because it isn't
// included in the normal style updates. We also need to update the layout
// here because some callers expect the layout stays clean.
if (DisplayLockUtilities::NearestLockedExclusiveAncestor(*this))
GetDocument().UpdateStyleAndLayoutForNode(this);
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 = MakeGarbageCollected<Attr>(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);
}
void Element::SynchronizeContentAttributeAndElementReference(
const QualifiedName& name) {
ExplicitlySetAttrElementsMap* element_attribute_map =
GetDocument().GetExplicitlySetAttrElementsMap(this);
element_attribute_map->erase(name);
}
void Element::SetElementAttribute(const QualifiedName& name, Element* element) {
ExplicitlySetAttrElementsMap* explicitly_set_attr_elements_map_ =
GetDocument().GetExplicitlySetAttrElementsMap(this);
// If the reflected element is explicitly null, or is not a member of this
// elements shadow including ancestor tree, then we remove the content
// attribute and the explicitly set attr-element.
// Note this means that explicitly set elements can cross ancestral shadow
// boundaries, but not descendant ones. See the spec for more details:
// https://whatpr.org/html/3917/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes:concept-shadow-including-ancestor
if (!element ||
!ElementIsDescendantOfShadowIncludingAncestor(*this, *element)) {
explicitly_set_attr_elements_map_->erase(name);
removeAttribute(name);
return;
}
const AtomicString id = element->GetIdAttribute();
// Explicitly set attr-elements must have a valid id attribute, and also
// refer to the first element in tree order of |this| elements node tree in
// order for the content attribute to reflect the ID. Where these conditions
// aren't met, the content attribute should reflect the empty string. Note
// that the explicitly set attr-element is still set. See the spec for more
// details:
// https://whatpr.org/html/3917/common-dom-interfaces.html#reflecting-content-attributes-in-idl-attributes:root-2
if (id.IsNull() || GetTreeScope() != element->GetTreeScope() ||
GetTreeScope().getElementById(id) != element)
setAttribute(name, g_empty_atom);
else
setAttribute(name, id);
auto result = explicitly_set_attr_elements_map_->insert(name, nullptr);
if (result.is_new_entry) {
result.stored_value->value =
MakeGarbageCollected<HeapVector<Member<Element>>>();
} else {
result.stored_value->value->clear();
}
result.stored_value->value->push_back(element);
}
Element* Element::GetElementAttribute(const QualifiedName& name) {
HeapVector<Member<Element>>* element_attribute_vector =
GetExplicitlySetElementsForAttr(this, name);
if (element_attribute_vector) {
DCHECK_EQ(element_attribute_vector->size(), 1u);
Element* explicitly_set_element = element_attribute_vector->at(0);
// Only return the explicit element if it still exists in the same scope.
if (explicitly_set_element)
return explicitly_set_element;
}
// Compute the attr-associated element, this can be null.
AtomicString id = getAttribute(name);
if (id.IsNull())
return nullptr;
// Will return null if the id is empty.
return GetTreeScope().getElementById(id);
}
void Element::SetElementArrayAttribute(
const QualifiedName& name,
HeapVector<Member<Element>> given_elements,
bool is_null) {
ExplicitlySetAttrElementsMap* element_attribute_map =
GetDocument().GetExplicitlySetAttrElementsMap(this);
if (is_null) {
element_attribute_map->erase(name);
removeAttribute(name);
return;
}
// Get or create element array, and remove any pre-existing elements.
// Note that this performs two look ups on |name| within the map, as
// modifying the content attribute will cause the synchronization steps to
// be run which with modifies the hash map, and can cause a rehash.
HeapVector<Member<Element>>* elements = element_attribute_map->at(name);
if (!elements)
elements = MakeGarbageCollected<HeapVector<Member<Element>>>();
else
elements->clear();
SpaceSplitString value;
for (auto element : given_elements) {
// Elements that are not descendants of this element's shadow including
// ancestors are dropped.
if (!ElementIsDescendantOfShadowIncludingAncestor(*this, *element))
continue;
// If |value| is null, this means a previous element must have been invalid,
// and the content attribute should reflect the empty string, so we don't
// continue trying to compute it.
if (value.IsNull() && !elements->IsEmpty()) {
elements->push_back(element);
continue;
}
elements->push_back(element);
const AtomicString given_element_id = element->GetIdAttribute();
// We compute the content attribute string as a space separated string of
// the given |element| ids. Every |element| in |given_elements| must have an
// id, must be in the same tree scope and must be the first id in tree order
// with that id, otherwise the content attribute should reflect the empty
// string.
if (given_element_id.IsNull() ||
GetTreeScope() != element->GetTreeScope() ||
GetTreeScope().getElementById(given_element_id) != element) {
value.Clear();
continue;
}
// Whitespace between elements is added when the string is serialized.
value.Add(given_element_id);
}
setAttribute(name, value.SerializeToString());
element_attribute_map->Set(name, elements);
}
HeapVector<Member<Element>> Element::GetElementArrayAttribute(
const QualifiedName& name,
bool& is_null) {
HeapVector<Member<Element>>* explicitly_set_elements =
GetExplicitlySetElementsForAttr(this, name);
is_null = false;
if (explicitly_set_elements) {
return *explicitly_set_elements;
}
String attribute_value = getAttribute(name).GetString();
HeapVector<Member<Element>> content_elements;
Vector<String> tokens;
attribute_value = attribute_value.SimplifyWhiteSpace();
attribute_value.Split(' ', tokens);
for (auto token : tokens) {
Element* candidate = GetTreeScope().getElementById(AtomicString(token));
if (candidate)
content_elements.push_back(candidate);
}
if (content_elements.IsEmpty())
is_null = true;
return content_elements;
}
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(
MakeGarbageCollected<NamedNodeMap>(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_)
To<SVGElement>(this)->SynchronizeAnimatedSVGAttribute(AnyQName());
}
inline void Element::SynchronizeAttribute(const QualifiedName& name) const {
if (!GetElementData())
return;
if (UNLIKELY(name == html_names::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.
To<SVGElement>(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) == html_names::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.
To<SVGElement>(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,
WritingMode writing_mode,
bool is_ltr) {
bool is_horizontal_writing_mode = IsHorizontalWritingMode(writing_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") {
if (axis == kHorizontalScroll) {
switch (writing_mode) {
case WritingMode::kHorizontalTb:
return is_ltr ? ScrollAlignment::kAlignLeftAlways
: ScrollAlignment::kAlignRightAlways;
case WritingMode::kVerticalRl:
case WritingMode::kSidewaysRl:
return ScrollAlignment::kAlignRightAlways;
case WritingMode::kVerticalLr:
case WritingMode::kSidewaysLr:
return ScrollAlignment::kAlignLeftAlways;
default:
NOTREACHED();
return ScrollAlignment::kAlignLeftAlways;
}
} else {
switch (writing_mode) {
case WritingMode::kHorizontalTb:
return ScrollAlignment::kAlignTopAlways;
case WritingMode::kVerticalRl:
case WritingMode::kSidewaysRl:
case WritingMode::kVerticalLr:
return is_ltr ? ScrollAlignment::kAlignTopAlways
: ScrollAlignment::kAlignBottomAlways;
case WritingMode::kSidewaysLr:
return is_ltr ? ScrollAlignment::kAlignBottomAlways
: ScrollAlignment::kAlignTopAlways;
default:
NOTREACHED();
return ScrollAlignment::kAlignTopAlways;
}
}
}
if (alignment == "end") {
if (axis == kHorizontalScroll) {
switch (writing_mode) {
case WritingMode::kHorizontalTb:
return is_ltr ? ScrollAlignment::kAlignRightAlways
: ScrollAlignment::kAlignLeftAlways;
case WritingMode::kVerticalRl:
case WritingMode::kSidewaysRl:
return ScrollAlignment::kAlignLeftAlways;
case WritingMode::kVerticalLr:
case WritingMode::kSidewaysLr:
return ScrollAlignment::kAlignRightAlways;
default:
NOTREACHED();
return ScrollAlignment::kAlignRightAlways;
}
} else {
switch (writing_mode) {
case WritingMode::kHorizontalTb:
return ScrollAlignment::kAlignBottomAlways;
case WritingMode::kVerticalRl:
case WritingMode::kSidewaysRl:
case WritingMode::kVerticalLr:
return is_ltr ? ScrollAlignment::kAlignBottomAlways
: ScrollAlignment::kAlignTopAlways;
case WritingMode::kSidewaysLr:
return is_ltr ? ScrollAlignment::kAlignTopAlways
: ScrollAlignment::kAlignBottomAlways;
default:
NOTREACHED();
return ScrollAlignment::kAlignBottomAlways;
}
}
}
// Default values
if (is_horizontal_writing_mode) {
return (axis == kHorizontalScroll) ? ScrollAlignment::kAlignToEdgeIfNeeded
: ScrollAlignment::kAlignTopAlways;
}
return (axis == kHorizontalScroll) ? ScrollAlignment::kAlignLeftAlways
: ScrollAlignment::kAlignToEdgeIfNeeded;
}
void Element::scrollIntoViewWithOptions(const ScrollIntoViewOptions* options) {
ActivateDisplayLockIfNeeded(DisplayLockActivationReason::kScrollIntoView);
GetDocument().EnsurePaintLocationDataValidForNode(this);
ScrollIntoViewNoVisualUpdate(options);
}
void Element::ScrollIntoViewNoVisualUpdate(
const ScrollIntoViewOptions* options) {
if (!GetLayoutObject() || !GetDocument().GetPage())
return;
if (DisplayLockPreventsActivation(
DisplayLockActivationReason::kScrollIntoView))
return;
ScrollBehavior behavior = (options->behavior() == "smooth")
? kScrollBehaviorSmooth
: kScrollBehaviorAuto;
WritingMode writing_mode = GetComputedStyle()->GetWritingMode();
bool is_ltr = GetComputedStyle()->IsLeftToRightDirection();
ScrollAlignment align_x =
ToPhysicalAlignment(options, kHorizontalScroll, writing_mode, is_ltr);
ScrollAlignment align_y =
ToPhysicalAlignment(options, kVerticalScroll, writing_mode, is_ltr);
PhysicalRect bounds = BoundingBoxForScrollIntoView();
GetLayoutObject()->ScrollRectToVisible(
bounds, {align_x, align_y, kProgrammaticScroll,
/*make_visible_in_visual_viewport=*/true, behavior});
GetDocument().SetSequentialFocusNavigationStartingPoint(this);
}
void Element::scrollIntoViewIfNeeded(bool center_if_needed) {
GetDocument().EnsurePaintLocationDataValidForNode(this);
if (!GetLayoutObject())
return;
PhysicalRect bounds = BoundingBoxForScrollIntoView();
if (center_if_needed) {
GetLayoutObject()->ScrollRectToVisible(
bounds, {ScrollAlignment::kAlignCenterIfNeeded,
ScrollAlignment::kAlignCenterIfNeeded});
} else {
GetLayoutObject()->ScrollRectToVisible(
bounds, {ScrollAlignment::kAlignToEdgeIfNeeded,
ScrollAlignment::kAlignToEdgeIfNeeded});
}
}
int Element::OffsetLeft() {
GetDocument().EnsurePaintLocationDataValidForNode(this);
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(
layout_object->PixelSnappedOffsetLeft(OffsetParent())),
layout_object->StyleRef())
.Round();
return 0;
}
int Element::OffsetTop() {
GetDocument().EnsurePaintLocationDataValidForNode(this);
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(layout_object->PixelSnappedOffsetTop(OffsetParent())),
layout_object->StyleRef())
.Round();
return 0;
}
int Element::OffsetWidth() {
GetDocument().EnsurePaintLocationDataValidForNode(this);
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(
layout_object->PixelSnappedOffsetWidth(OffsetParent())),
layout_object->StyleRef())
.Round();
return 0;
}
int Element::OffsetHeight() {
GetDocument().EnsurePaintLocationDataValidForNode(this);
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(
layout_object->PixelSnappedOffsetHeight(OffsetParent())),
layout_object->StyleRef())
.Round();
return 0;
}
Element* Element::OffsetParent() {
GetDocument().UpdateStyleAndLayoutForNode(this);
LayoutObject* layout_object = GetLayoutObject();
return layout_object ? layout_object->OffsetParent() : nullptr;
}
int Element::clientLeft() {
GetDocument().UpdateStyleAndLayoutForNode(this);
if (LayoutBox* layout_object = GetLayoutBox())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(layout_object->ClientLeft(),
layout_object->StyleRef())
.Round();
return 0;
}
int Element::clientTop() {
GetDocument().UpdateStyleAndLayoutForNode(this);
if (LayoutBox* layout_object = GetLayoutBox())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(layout_object->ClientTop(),
layout_object->StyleRef())
.Round();
return 0;
}
int Element::clientWidth() {
// When in strict mode, clientWidth for the document element should return the
// width of the containing frame.
// When in quirks mode, clientWidth for the body element should return the
// width of the containing frame.
bool in_quirks_mode = GetDocument().InQuirksMode();
if ((!in_quirks_mode && GetDocument().documentElement() == this) ||
(in_quirks_mode && IsHTMLElement() && GetDocument().body() == this)) {
auto* layout_view = GetDocument().GetLayoutView();
if (layout_view) {
// TODO(crbug.com/740879): Use per-page overlay scrollbar settings.
if (!ScrollbarThemeSettings::OverlayScrollbarsEnabled() ||
!GetDocument().GetFrame()->IsLocalRoot())
GetDocument().UpdateStyleAndLayoutForNode(this);
if (GetDocument().GetPage()->GetSettings().GetForceZeroLayoutHeight())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
layout_view->OverflowClipRect(PhysicalOffset()).Width(),
layout_view->StyleRef())
.Round();
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(layout_view->GetLayoutSize().Width()),
layout_view->StyleRef())
.Round();
}
}
GetDocument().UpdateStyleAndLayoutForNode(this);
if (LayoutBox* layout_object = GetLayoutBox())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(
layout_object
->PixelSnappedClientWidthWithTableSpecialBehavior()),
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) {
// TODO(crbug.com/740879): Use per-page overlay scrollbar settings.
if (!ScrollbarThemeSettings::OverlayScrollbarsEnabled() ||
!GetDocument().GetFrame()->IsLocalRoot())
GetDocument().UpdateStyleAndLayoutForNode(this);
if (GetDocument().GetPage()->GetSettings().GetForceZeroLayoutHeight())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
layout_view->OverflowClipRect(PhysicalOffset()).Height(),
layout_view->StyleRef())
.Round();
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(layout_view->GetLayoutSize().Height()),
layout_view->StyleRef())
.Round();
}
}
GetDocument().UpdateStyleAndLayoutForNode(this);
if (LayoutBox* layout_object = GetLayoutBox())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(
layout_object
->PixelSnappedClientHeightWithTableSpecialBehavior()),
layout_object->StyleRef())
.Round();
return 0;
}
PaintLayerScrollableArea* Element::GetScrollableArea() const {
LayoutBox* box = GetLayoutBox();
if (!box || !box->HasOverflowClip())
return nullptr;
return box->GetScrollableArea();
}
double Element::scrollLeft() {
if (!InActiveDocument())
return 0;
GetDocument().UpdateStyleAndLayoutForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (GetDocument().domWindow())
return GetDocument().domWindow()->scrollX();
return 0;
}
if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) {
DCHECK(GetLayoutBox());
if (HasLeftwardDirection(*this)) {
UseCounter::Count(
GetDocument(),
WebFeature::
kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop);
}
// In order to keep the behavior of element scroll consistent with document
// scroll, and consistent with the behavior of other vendors, the scrollLeft
// of a box is changed to the offset from |ScrollOrigin()|.
if (RuntimeEnabledFeatures::CSSOMViewScrollCoordinatesEnabled()) {
return AdjustForAbsoluteZoom::AdjustScroll(
scrollable_area->GetScrollOffset().Width(), *GetLayoutBox());
} else {
return AdjustForAbsoluteZoom::AdjustScroll(
scrollable_area->ScrollPosition().X(), *GetLayoutBox());
}
}
return 0;
}
double Element::scrollTop() {
if (!InActiveDocument())
return 0;
GetDocument().UpdateStyleAndLayoutForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (GetDocument().domWindow())
return GetDocument().domWindow()->scrollY();
return 0;
}
if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) {
DCHECK(GetLayoutBox());
if (HasUpwardDirection(*this)) {
UseCounter::Count(
GetDocument(),
WebFeature::
kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop);
}
// In order to keep the behavior of element scroll consistent with document
// scroll, and consistent with the behavior of other vendors, the scrollTop
// of a box is changed to the offset from |ScrollOrigin()|.
if (RuntimeEnabledFeatures::CSSOMViewScrollCoordinatesEnabled()) {
return AdjustForAbsoluteZoom::AdjustScroll(
scrollable_area->GetScrollOffset().Height(), *GetLayoutBox());
} else {
return AdjustForAbsoluteZoom::AdjustScroll(
scrollable_area->ScrollPosition().Y(), *GetLayoutBox());
}
}
return 0;
}
void Element::setScrollLeft(double new_left) {
if (!InActiveDocument())
return;
GetDocument().UpdateStyleAndLayoutForNode(this);
new_left = ScrollableArea::NormalizeNonFiniteScroll(new_left);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (LocalDOMWindow* window = GetDocument().domWindow()) {
ScrollToOptions* options = ScrollToOptions::Create();
options->setLeft(new_left);
window->scrollTo(options);
}
} else if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) {
LayoutBox* box = GetLayoutBox();
DCHECK(box);
if (HasLeftwardDirection(*this)) {
UseCounter::Count(
GetDocument(),
WebFeature::
kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop);
if (new_left > 0) {
UseCounter::Count(
GetDocument(),
WebFeature::
kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTopSetPositive);
}
}
if (RuntimeEnabledFeatures::CSSOMViewScrollCoordinatesEnabled()) {
ScrollOffset end_offset(new_left * box->Style()->EffectiveZoom(),
scrollable_area->GetScrollOffset().Height());
std::unique_ptr<cc::SnapSelectionStrategy> strategy =
cc::SnapSelectionStrategy::CreateForEndPosition(
gfx::ScrollOffset(
scrollable_area->ScrollOffsetToPosition(end_offset)),
true, false);
base::Optional<FloatPoint> snap_point =
scrollable_area->GetSnapPositionAndSetTarget(*strategy);
if (snap_point.has_value()) {
end_offset =
scrollable_area->ScrollPositionToOffset(snap_point.value());
}
scrollable_area->SetScrollOffset(end_offset, kProgrammaticScroll,
kScrollBehaviorAuto);
} else {
FloatPoint end_point(new_left * box->Style()->EffectiveZoom(),
scrollable_area->ScrollPosition().Y());
std::unique_ptr<cc::SnapSelectionStrategy> strategy =
cc::SnapSelectionStrategy::CreateForEndPosition(
gfx::ScrollOffset(end_point), true, false);
end_point =
scrollable_area->GetSnapPositionAndSetTarget(*strategy).value_or(
end_point);
FloatPoint new_position(end_point.X(),
scrollable_area->ScrollPosition().Y());
scrollable_area->ScrollToAbsolutePosition(new_position,
kScrollBehaviorAuto);
}
}
}
void Element::setScrollTop(double new_top) {
if (!InActiveDocument())
return;
GetDocument().UpdateStyleAndLayoutForNode(this);
new_top = ScrollableArea::NormalizeNonFiniteScroll(new_top);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (LocalDOMWindow* window = GetDocument().domWindow()) {
ScrollToOptions* options = ScrollToOptions::Create();
options->setTop(new_top);
window->scrollTo(options);
}
} else if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) {
LayoutBox* box = GetLayoutBox();
DCHECK(box);
if (HasUpwardDirection(*this)) {
UseCounter::Count(
GetDocument(),
WebFeature::
kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop);
if (new_top > 0) {
UseCounter::Count(
GetDocument(),
WebFeature::
kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTopSetPositive);
}
}
if (RuntimeEnabledFeatures::CSSOMViewScrollCoordinatesEnabled()) {
ScrollOffset end_offset(scrollable_area->GetScrollOffset().Width(),
new_top * box->Style()->EffectiveZoom());
std::unique_ptr<cc::SnapSelectionStrategy> strategy =
cc::SnapSelectionStrategy::CreateForEndPosition(
gfx::ScrollOffset(
scrollable_area->ScrollOffsetToPosition(end_offset)),
false, true);
base::Optional<FloatPoint> snap_point =
scrollable_area->GetSnapPositionAndSetTarget(*strategy);
if (snap_point.has_value()) {
end_offset =
scrollable_area->ScrollPositionToOffset(snap_point.value());
}
scrollable_area->SetScrollOffset(end_offset, kProgrammaticScroll,
kScrollBehaviorAuto);
} else {
FloatPoint end_point(scrollable_area->ScrollPosition().X(),
new_top * box->Style()->EffectiveZoom());
std::unique_ptr<cc::SnapSelectionStrategy> strategy =
cc::SnapSelectionStrategy::CreateForEndPosition(
gfx::ScrollOffset(end_point), false, true);
end_point =
scrollable_area->GetSnapPositionAndSetTarget(*strategy).value_or(
end_point);
FloatPoint new_position(scrollable_area->ScrollPosition().X(),
end_point.Y());
scrollable_area->ScrollToAbsolutePosition(new_position,
kScrollBehaviorAuto);
}
}
}
int Element::scrollWidth() {
if (!InActiveDocument())
return 0;
GetDocument().UpdateStyleAndLayoutForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (GetDocument().View()) {
return AdjustForAbsoluteZoom::AdjustInt(
GetDocument().View()->LayoutViewport()->ContentsSize().Width(),
GetDocument().GetFrame()->PageZoomFactor());
}
return 0;
}
if (LayoutBox* box = GetLayoutBox()) {
return AdjustForAbsoluteZoom::AdjustInt(box->PixelSnappedScrollWidth(),
box);
}
return 0;
}
int Element::scrollHeight() {
if (!InActiveDocument())
return 0;
GetDocument().UpdateStyleAndLayoutForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (GetDocument().View()) {
return AdjustForAbsoluteZoom::AdjustInt(
GetDocument().View()->LayoutViewport()->ContentsSize().Height(),
GetDocument().GetFrame()->PageZoomFactor());
}
return 0;
}
if (LayoutBox* box = GetLayoutBox()) {
return AdjustForAbsoluteZoom::AdjustInt(box->PixelSnappedScrollHeight(),
box);
}
return 0;
}
void Element::scrollBy(double x, double y) {
ScrollToOptions* scroll_to_options = ScrollToOptions::Create();
scroll_to_options->setLeft(x);
scroll_to_options->setTop(y);
scrollBy(scroll_to_options);
}
void Element::scrollBy(const ScrollToOptions* scroll_to_options) {
if (!InActiveDocument())
return;
// FIXME: This should be removed once scroll updates are processed only after
// the compositing update. See http://crbug.com/420741.
GetDocument().UpdateStyleAndLayoutForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
ScrollFrameBy(scroll_to_options);
} else {
ScrollLayoutBoxBy(scroll_to_options);
}
}
void Element::scrollTo(double x, double y) {
ScrollToOptions* scroll_to_options = ScrollToOptions::Create();
scroll_to_options->setLeft(x);
scroll_to_options->setTop(y);
scrollTo(scroll_to_options);
}
void Element::scrollTo(const ScrollToOptions* scroll_to_options) {
if (!InActiveDocument())
return;
// FIXME: This should be removed once scroll updates are processed only after
// the compositing update. See http://crbug.com/420741.
GetDocument().UpdateStyleAndLayoutForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
ScrollFrameTo(scroll_to_options);
} else {
ScrollLayoutBoxTo(scroll_to_options);
}
}
void Element::ScrollLayoutBoxBy(const ScrollToOptions* scroll_to_options) {
gfx::ScrollOffset displacement;
if (scroll_to_options->hasLeft()) {
displacement.set_x(
ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->left()));
}
if (scroll_to_options->hasTop()) {
displacement.set_y(
ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->top()));
}
ScrollBehavior scroll_behavior = kScrollBehaviorAuto;
ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(),
scroll_behavior);
if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) {
LayoutBox* box = GetLayoutBox();
DCHECK(box);
gfx::ScrollOffset current_position(scrollable_area->ScrollPosition().X(),
scrollable_area->ScrollPosition().Y());
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 =
scrollable_area->GetSnapPositionAndSetTarget(*strategy).value_or(
new_position);
scrollable_area->ScrollToAbsolutePosition(new_position, scroll_behavior);
}
}
void Element::ScrollLayoutBoxTo(const ScrollToOptions* scroll_to_options) {
ScrollBehavior scroll_behavior = kScrollBehaviorAuto;
ScrollableArea::ScrollBehaviorFromString(scroll_to_options->behavior(),
scroll_behavior);
if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) {
LayoutBox* box = GetLayoutBox();
DCHECK(box);
if (scroll_to_options->hasLeft() && HasLeftwardDirection(*this)) {
UseCounter::Count(
GetDocument(),
WebFeature::
kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop);
if (scroll_to_options->left() > 0) {
UseCounter::Count(
GetDocument(),
WebFeature::
kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTopSetPositive);
}
}
if (scroll_to_options->hasTop() && HasUpwardDirection(*this)) {
UseCounter::Count(
GetDocument(),
WebFeature::
kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTop);
if (scroll_to_options->top() > 0) {
UseCounter::Count(
GetDocument(),
WebFeature::
kElementWithLeftwardOrUpwardOverflowDirection_ScrollLeftOrTopSetPositive);
}
}
// In order to keep the behavior of element scroll consistent with document
// scroll, and consistent with the behavior of other vendors, the
// offsets in |scroll_to_options| are treated as the offset from
// |ScrollOrigin()|.
if (RuntimeEnabledFeatures::CSSOMViewScrollCoordinatesEnabled()) {
ScrollOffset new_offset = scrollable_area->GetScrollOffset();
if (scroll_to_options->hasLeft()) {
new_offset.SetWidth(ScrollableArea::NormalizeNonFiniteScroll(
scroll_to_options->left()) *
box->Style()->EffectiveZoom());
}
if (scroll_to_options->hasTop()) {
new_offset.SetHeight(
ScrollableArea::NormalizeNonFiniteScroll(scroll_to_options->top()) *
box->Style()->EffectiveZoom());
}
std::unique_ptr<cc::SnapSelectionStrategy> strategy =
cc::SnapSelectionStrategy::CreateForEndPosition(
gfx::ScrollOffset(
scrollable_area->ScrollOffsetToPosition(new_offset)),
scroll_to_options->hasLeft(), scroll_to_options->hasTop());
base::Optional<FloatPoint> snap_point =
scrollable_area->GetSnapPositionAndSetTarget(*strategy);
if (snap_point.has_value()) {
new_offset =
scrollable_area->ScrollPositionToOffset(snap_point.value());
}
scrollable_area->SetScrollOffset(new_offset, kProgrammaticScroll,
scroll_behavior);
} else {
FloatPoint new_position(scrollable_area->ScrollPosition().X(),
scrollable_area->ScrollPosition().Y());
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 =
scrollable_area->GetSnapPositionAndSetTarget(*strategy).value_or(
new_position);
scrollable_area->ScrollToAbsolutePosition(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 =
viewport->GetSnapPositionAndSetTarget(*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 =
viewport->GetSnapPositionAndSetTarget(*strategy).value_or(new_position);
new_offset = viewport->ScrollPositionToOffset(new_position);
viewport->SetScrollOffset(new_offset, kProgrammaticScroll, scroll_behavior);
}
IntRect Element::BoundsInViewport() const {
GetDocument().EnsurePaintLocationDataValidForNode(this);
LocalFrameView* view = GetDocument().View();
if (!view)
return IntRect();
Vector<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.
auto* svg_element = DynamicTo<SVGElement>(this);
if (svg_element && GetLayoutObject() &&
!GetLayoutObject()->IsSVGForeignObject()) {
// Get the bounding rectangle from the SVG model.
// TODO(pdr): This should include stroke.
if (svg_element->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
PhysicalRect rect(
RoundedIntRect(GetLayoutObject()->AbsoluteBoundingBoxFloatRect()));
PhysicalRect frame_clip_rect =
GetDocument().View()->GetLayoutView()->ClippingRect(PhysicalOffset());
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, kTraverseDocumentBoundaries, kDefaultVisualRectFlags);
// TODO(layout-dev): Callers of this method don't expect the offset of the
// local frame root from a remote top-level frame to be applied here. They
// expect the result to be in the coordinate system of the local root frame.
// Either the method should be renamed to something which communicates that,
// or callers should be updated to expect actual top-level frame coordinates.
rect.Move(-PhysicalOffset(
GetDocument().GetFrame()->LocalFrameRoot().RemoteViewportOffset()));
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.
auto* svg_element = DynamicTo<SVGElement>(this);
if (svg_element && !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 (svg_element->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);
}
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.UpdateStyleAndLayoutForNode(this);
UpdateDistributionForFlatTreeTraversal();
AXContext ax_context(document);
return ax_context.GetAXObjectCache().ComputedRoleForNode(this);
}
String Element::computedName() {
Document& document = GetDocument();
if (!document.IsActive())
return String();
document.UpdateStyleAndLayoutForNode(this);
UpdateDistributionForFlatTreeTraversal();
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(html_names::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)) {
auto* ancestor_element = DynamicTo<Element>(ancestor);
if (ancestor_element &&
ancestor_element->Invisible() != InvisibleState::kMissing) {
invisible_ancestors.push_back(ancestor_element);
activated_elements.push_back(ancestor.GetTreeScope().Retarget(*this));
}
}
auto* activated_element_iterator = activated_elements.begin();
for (Element* ancestor : invisible_ancestors) {
DCHECK(activated_element_iterator != activated_elements.end());
ancestor->DispatchEvent(*MakeGarbageCollected<ActivateInvisibleEvent>(
*activated_element_iterator));
++activated_element_iterator;
}
}
bool Element::IsInsideInvisibleStaticSubtree() const {
if (!RuntimeEnabledFeatures::InvisibleDOMEnabled())
return false;
for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*this)) {
auto* ancestor_element = DynamicTo<Element>(ancestor);
if (ancestor_element &&
ancestor_element->Invisible() == InvisibleState::kStatic)
return true;
}
return false;
}
bool Element::IsInsideInvisibleSubtree() const {
if (!RuntimeEnabledFeatures::InvisibleDOMEnabled() ||
!CanParticipateInFlatTree())
return false;
for (Node& ancestor : FlatTreeTraversal::InclusiveAncestorsOf(*this)) {
auto* ancestor_element = DynamicTo<Element>(ancestor);
if (ancestor_element &&
ancestor_element->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(html_names::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));
}
std::pair<wtf_size_t, const QualifiedName>
Element::LookupAttributeQNameInternal(const AtomicString& local_name) const {
AtomicString case_adjusted_local_name = LowercaseIfNecessary(local_name);
if (!GetElementData()) {
return std::make_pair(
kNotFound,
QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom));
}
AttributeCollection attributes = GetElementData()->Attributes();
wtf_size_t index = attributes.FindIndex(case_adjusted_local_name);
return std::make_pair(
index,
index != kNotFound
? attributes[index].GetName()
: QualifiedName(g_null_atom, case_adjusted_local_name, g_null_atom));
}
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);
wtf_size_t index;
QualifiedName q_name = QualifiedName::Null();
std::tie(index, q_name) = LookupAttributeQNameInternal(local_name);
String trusted_value = GetStringFromSpecificTrustedType(
value, ExpectedTrustedTypeForAttribute(q_name), &GetDocument(),
exception_state);
if (exception_state.HadException())
return;
SetAttributeInternal(index, q_name, AtomicString(trusted_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::setAttribute(const QualifiedName& name,
const AtomicString& value,
ExceptionState& exception_state) {
SynchronizeAttribute(name);
wtf_size_t index = GetElementData()
? GetElementData()->Attributes().FindIndex(name)
: kNotFound;
String trusted_value = GetStringFromSpecificTrustedType(
value, ExpectedTrustedTypeForAttribute(name), &GetDocument(),
exception_state);
if (exception_state.HadException())
return;
SetAttributeInternal(index, name, AtomicString(trusted_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& local_name,
const StringOrTrustedHTMLOrTrustedScriptOrTrustedScriptURL& string_or_TT,
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);
wtf_size_t index;
QualifiedName q_name = QualifiedName::Null();
std::tie(index, q_name) = LookupAttributeQNameInternal(local_name);
String value = GetStringFromSpecificTrustedType(
string_or_TT, ExpectedTrustedTypeForAttribute(q_name), &GetDocument(),
exception_state);
if (exception_state.HadException())
return;
SetAttributeInternal(index, q_name, AtomicString(value),
kNotInSynchronizationOfLazyAttribute);
}
const AttrNameToTrustedType& Element::GetCheckedAttributeTypes() const {
DEFINE_STATIC_LOCAL(AttrNameToTrustedType, attribute_map, ({}));
return attribute_map;
}
SpecificTrustedType Element::ExpectedTrustedTypeForAttribute(
const QualifiedName& q_name) const {
// There are only a handful of namespaced attributes we care about
// (xlink:href), and all of those have identical Trusted Types
// properties to their namespace-less counterpart. So we check whether this
// is one of SVG's 'known' attributes, and if so just check the local
// name part as usual.
if (!q_name.NamespaceURI().IsNull() &&
!SVGAnimatedHref::IsKnownAttribute(q_name)) {
return SpecificTrustedType::kNone;
}
const AttrNameToTrustedType* attribute_types = &GetCheckedAttributeTypes();
AttrNameToTrustedType::const_iterator iter =
attribute_types->find(q_name.LocalName());
if (iter != attribute_types->end())
return iter->value;
if (q_name.LocalName().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.
return SpecificTrustedType::kTrustedScript;
}
return SpecificTrustedType::kNone;
}
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));
}
}
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 == html_names::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) {
part().DidUpdateAttributeValue(params.old_value, params.new_value);
GetDocument().GetStyleEngine().PartChangedForElement(*this);
} else if (name == html_names::kExportpartsAttr) {
EnsureElementRareData().SetPartNamesMap(params.new_value);
GetDocument().GetStyleEngine().ExportpartsChangedForElement(*this);
} else if (IsElementReflectionAttribute(name)) {
SynchronizeContentAttributeAndElementReference(name);
} else if (IsStyledElement()) {
if (name == html_names::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::DisplayLockingEnabled(
GetExecutionContext()) &&
name == html_names::kRendersubtreeAttr &&
params.old_value != params.new_value &&
DisplayLockContext::IsAttributeVersion(
GetDisplayLockContext())) {
UseCounter::Count(GetDocument(), WebFeature::kRenderSubtreeAttribute);
// This is needed to ensure that proper containment is put in place.
SetNeedsStyleRecalc(kLocalStyleChange,
StyleChangeReasonForTracing::FromAttribute(name));
SpaceSplitString tokens(params.new_value.LowerASCII());
uint16_t activation_mask =
static_cast<uint16_t>(DisplayLockActivationReason::kAny);
// Figure out the activation mask.
if (tokens.Contains("skip-activation"))
activation_mask = 0;
if (tokens.Contains("skip-viewport-activation")) {
activation_mask &=
~static_cast<uint16_t>(DisplayLockActivationReason::kViewport);
}
EnsureDisplayLockContext(DisplayLockContextCreateMethod::kAttribute)
.SetActivatable(activation_mask);
const bool should_be_invisible = tokens.Contains("invisible");
if (should_be_invisible) {
if (!GetDisplayLockContext()->IsLocked())
GetDisplayLockContext()->StartAcquire();
} else {
// Getting unlocked.
if (GetDisplayLockContext()->IsLocked())
GetDisplayLockContext()->StartCommit();
}
} 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 == html_names::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) ||