blob: 031570e0b3c9cfa9849c9f597ff6e3d01c586f53 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* Copyright (C) 2004-2008, 2013, 2014 Apple Inc. All rights reserved.
* Copyright (C) 2009 Torch Mobile Inc. All rights reserved.
* (http://www.torchmobile.com/)
* Copyright (C) 2011 Motorola Mobility. All rights reserved.
*
* 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/html/html_element.h"
#include "base/cxx17_backports.h"
#include "third_party/blink/renderer/bindings/core/v8/js_event_handler_for_content_attribute.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_stringtreatnullasemptystring_trustedscript.h"
#include "third_party/blink/renderer/core/css/css_color.h"
#include "third_party/blink/renderer/core/css/css_identifier_value.h"
#include "third_party/blink/renderer/core/css/css_markup.h"
#include "third_party/blink/renderer/core/css/css_numeric_literal_value.h"
#include "third_party/blink/renderer/core/css/css_property_names.h"
#include "third_party/blink/renderer/core/css/css_property_value_set.h"
#include "third_party/blink/renderer/core/css/css_ratio_value.h"
#include "third_party/blink/renderer/core/css/css_value_list.h"
#include "third_party/blink/renderer/core/css/style_change_reason.h"
#include "third_party/blink/renderer/core/css_value_keywords.h"
#include "third_party/blink/renderer/core/dom/document_fragment.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_listener.h"
#include "third_party/blink/renderer/core/dom/events/simulated_click_options.h"
#include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/node_lists_node_data.h"
#include "third_party/blink/renderer/core/dom/node_traversal.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/dom/slot_assignment.h"
#include "third_party/blink/renderer/core/dom/slot_assignment_engine.h"
#include "third_party/blink/renderer/core/dom/slot_assignment_recalc_forbidden_scope.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/serializers/serialization.h"
#include "third_party/blink/renderer/core/editing/spellcheck/spell_checker.h"
#include "third_party/blink/renderer/core/events/keyboard_event.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/deprecation.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/settings.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/element_internals.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/forms/labels_node_list.h"
#include "third_party/blink/renderer/core/html/html_br_element.h"
#include "third_party/blink/renderer/core/html/html_dimension.h"
#include "third_party/blink/renderer/core/html/html_document.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/html/html_slot_element.h"
#include "third_party/blink/renderer/core/html/html_template_element.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/input_type_names.h"
#include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/mathml/mathml_element.h"
#include "third_party/blink/renderer/core/mathml_names.h"
#include "third_party/blink/renderer/core/page/spatial_navigation.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/svg/svg_svg_element.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_script.h"
#include "third_party/blink/renderer/core/xml_names.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/language.h"
#include "third_party/blink/renderer/platform/text/bidi_resolver.h"
#include "third_party/blink/renderer/platform/text/bidi_text_run.h"
#include "third_party/blink/renderer/platform/text/text_run_iterator.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
namespace blink {
using AttributeChangedFunction =
void (HTMLElement::*)(const Element::AttributeModificationParams& params);
struct AttributeTriggers {
const QualifiedName& attribute;
WebFeature web_feature;
const AtomicString& event;
AttributeChangedFunction function;
};
namespace {
// https://html.spec.whatwg.org/multipage/interaction.html#editing-host
// An editing host is either an HTML element with its contenteditable attribute
// in the true state, or a child HTML element of a Document whose design mode
// enabled is true.
// https://w3c.github.io/editing/execCommand.html#editable
// Something is editable if it is a node; it is not an editing host; it does not
// have a contenteditable attribute set to the false state; its parent is an
// editing host or editable; and either it is an HTML element, or it is an svg
// or math element, or it is not an Element and its parent is an HTML element.
bool IsEditableOrEditingHost(const Node& node) {
auto* html_element = DynamicTo<HTMLElement>(node);
if (html_element) {
ContentEditableType content_editable =
html_element->contentEditableNormalized();
if (content_editable == ContentEditableType::kContentEditable ||
content_editable == ContentEditableType::kPlaintextOnly)
return true;
if (html_element->GetDocument().InDesignMode() &&
html_element->isConnected()) {
return true;
}
if (content_editable == ContentEditableType::kNotContentEditable)
return false;
}
if (!node.parentNode())
return false;
if (!IsEditableOrEditingHost(*node.parentNode()))
return false;
if (html_element)
return true;
if (IsA<SVGSVGElement>(node))
return true;
if (auto* mathml_element = DynamicTo<MathMLElement>(node))
return mathml_element->HasTagName(mathml_names::kMathTag);
return !IsA<Element>(node) && node.parentNode()->IsHTMLElement();
}
const WebFeature kNoWebFeature = static_cast<WebFeature>(0);
HTMLElement* GetParentForDirectionality(const HTMLElement& element,
bool& needs_slot_assignment_recalc) {
if (element.IsPseudoElement())
return DynamicTo<HTMLElement>(element.ParentOrShadowHostNode());
if (element.IsChildOfShadowHost()) {
ShadowRoot* root = element.ShadowRootOfParent();
if (!root || !root->HasSlotAssignment())
return nullptr;
if (root->NeedsSlotAssignmentRecalc()) {
needs_slot_assignment_recalc = true;
return nullptr;
}
}
if (auto* parent_slot = ToHTMLSlotElementIfSupportsAssignmentOrNull(
element.parentElement())) {
ShadowRoot* root = parent_slot->ContainingShadowRoot();
if (root->NeedsSlotAssignmentRecalc()) {
needs_slot_assignment_recalc = true;
return nullptr;
}
}
// We should take care of all cases that would trigger a slot assignment
// recalc, and delay the check for later for a performance reason.
SlotAssignmentRecalcForbiddenScope forbid_slot_recalc(element.GetDocument());
return DynamicTo<HTMLElement>(FlatTreeTraversal::ParentElement(element));
}
} // anonymous namespace
String HTMLElement::DebugNodeName() const {
if (IsA<HTMLDocument>(GetDocument())) {
return TagQName().HasPrefix() ? Element::nodeName().UpperASCII()
: TagQName().LocalName().UpperASCII();
}
return Element::nodeName();
}
String HTMLElement::nodeName() const {
// localNameUpper may intern and cache an AtomicString.
DCHECK(IsMainThread());
// FIXME: Would be nice to have an atomicstring lookup based off uppercase
// chars that does not have to copy the string on a hit in the hash.
// FIXME: We should have a way to detect XHTML elements and replace the
// hasPrefix() check with it.
if (IsA<HTMLDocument>(GetDocument())) {
if (!TagQName().HasPrefix())
return TagQName().LocalNameUpper();
return Element::nodeName().UpperASCII();
}
return Element::nodeName();
}
bool HTMLElement::ShouldSerializeEndTag() const {
// See https://www.w3.org/TR/DOM-Parsing/
if (HasTagName(html_names::kAreaTag) || HasTagName(html_names::kBaseTag) ||
HasTagName(html_names::kBasefontTag) ||
HasTagName(html_names::kBgsoundTag) || HasTagName(html_names::kBrTag) ||
HasTagName(html_names::kColTag) || HasTagName(html_names::kEmbedTag) ||
HasTagName(html_names::kFrameTag) || HasTagName(html_names::kHrTag) ||
HasTagName(html_names::kImgTag) || HasTagName(html_names::kInputTag) ||
HasTagName(html_names::kKeygenTag) || HasTagName(html_names::kLinkTag) ||
HasTagName(html_names::kMetaTag) || HasTagName(html_names::kParamTag) ||
HasTagName(html_names::kSourceTag) || HasTagName(html_names::kTrackTag) ||
HasTagName(html_names::kWbrTag))
return false;
return true;
}
static inline CSSValueID UnicodeBidiAttributeForDirAuto(HTMLElement* element) {
if (element->HasTagName(html_names::kPreTag) ||
element->HasTagName(html_names::kTextareaTag))
return CSSValueID::kPlaintext;
// FIXME: For bdo element, dir="auto" should result in "bidi-override isolate"
// but we don't support having multiple values in unicode-bidi yet.
// See https://bugs.webkit.org/show_bug.cgi?id=73164.
return CSSValueID::kIsolate;
}
unsigned HTMLElement::ParseBorderWidthAttribute(
const AtomicString& value) const {
unsigned border_width = 0;
if (value.IsEmpty() || !ParseHTMLNonNegativeInteger(value, border_width)) {
if (HasTagName(html_names::kTableTag) && !value.IsNull())
return 1;
}
return border_width;
}
void HTMLElement::ApplyBorderAttributeToStyle(
const AtomicString& value,
MutableCSSPropertyValueSet* style) {
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kBorderWidth,
ParseBorderWidthAttribute(value),
CSSPrimitiveValue::UnitType::kPixels);
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kBorderStyle,
CSSValueID::kSolid);
}
void HTMLElement::MapLanguageAttributeToLocale(
const AtomicString& value,
MutableCSSPropertyValueSet* style) {
if (!value.IsEmpty()) {
// Have to quote so the locale id is treated as a string instead of as a CSS
// keyword.
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kWebkitLocale,
SerializeString(value));
// FIXME: Remove the following UseCounter code when we collect enough
// data.
UseCounter::Count(GetDocument(), WebFeature::kLangAttribute);
if (IsA<HTMLHtmlElement>(this))
UseCounter::Count(GetDocument(), WebFeature::kLangAttributeOnHTML);
else if (IsA<HTMLBodyElement>(this))
UseCounter::Count(GetDocument(), WebFeature::kLangAttributeOnBody);
String html_language = value.GetString();
wtf_size_t first_separator = html_language.find('-');
if (first_separator != kNotFound)
html_language = html_language.Left(first_separator);
String ui_language = DefaultLanguage();
first_separator = ui_language.find('-');
if (first_separator != kNotFound)
ui_language = ui_language.Left(first_separator);
first_separator = ui_language.find('_');
if (first_separator != kNotFound)
ui_language = ui_language.Left(first_separator);
if (!DeprecatedEqualIgnoringCase(html_language, ui_language)) {
UseCounter::Count(GetDocument(),
WebFeature::kLangAttributeDoesNotMatchToUILocale);
}
} else {
// The empty string means the language is explicitly unknown.
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kWebkitLocale,
CSSValueID::kAuto);
}
}
bool HTMLElement::IsPresentationAttribute(const QualifiedName& name) const {
if (name == html_names::kAlignAttr ||
name == html_names::kContenteditableAttr ||
name == html_names::kHiddenAttr || name == html_names::kLangAttr ||
name.Matches(xml_names::kLangAttr) ||
name == html_names::kDraggableAttr || name == html_names::kDirAttr)
return true;
return Element::IsPresentationAttribute(name);
}
static inline bool IsValidDirAttribute(const AtomicString& value) {
return EqualIgnoringASCIICase(value, "auto") ||
EqualIgnoringASCIICase(value, "ltr") ||
EqualIgnoringASCIICase(value, "rtl");
}
void HTMLElement::CollectStyleForPresentationAttribute(
const QualifiedName& name,
const AtomicString& value,
MutableCSSPropertyValueSet* style) {
if (name == html_names::kAlignAttr) {
if (EqualIgnoringASCIICase(value, "middle")) {
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kTextAlign,
CSSValueID::kCenter);
} else {
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kTextAlign,
value);
}
} else if (name == html_names::kContenteditableAttr) {
if (value.IsEmpty() || EqualIgnoringASCIICase(value, "true")) {
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kWebkitUserModify, CSSValueID::kReadWrite);
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kOverflowWrap, CSSValueID::kBreakWord);
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kWebkitLineBreak, CSSValueID::kAfterWhiteSpace);
UseCounter::Count(GetDocument(), WebFeature::kContentEditableTrue);
if (HasTagName(html_names::kHTMLTag)) {
UseCounter::Count(GetDocument(),
WebFeature::kContentEditableTrueOnHTML);
}
} else if (EqualIgnoringASCIICase(value, "plaintext-only")) {
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kWebkitUserModify,
CSSValueID::kReadWritePlaintextOnly);
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kOverflowWrap, CSSValueID::kBreakWord);
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kWebkitLineBreak, CSSValueID::kAfterWhiteSpace);
UseCounter::Count(GetDocument(),
WebFeature::kContentEditablePlainTextOnly);
} else if (EqualIgnoringASCIICase(value, "false")) {
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kWebkitUserModify, CSSValueID::kReadOnly);
}
} else if (name == html_names::kHiddenAttr) {
if (RuntimeEnabledFeatures::BeforeMatchEventEnabled(
GetExecutionContext()) &&
EqualIgnoringASCIICase(value, "until-found")) {
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kContentVisibility, CSSValueID::kHidden);
} else {
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kDisplay,
CSSValueID::kNone);
}
} else if (name == html_names::kDraggableAttr) {
UseCounter::Count(GetDocument(), WebFeature::kDraggableAttribute);
if (EqualIgnoringASCIICase(value, "true")) {
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kWebkitUserDrag, CSSValueID::kElement);
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kUserSelect,
CSSValueID::kNone);
} else if (EqualIgnoringASCIICase(value, "false")) {
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kWebkitUserDrag, CSSValueID::kNone);
}
} else if (name == html_names::kDirAttr) {
if (EqualIgnoringASCIICase(value, "auto")) {
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kUnicodeBidi,
UnicodeBidiAttributeForDirAuto(this));
} else {
if (IsValidDirAttribute(value)) {
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kDirection, value);
} else if (IsA<HTMLBodyElement>(*this)) {
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kDirection, "ltr");
}
if (!HasTagName(html_names::kBdiTag) &&
!HasTagName(html_names::kBdoTag) &&
!HasTagName(html_names::kOutputTag)) {
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kUnicodeBidi, CSSValueID::kIsolate);
}
}
} else if (name.Matches(xml_names::kLangAttr)) {
MapLanguageAttributeToLocale(value, style);
} else if (name == html_names::kLangAttr) {
// xml:lang has a higher priority than lang.
if (!FastHasAttribute(xml_names::kLangAttr))
MapLanguageAttributeToLocale(value, style);
} else {
Element::CollectStyleForPresentationAttribute(name, value, style);
}
}
// static
AttributeTriggers* HTMLElement::TriggersForAttributeName(
const QualifiedName& attr_name) {
const AtomicString& kNoEvent = g_null_atom;
static AttributeTriggers attribute_triggers[] = {
{html_names::kDirAttr, kNoWebFeature, kNoEvent,
&HTMLElement::OnDirAttrChanged},
{html_names::kFormAttr, kNoWebFeature, kNoEvent,
&HTMLElement::OnFormAttrChanged},
{html_names::kInertAttr, WebFeature::kInertAttribute, kNoEvent,
&HTMLElement::OnInertAttrChanged},
{html_names::kLangAttr, kNoWebFeature, kNoEvent,
&HTMLElement::OnLangAttrChanged},
{html_names::kNonceAttr, kNoWebFeature, kNoEvent,
&HTMLElement::OnNonceAttrChanged},
{html_names::kTabindexAttr, kNoWebFeature, kNoEvent,
&HTMLElement::OnTabIndexAttrChanged},
{xml_names::kLangAttr, kNoWebFeature, kNoEvent,
&HTMLElement::OnXMLLangAttrChanged},
{html_names::kOnabortAttr, kNoWebFeature, event_type_names::kAbort,
nullptr},
{html_names::kOnanimationendAttr, kNoWebFeature,
event_type_names::kAnimationend, nullptr},
{html_names::kOnanimationiterationAttr, kNoWebFeature,
event_type_names::kAnimationiteration, nullptr},
{html_names::kOnanimationstartAttr, kNoWebFeature,
event_type_names::kAnimationstart, nullptr},
{html_names::kOnauxclickAttr, kNoWebFeature, event_type_names::kAuxclick,
nullptr},
{html_names::kOnbeforecopyAttr, kNoWebFeature,
event_type_names::kBeforecopy, nullptr},
{html_names::kOnbeforecutAttr, kNoWebFeature,
event_type_names::kBeforecut, nullptr},
{html_names::kOnbeforepasteAttr, kNoWebFeature,
event_type_names::kBeforepaste, nullptr},
{html_names::kOnblurAttr, kNoWebFeature, event_type_names::kBlur,
nullptr},
{html_names::kOncancelAttr, kNoWebFeature, event_type_names::kCancel,
nullptr},
{html_names::kOncanplayAttr, kNoWebFeature, event_type_names::kCanplay,
nullptr},
{html_names::kOncanplaythroughAttr, kNoWebFeature,
event_type_names::kCanplaythrough, nullptr},
{html_names::kOnchangeAttr, kNoWebFeature, event_type_names::kChange,
nullptr},
{html_names::kOnclickAttr, kNoWebFeature, event_type_names::kClick,
nullptr},
{html_names::kOncloseAttr, kNoWebFeature, event_type_names::kClose,
nullptr},
{html_names::kOncontextmenuAttr, kNoWebFeature,
event_type_names::kContextmenu, nullptr},
{html_names::kOncopyAttr, kNoWebFeature, event_type_names::kCopy,
nullptr},
{html_names::kOncuechangeAttr, kNoWebFeature,
event_type_names::kCuechange, nullptr},
{html_names::kOncutAttr, kNoWebFeature, event_type_names::kCut, nullptr},
{html_names::kOndblclickAttr, kNoWebFeature, event_type_names::kDblclick,
nullptr},
{html_names::kOndragAttr, kNoWebFeature, event_type_names::kDrag,
nullptr},
{html_names::kOndragendAttr, kNoWebFeature, event_type_names::kDragend,
nullptr},
{html_names::kOndragenterAttr, kNoWebFeature,
event_type_names::kDragenter, nullptr},
{html_names::kOndragleaveAttr, kNoWebFeature,
event_type_names::kDragleave, nullptr},
{html_names::kOndragoverAttr, kNoWebFeature, event_type_names::kDragover,
nullptr},
{html_names::kOndragstartAttr, kNoWebFeature,
event_type_names::kDragstart, nullptr},
{html_names::kOndropAttr, kNoWebFeature, event_type_names::kDrop,
nullptr},
{html_names::kOndurationchangeAttr, kNoWebFeature,
event_type_names::kDurationchange, nullptr},
{html_names::kOnemptiedAttr, kNoWebFeature, event_type_names::kEmptied,
nullptr},
{html_names::kOnendedAttr, kNoWebFeature, event_type_names::kEnded,
nullptr},
{html_names::kOnerrorAttr, kNoWebFeature, event_type_names::kError,
nullptr},
{html_names::kOnfocusAttr, kNoWebFeature, event_type_names::kFocus,
nullptr},
{html_names::kOnfocusinAttr, kNoWebFeature, event_type_names::kFocusin,
nullptr},
{html_names::kOnfocusoutAttr, kNoWebFeature, event_type_names::kFocusout,
nullptr},
{html_names::kOnformdataAttr, kNoWebFeature, event_type_names::kFormdata,
nullptr},
{html_names::kOngotpointercaptureAttr, kNoWebFeature,
event_type_names::kGotpointercapture, nullptr},
{html_names::kOninputAttr, kNoWebFeature, event_type_names::kInput,
nullptr},
{html_names::kOninvalidAttr, kNoWebFeature, event_type_names::kInvalid,
nullptr},
{html_names::kOnkeydownAttr, kNoWebFeature, event_type_names::kKeydown,
nullptr},
{html_names::kOnkeypressAttr, kNoWebFeature, event_type_names::kKeypress,
nullptr},
{html_names::kOnkeyupAttr, kNoWebFeature, event_type_names::kKeyup,
nullptr},
{html_names::kOnloadAttr, kNoWebFeature, event_type_names::kLoad,
nullptr},
{html_names::kOnloadeddataAttr, kNoWebFeature,
event_type_names::kLoadeddata, nullptr},
{html_names::kOnloadedmetadataAttr, kNoWebFeature,
event_type_names::kLoadedmetadata, nullptr},
{html_names::kOnloadstartAttr, kNoWebFeature,
event_type_names::kLoadstart, nullptr},
{html_names::kOnlostpointercaptureAttr, kNoWebFeature,
event_type_names::kLostpointercapture, nullptr},
{html_names::kOnmousedownAttr, kNoWebFeature,
event_type_names::kMousedown, nullptr},
{html_names::kOnmouseenterAttr, kNoWebFeature,
event_type_names::kMouseenter, nullptr},
{html_names::kOnmouseleaveAttr, kNoWebFeature,
event_type_names::kMouseleave, nullptr},
{html_names::kOnmousemoveAttr, kNoWebFeature,
event_type_names::kMousemove, nullptr},
{html_names::kOnmouseoutAttr, kNoWebFeature, event_type_names::kMouseout,
nullptr},
{html_names::kOnmouseoverAttr, kNoWebFeature,
event_type_names::kMouseover, nullptr},
{html_names::kOnmouseupAttr, kNoWebFeature, event_type_names::kMouseup,
nullptr},
{html_names::kOnmousewheelAttr, kNoWebFeature,
event_type_names::kMousewheel, nullptr},
{html_names::kOnoverscrollAttr, kNoWebFeature,
event_type_names::kOverscroll, nullptr},
{html_names::kOnpasteAttr, kNoWebFeature, event_type_names::kPaste,
nullptr},
{html_names::kOnpauseAttr, kNoWebFeature, event_type_names::kPause,
nullptr},
{html_names::kOnplayAttr, kNoWebFeature, event_type_names::kPlay,
nullptr},
{html_names::kOnplayingAttr, kNoWebFeature, event_type_names::kPlaying,
nullptr},
{html_names::kOnpointercancelAttr, kNoWebFeature,
event_type_names::kPointercancel, nullptr},
{html_names::kOnpointerdownAttr, kNoWebFeature,
event_type_names::kPointerdown, nullptr},
{html_names::kOnpointerenterAttr, kNoWebFeature,
event_type_names::kPointerenter, nullptr},
{html_names::kOnpointerleaveAttr, kNoWebFeature,
event_type_names::kPointerleave, nullptr},
{html_names::kOnpointermoveAttr, kNoWebFeature,
event_type_names::kPointermove, nullptr},
{html_names::kOnpointeroutAttr, kNoWebFeature,
event_type_names::kPointerout, nullptr},
{html_names::kOnpointeroverAttr, kNoWebFeature,
event_type_names::kPointerover, nullptr},
{html_names::kOnpointerrawupdateAttr, kNoWebFeature,
event_type_names::kPointerrawupdate, nullptr},
{html_names::kOnpointerupAttr, kNoWebFeature,
event_type_names::kPointerup, nullptr},
{html_names::kOnprogressAttr, kNoWebFeature, event_type_names::kProgress,
nullptr},
{html_names::kOnratechangeAttr, kNoWebFeature,
event_type_names::kRatechange, nullptr},
{html_names::kOnresetAttr, kNoWebFeature, event_type_names::kReset,
nullptr},
{html_names::kOnresizeAttr, kNoWebFeature, event_type_names::kResize,
nullptr},
{html_names::kOnscrollAttr, kNoWebFeature, event_type_names::kScroll,
nullptr},
{html_names::kOnscrollendAttr, kNoWebFeature,
event_type_names::kScrollend, nullptr},
{html_names::kOnseekedAttr, kNoWebFeature, event_type_names::kSeeked,
nullptr},
{html_names::kOnseekingAttr, kNoWebFeature, event_type_names::kSeeking,
nullptr},
{html_names::kOnselectAttr, kNoWebFeature, event_type_names::kSelect,
nullptr},
{html_names::kOnselectstartAttr, kNoWebFeature,
event_type_names::kSelectstart, nullptr},
{html_names::kOnslotchangeAttr, kNoWebFeature,
event_type_names::kSlotchange, nullptr},
{html_names::kOnstalledAttr, kNoWebFeature, event_type_names::kStalled,
nullptr},
{html_names::kOnsubmitAttr, kNoWebFeature, event_type_names::kSubmit,
nullptr},
{html_names::kOnsuspendAttr, kNoWebFeature, event_type_names::kSuspend,
nullptr},
{html_names::kOntimeupdateAttr, kNoWebFeature,
event_type_names::kTimeupdate, nullptr},
{html_names::kOntoggleAttr, kNoWebFeature, event_type_names::kToggle,
nullptr},
{html_names::kOntouchcancelAttr, kNoWebFeature,
event_type_names::kTouchcancel, nullptr},
{html_names::kOntouchendAttr, kNoWebFeature, event_type_names::kTouchend,
nullptr},
{html_names::kOntouchmoveAttr, kNoWebFeature,
event_type_names::kTouchmove, nullptr},
{html_names::kOntouchstartAttr, kNoWebFeature,
event_type_names::kTouchstart, nullptr},
{html_names::kOntransitionendAttr, kNoWebFeature,
event_type_names::kWebkitTransitionEnd, nullptr},
{html_names::kOnvolumechangeAttr, kNoWebFeature,
event_type_names::kVolumechange, nullptr},
{html_names::kOnwaitingAttr, kNoWebFeature, event_type_names::kWaiting,
nullptr},
{html_names::kOnwebkitanimationendAttr, kNoWebFeature,
event_type_names::kWebkitAnimationEnd, nullptr},
{html_names::kOnwebkitanimationiterationAttr, kNoWebFeature,
event_type_names::kWebkitAnimationIteration, nullptr},
{html_names::kOnwebkitanimationstartAttr, kNoWebFeature,
event_type_names::kWebkitAnimationStart, nullptr},
{html_names::kOnwebkitfullscreenchangeAttr, kNoWebFeature,
event_type_names::kWebkitfullscreenchange, nullptr},
{html_names::kOnwebkitfullscreenerrorAttr, kNoWebFeature,
event_type_names::kWebkitfullscreenerror, nullptr},
{html_names::kOnwebkittransitionendAttr, kNoWebFeature,
event_type_names::kWebkitTransitionEnd, nullptr},
{html_names::kOnwheelAttr, kNoWebFeature, event_type_names::kWheel,
nullptr},
// Begin ARIA attributes.
{html_names::kAriaActivedescendantAttr,
WebFeature::kARIAActiveDescendantAttribute, kNoEvent, nullptr},
{html_names::kAriaAtomicAttr, WebFeature::kARIAAtomicAttribute, kNoEvent,
nullptr},
{html_names::kAriaAutocompleteAttr,
WebFeature::kARIAAutocompleteAttribute, kNoEvent, nullptr},
{html_names::kAriaBusyAttr, WebFeature::kARIABusyAttribute, kNoEvent,
nullptr},
{html_names::kAriaCheckedAttr, WebFeature::kARIACheckedAttribute,
kNoEvent, nullptr},
{html_names::kAriaColcountAttr, WebFeature::kARIAColCountAttribute,
kNoEvent, nullptr},
{html_names::kAriaColindexAttr, WebFeature::kARIAColIndexAttribute,
kNoEvent, nullptr},
{html_names::kAriaColspanAttr, WebFeature::kARIAColSpanAttribute,
kNoEvent, nullptr},
{html_names::kAriaControlsAttr, WebFeature::kARIAControlsAttribute,
kNoEvent, nullptr},
{html_names::kAriaCurrentAttr, WebFeature::kARIACurrentAttribute,
kNoEvent, nullptr},
{html_names::kAriaDescribedbyAttr, WebFeature::kARIADescribedByAttribute,
kNoEvent, nullptr},
{html_names::kAriaDescriptionAttr, WebFeature::kARIADescriptionAttribute,
kNoEvent, nullptr},
{html_names::kAriaDetailsAttr, WebFeature::kARIADetailsAttribute,
kNoEvent, nullptr},
{html_names::kAriaDisabledAttr, WebFeature::kARIADisabledAttribute,
kNoEvent, nullptr},
{html_names::kAriaDropeffectAttr, WebFeature::kARIADropEffectAttribute,
kNoEvent, nullptr},
{html_names::kAriaErrormessageAttr,
WebFeature::kARIAErrorMessageAttribute, kNoEvent, nullptr},
{html_names::kAriaExpandedAttr, WebFeature::kARIAExpandedAttribute,
kNoEvent, nullptr},
{html_names::kAriaFlowtoAttr, WebFeature::kARIAFlowToAttribute, kNoEvent,
nullptr},
{html_names::kAriaGrabbedAttr, WebFeature::kARIAGrabbedAttribute,
kNoEvent, nullptr},
{html_names::kAriaHaspopupAttr, WebFeature::kARIAHasPopupAttribute,
kNoEvent, nullptr},
{html_names::kAriaHiddenAttr, WebFeature::kARIAHiddenAttribute, kNoEvent,
nullptr},
{html_names::kAriaInvalidAttr, WebFeature::kARIAInvalidAttribute,
kNoEvent, nullptr},
{html_names::kAriaKeyshortcutsAttr,
WebFeature::kARIAKeyShortcutsAttribute, kNoEvent, nullptr},
{html_names::kAriaLabelAttr, WebFeature::kARIALabelAttribute, kNoEvent,
nullptr},
{html_names::kAriaLabeledbyAttr, WebFeature::kARIALabeledByAttribute,
kNoEvent, nullptr},
{html_names::kAriaLabelledbyAttr, WebFeature::kARIALabelledByAttribute,
kNoEvent, nullptr},
{html_names::kAriaLevelAttr, WebFeature::kARIALevelAttribute, kNoEvent,
nullptr},
{html_names::kAriaLiveAttr, WebFeature::kARIALiveAttribute, kNoEvent,
nullptr},
{html_names::kAriaModalAttr, WebFeature::kARIAModalAttribute, kNoEvent,
nullptr},
{html_names::kAriaMultilineAttr, WebFeature::kARIAMultilineAttribute,
kNoEvent, nullptr},
{html_names::kAriaMultiselectableAttr,
WebFeature::kARIAMultiselectableAttribute, kNoEvent, nullptr},
{html_names::kAriaOrientationAttr, WebFeature::kARIAOrientationAttribute,
kNoEvent, nullptr},
{html_names::kAriaOwnsAttr, WebFeature::kARIAOwnsAttribute, kNoEvent,
nullptr},
{html_names::kAriaPlaceholderAttr, WebFeature::kARIAPlaceholderAttribute,
kNoEvent, nullptr},
{html_names::kAriaPosinsetAttr, WebFeature::kARIAPosInSetAttribute,
kNoEvent, nullptr},
{html_names::kAriaPressedAttr, WebFeature::kARIAPressedAttribute,
kNoEvent, nullptr},
{html_names::kAriaReadonlyAttr, WebFeature::kARIAReadOnlyAttribute,
kNoEvent, nullptr},
{html_names::kAriaRelevantAttr, WebFeature::kARIARelevantAttribute,
kNoEvent, nullptr},
{html_names::kAriaRequiredAttr, WebFeature::kARIARequiredAttribute,
kNoEvent, nullptr},
{html_names::kAriaRoledescriptionAttr,
WebFeature::kARIARoleDescriptionAttribute, kNoEvent, nullptr},
{html_names::kAriaRowcountAttr, WebFeature::kARIARowCountAttribute,
kNoEvent, nullptr},
{html_names::kAriaRowindexAttr, WebFeature::kARIARowIndexAttribute,
kNoEvent, nullptr},
{html_names::kAriaRowspanAttr, WebFeature::kARIARowSpanAttribute,
kNoEvent, nullptr},
{html_names::kAriaSelectedAttr, WebFeature::kARIASelectedAttribute,
kNoEvent, nullptr},
{html_names::kAriaSetsizeAttr, WebFeature::kARIASetSizeAttribute,
kNoEvent, nullptr},
{html_names::kAriaSortAttr, WebFeature::kARIASortAttribute, kNoEvent,
nullptr},
{html_names::kAriaTouchpassthroughAttr,
WebFeature::kARIATouchpassthroughAttribute, kNoEvent, nullptr},
{html_names::kAriaValuemaxAttr, WebFeature::kARIAValueMaxAttribute,
kNoEvent, nullptr},
{html_names::kAriaValueminAttr, WebFeature::kARIAValueMinAttribute,
kNoEvent, nullptr},
{html_names::kAriaValuenowAttr, WebFeature::kARIAValueNowAttribute,
kNoEvent, nullptr},
{html_names::kAriaValuetextAttr, WebFeature::kARIAValueTextAttribute,
kNoEvent, nullptr},
{html_names::kAriaVirtualcontentAttr,
WebFeature::kARIAVirtualcontentAttribute, kNoEvent, nullptr},
// End ARIA attributes.
{html_names::kAutocapitalizeAttr, WebFeature::kAutocapitalizeAttribute,
kNoEvent, nullptr},
};
using AttributeToTriggerIndexMap = HashMap<QualifiedName, uint32_t>;
DEFINE_STATIC_LOCAL(AttributeToTriggerIndexMap,
attribute_to_trigger_index_map, ());
if (!attribute_to_trigger_index_map.size()) {
for (uint32_t i = 0; i < base::size(attribute_triggers); ++i)
attribute_to_trigger_index_map.insert(attribute_triggers[i].attribute, i);
}
auto iter = attribute_to_trigger_index_map.find(attr_name);
if (iter != attribute_to_trigger_index_map.end())
return &attribute_triggers[iter->value];
return nullptr;
}
// static
const AtomicString& HTMLElement::EventNameForAttributeName(
const QualifiedName& attr_name) {
AttributeTriggers* triggers = TriggersForAttributeName(attr_name);
if (triggers)
return triggers->event;
return g_null_atom;
}
void HTMLElement::AttributeChanged(const AttributeModificationParams& params) {
Element::AttributeChanged(params);
if (params.name == html_names::kDisabledAttr &&
params.old_value.IsNull() != params.new_value.IsNull()) {
if (IsFormAssociatedCustomElement()) {
EnsureElementInternals().DisabledAttributeChanged();
if (params.reason == AttributeModificationReason::kDirectly &&
IsDisabledFormControl() &&
AdjustedFocusedElementInTreeScope() == this)
blur();
}
return;
}
if (params.reason != AttributeModificationReason::kDirectly)
return;
// adjustedFocusedElementInTreeScope() is not trivial. We should check
// attribute names, then call adjustedFocusedElementInTreeScope().
if (params.name == html_names::kHiddenAttr && !params.new_value.IsNull()) {
if (AdjustedFocusedElementInTreeScope() == this)
blur();
} else if (params.name == html_names::kSpellcheckAttr) {
if (GetDocument().GetFrame()) {
GetDocument().GetFrame()->GetSpellChecker().RespondToChangedEnablement(
*this, IsSpellCheckingEnabled());
}
} else if (params.name == html_names::kContenteditableAttr) {
if (GetDocument().GetFrame()) {
GetDocument()
.GetFrame()
->GetSpellChecker()
.RemoveSpellingAndGrammarMarkers(
*this, SpellChecker::ElementsType::kOnlyNonEditable);
}
if (AdjustedFocusedElementInTreeScope() != this)
return;
// 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();
}
}
void HTMLElement::ParseAttribute(const AttributeModificationParams& params) {
AttributeTriggers* triggers = TriggersForAttributeName(params.name);
if (!triggers)
return;
if (triggers->event != g_null_atom) {
SetAttributeEventListener(
triggers->event,
JSEventHandlerForContentAttribute::Create(
GetExecutionContext(), params.name, params.new_value));
}
if (triggers->web_feature != kNoWebFeature) {
// Count usage of attributes but ignore attributes in user agent shadow DOM.
if (!IsInUserAgentShadowRoot())
UseCounter::Count(GetDocument(), triggers->web_feature);
}
if (triggers->function)
((*this).*(triggers->function))(params);
}
DocumentFragment* HTMLElement::TextToFragment(const String& text,
ExceptionState& exception_state) {
DocumentFragment* fragment = DocumentFragment::Create(GetDocument());
unsigned i, length = text.length();
UChar c = 0;
for (unsigned start = 0; start < length;) {
// Find next line break.
for (i = start; i < length; i++) {
c = text[i];
if (c == '\r' || c == '\n')
break;
}
if (i > start) {
fragment->AppendChild(
Text::Create(GetDocument(), text.Substring(start, i - start)),
exception_state);
if (exception_state.HadException())
return nullptr;
}
if (i == length)
break;
fragment->AppendChild(MakeGarbageCollected<HTMLBRElement>(GetDocument()),
exception_state);
if (exception_state.HadException())
return nullptr;
// Make sure \r\n doesn't result in two line breaks.
if (c == '\r' && i + 1 < length && text[i + 1] == '\n')
i++;
start = i + 1; // Character after line break.
}
return fragment;
}
V8UnionStringTreatNullAsEmptyStringOrTrustedScript*
HTMLElement::innerTextForBinding() {
return MakeGarbageCollected<
V8UnionStringTreatNullAsEmptyStringOrTrustedScript>(innerText());
}
void HTMLElement::setInnerTextForBinding(
const V8UnionStringTreatNullAsEmptyStringOrTrustedScript*
string_or_trusted_script,
ExceptionState& exception_state) {
String value;
switch (string_or_trusted_script->GetContentType()) {
case V8UnionStringTreatNullAsEmptyStringOrTrustedScript::ContentType::
kStringTreatNullAsEmptyString:
value = string_or_trusted_script->GetAsStringTreatNullAsEmptyString();
break;
case V8UnionStringTreatNullAsEmptyStringOrTrustedScript::ContentType::
kTrustedScript:
value = string_or_trusted_script->GetAsTrustedScript()->toString();
break;
}
setInnerText(value, exception_state);
}
String HTMLElement::innerText() {
return Element::innerText();
}
void HTMLElement::setInnerText(const String& text,
ExceptionState& exception_state) {
// FIXME: This doesn't take whitespace collapsing into account at all.
if (!text.Contains('\n') && !text.Contains('\r')) {
if (text.IsEmpty()) {
RemoveChildren();
return;
}
ReplaceChildrenWithText(this, text, exception_state);
return;
}
// Add text nodes and <br> elements.
DocumentFragment* fragment = TextToFragment(text, exception_state);
if (!exception_state.HadException())
ReplaceChildrenWithFragment(this, fragment, exception_state);
}
void HTMLElement::setOuterText(const String& text,
ExceptionState& exception_state) {
ContainerNode* parent = parentNode();
if (!parent) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNoModificationAllowedError,
"The element has no parent.");
return;
}
Node* prev = previousSibling();
Node* next = nextSibling();
Node* new_child = nullptr;
// Convert text to fragment with <br> tags instead of linebreaks if needed.
if (text.Contains('\r') || text.Contains('\n'))
new_child = TextToFragment(text, exception_state);
else
new_child = Text::Create(GetDocument(), text);
if (exception_state.HadException())
return;
parent->ReplaceChild(new_child, this, exception_state);
Node* node = next ? next->previousSibling() : nullptr;
auto* next_text_node = DynamicTo<Text>(node);
if (!exception_state.HadException() && next_text_node)
MergeWithNextTextNode(next_text_node, exception_state);
auto* prev_text_node = DynamicTo<Text>(prev);
if (!exception_state.HadException() && prev && prev->IsTextNode())
MergeWithNextTextNode(prev_text_node, exception_state);
}
void HTMLElement::ApplyAspectRatioToStyle(const AtomicString& width,
const AtomicString& height,
MutableCSSPropertyValueSet* style) {
HTMLDimension width_dim;
if (!ParseDimensionValue(width, width_dim) || !width_dim.IsAbsolute())
return;
HTMLDimension height_dim;
if (!ParseDimensionValue(height, height_dim) || !height_dim.IsAbsolute())
return;
ApplyAspectRatioToStyle(width_dim.Value(), height_dim.Value(), style);
}
void HTMLElement::ApplyIntegerAspectRatioToStyle(
const AtomicString& width,
const AtomicString& height,
MutableCSSPropertyValueSet* style) {
unsigned width_val = 0;
if (!ParseHTMLNonNegativeInteger(width, width_val))
return;
unsigned height_val = 0;
if (!ParseHTMLNonNegativeInteger(height, height_val))
return;
ApplyAspectRatioToStyle(width_val, height_val, style);
}
void HTMLElement::ApplyAspectRatioToStyle(double width,
double height,
MutableCSSPropertyValueSet* style) {
auto* width_val = CSSNumericLiteralValue::Create(
width, CSSPrimitiveValue::UnitType::kNumber);
auto* height_val = CSSNumericLiteralValue::Create(
height, CSSPrimitiveValue::UnitType::kNumber);
auto* ratio_value =
MakeGarbageCollected<cssvalue::CSSRatioValue>(*width_val, *height_val);
CSSValueList* list = CSSValueList::CreateSpaceSeparated();
list->Append(*CSSIdentifierValue::Create(CSSValueID::kAuto));
list->Append(*ratio_value);
style->SetProperty(CSSPropertyID::kAspectRatio, *list);
}
void HTMLElement::ApplyAlignmentAttributeToStyle(
const AtomicString& alignment,
MutableCSSPropertyValueSet* style) {
// Vertical alignment with respect to the current baseline of the text
// right or left means floating images.
CSSValueID float_value = CSSValueID::kInvalid;
CSSValueID vertical_align_value = CSSValueID::kInvalid;
if (EqualIgnoringASCIICase(alignment, "absmiddle") ||
EqualIgnoringASCIICase(alignment, "abscenter")) {
vertical_align_value = CSSValueID::kMiddle;
} else if (EqualIgnoringASCIICase(alignment, "absbottom")) {
vertical_align_value = CSSValueID::kBottom;
} else if (EqualIgnoringASCIICase(alignment, "left")) {
float_value = CSSValueID::kLeft;
vertical_align_value = CSSValueID::kTop;
} else if (EqualIgnoringASCIICase(alignment, "right")) {
float_value = CSSValueID::kRight;
vertical_align_value = CSSValueID::kTop;
} else if (EqualIgnoringASCIICase(alignment, "top")) {
vertical_align_value = CSSValueID::kTop;
} else if (EqualIgnoringASCIICase(alignment, "middle")) {
vertical_align_value = CSSValueID::kWebkitBaselineMiddle;
} else if (EqualIgnoringASCIICase(alignment, "center")) {
vertical_align_value = CSSValueID::kMiddle;
} else if (EqualIgnoringASCIICase(alignment, "bottom")) {
vertical_align_value = CSSValueID::kBaseline;
} else if (EqualIgnoringASCIICase(alignment, "texttop")) {
vertical_align_value = CSSValueID::kTextTop;
}
if (IsValidCSSValueID(float_value)) {
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kFloat,
float_value);
}
if (IsValidCSSValueID(vertical_align_value)) {
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kVerticalAlign, vertical_align_value);
}
}
bool HTMLElement::HasCustomFocusLogic() const {
return false;
}
ContentEditableType HTMLElement::contentEditableNormalized() const {
const AtomicString& value =
FastGetAttribute(html_names::kContenteditableAttr);
if (value.IsNull())
return ContentEditableType::kInherit;
if (value.IsEmpty() || EqualIgnoringASCIICase(value, "true"))
return ContentEditableType::kContentEditable;
if (EqualIgnoringASCIICase(value, "false"))
return ContentEditableType::kNotContentEditable;
if (EqualIgnoringASCIICase(value, "plaintext-only"))
return ContentEditableType::kPlaintextOnly;
return ContentEditableType::kInherit;
}
String HTMLElement::contentEditable() const {
switch (contentEditableNormalized()) {
case ContentEditableType::kInherit:
return "inherit";
case ContentEditableType::kContentEditable:
return "true";
case ContentEditableType::kNotContentEditable:
return "false";
case ContentEditableType::kPlaintextOnly:
return "plaintext-only";
}
}
void HTMLElement::setContentEditable(const String& enabled,
ExceptionState& exception_state) {
if (EqualIgnoringASCIICase(enabled, "true"))
setAttribute(html_names::kContenteditableAttr, "true");
else if (EqualIgnoringASCIICase(enabled, "false"))
setAttribute(html_names::kContenteditableAttr, "false");
else if (EqualIgnoringASCIICase(enabled, "plaintext-only"))
setAttribute(html_names::kContenteditableAttr, "plaintext-only");
else if (EqualIgnoringASCIICase(enabled, "inherit"))
removeAttribute(html_names::kContenteditableAttr);
else
exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
"The value provided ('" + enabled +
"') is not one of 'true', 'false', "
"'plaintext-only', or 'inherit'.");
}
const AtomicString& HTMLElement::autocapitalize() const {
DEFINE_STATIC_LOCAL(const AtomicString, kOff, ("off"));
DEFINE_STATIC_LOCAL(const AtomicString, kNone, ("none"));
DEFINE_STATIC_LOCAL(const AtomicString, kCharacters, ("characters"));
DEFINE_STATIC_LOCAL(const AtomicString, kWords, ("words"));
DEFINE_STATIC_LOCAL(const AtomicString, kSentences, ("sentences"));
const AtomicString& value = FastGetAttribute(html_names::kAutocapitalizeAttr);
if (value.IsEmpty())
return g_empty_atom;
if (EqualIgnoringASCIICase(value, kNone) ||
EqualIgnoringASCIICase(value, kOff))
return kNone;
if (EqualIgnoringASCIICase(value, kCharacters))
return kCharacters;
if (EqualIgnoringASCIICase(value, kWords))
return kWords;
// "sentences", "on", or an invalid value
return kSentences;
}
void HTMLElement::setAutocapitalize(const AtomicString& value) {
setAttribute(html_names::kAutocapitalizeAttr, value);
}
bool HTMLElement::isContentEditableForBinding() const {
return IsEditableOrEditingHost(*this);
}
bool HTMLElement::draggable() const {
return EqualIgnoringASCIICase(FastGetAttribute(html_names::kDraggableAttr),
"true");
}
void HTMLElement::setDraggable(bool value) {
setAttribute(html_names::kDraggableAttr, value ? "true" : "false");
}
bool HTMLElement::spellcheck() const {
return IsSpellCheckingEnabled();
}
void HTMLElement::setSpellcheck(bool enable) {
setAttribute(html_names::kSpellcheckAttr, enable ? "true" : "false");
}
void HTMLElement::click() {
DispatchSimulatedClick(nullptr, SimulatedClickCreationScope::kFromScript);
}
void HTMLElement::AccessKeyAction(SimulatedClickCreationScope creation_scope) {
DispatchSimulatedClick(nullptr, creation_scope);
}
String HTMLElement::title() const {
return FastGetAttribute(html_names::kTitleAttr);
}
TranslateAttributeMode HTMLElement::GetTranslateAttributeMode() const {
const AtomicString& value = FastGetAttribute(html_names::kTranslateAttr);
if (value == g_null_atom)
return kTranslateAttributeInherit;
if (EqualIgnoringASCIICase(value, "yes") || EqualIgnoringASCIICase(value, ""))
return kTranslateAttributeYes;
if (EqualIgnoringASCIICase(value, "no"))
return kTranslateAttributeNo;
return kTranslateAttributeInherit;
}
bool HTMLElement::translate() const {
for (const HTMLElement* element = this; element;
element = Traversal<HTMLElement>::FirstAncestor(*element)) {
TranslateAttributeMode mode = element->GetTranslateAttributeMode();
if (mode != kTranslateAttributeInherit) {
DCHECK(mode == kTranslateAttributeYes || mode == kTranslateAttributeNo);
return mode == kTranslateAttributeYes;
}
}
// Default on the root element is translate=yes.
return true;
}
void HTMLElement::setTranslate(bool enable) {
setAttribute(html_names::kTranslateAttr, enable ? "yes" : "no");
}
// Returns the conforming 'dir' value associated with the state the attribute is
// in (in its canonical case), if any, or the empty string if the attribute is
// in a state that has no associated keyword value or if the attribute is not in
// a defined state (e.g. the attribute is missing and there is no missing value
// default).
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-dom-interfaces.html#limited-to-only-known-values
static inline const AtomicString& ToValidDirValue(const AtomicString& value) {
DEFINE_STATIC_LOCAL(const AtomicString, ltr_value, ("ltr"));
DEFINE_STATIC_LOCAL(const AtomicString, rtl_value, ("rtl"));
DEFINE_STATIC_LOCAL(const AtomicString, auto_value, ("auto"));
if (EqualIgnoringASCIICase(value, ltr_value))
return ltr_value;
if (EqualIgnoringASCIICase(value, rtl_value))
return rtl_value;
if (EqualIgnoringASCIICase(value, auto_value))
return auto_value;
return g_null_atom;
}
const AtomicString& HTMLElement::dir() {
return ToValidDirValue(FastGetAttribute(html_names::kDirAttr));
}
void HTMLElement::setDir(const AtomicString& value) {
setAttribute(html_names::kDirAttr, value);
}
HTMLFormElement* HTMLElement::formOwner() const {
if (const auto* internals = GetElementInternals())
return internals->Form();
return nullptr;
}
HTMLFormElement* HTMLElement::FindFormAncestor() const {
return Traversal<HTMLFormElement>::FirstAncestor(*this);
}
static inline bool ElementAffectsDirectionality(const Node* node) {
auto* html_element = DynamicTo<HTMLElement>(node);
return html_element && (IsA<HTMLBDIElement>(*html_element) ||
IsValidDirAttribute(html_element->FastGetAttribute(
html_names::kDirAttr)));
}
void HTMLElement::ChildrenChanged(const ChildrenChange& change) {
Element::ChildrenChanged(change);
if (HasDirectionAuto()) {
SetSelfOrAncestorHasDirAutoAttribute();
GetDocument().SetDirAttributeDirty();
}
if (GetDocument().IsDirAttributeDirty()) {
AdjustDirectionalityIfNeededAfterChildrenChanged(change);
if (change.IsChildInsertion() && !SelfOrAncestorHasDirAutoAttribute()) {
auto* element = DynamicTo<HTMLElement>(change.sibling_changed);
if (element && !element->NeedsInheritDirectionalityFromParent() &&
!ElementAffectsDirectionality(element))
element->UpdateDirectionalityAndDescendant(CachedDirectionality());
}
}
}
bool HTMLElement::HasDirectionAuto() const {
// <bdi> defaults to dir="auto"
// https://html.spec.whatwg.org/C/#the-bdi-element
const AtomicString& direction = FastGetAttribute(html_names::kDirAttr);
return (IsA<HTMLBDIElement>(*this) && direction == g_null_atom) ||
EqualIgnoringASCIICase(direction, "auto");
}
template <typename Traversal>
absl::optional<TextDirection> HTMLElement::ResolveAutoDirectionality(
bool& is_deferred,
Node* stay_within) const {
is_deferred = false;
if (auto* input_element = DynamicTo<HTMLInputElement>(*this)) {
bool has_strong_directionality;
return DetermineDirectionality(input_element->value(),
&has_strong_directionality);
}
// For <textarea>, the heuristic is applied on a per-paragraph level, and
// we should traverse the flat tree.
Node* node = (IsA<HTMLTextAreaElement>(*this) || IsA<HTMLSlotElement>(*this))
? FlatTreeTraversal::FirstChild(*this)
: Traversal::FirstChild(*this);
while (node) {
// Skip bdi, script, style and text form controls.
auto* element = DynamicTo<Element>(node);
if (EqualIgnoringASCIICase(node->nodeName(), "bdi") ||
IsA<HTMLScriptElement>(*node) || IsA<HTMLStyleElement>(*node) ||
(element && element->IsTextControl()) ||
(element && element->ShadowPseudoId() ==
shadow_element_names::kPseudoInputPlaceholder)) {
node = Traversal::NextSkippingChildren(*node, stay_within);
continue;
}
auto* slot = ToHTMLSlotElementIfSupportsAssignmentOrNull(node);
if (slot) {
ShadowRoot* root = slot->ContainingShadowRoot();
// Defer to adjust the directionality to avoid recalcuating slot
// assignment in FlatTreeTraversal when updating slot.
// ResolveAutoDirectionality will be adjusted after recalculating its
// children.
if (root->NeedsSlotAssignmentRecalc()) {
is_deferred = true;
return TextDirection::kLtr;
}
}
// Skip elements with valid dir attribute
if (auto* element_node = DynamicTo<Element>(node)) {
AtomicString dir_attribute_value =
element_node->FastGetAttribute(html_names::kDirAttr);
if (IsValidDirAttribute(dir_attribute_value)) {
node = Traversal::NextSkippingChildren(*node, stay_within);
continue;
}
}
if (node->IsTextNode()) {
bool has_strong_directionality;
TextDirection text_direction = DetermineDirectionality(
node->textContent(true), &has_strong_directionality);
if (has_strong_directionality)
return text_direction;
}
if (slot) {
absl::optional<TextDirection> text_direction =
slot->ResolveAutoDirectionality<FlatTreeTraversal>(is_deferred,
stay_within);
if (text_direction.has_value())
return text_direction;
}
node = Traversal::Next(*node, stay_within);
}
return absl::nullopt;
}
void HTMLElement::AdjustDirectionalityIfNeededAfterChildAttributeChanged(
Element* child) {
DCHECK(SelfOrAncestorHasDirAutoAttribute());
bool is_deferred;
TextDirection text_direction =
ResolveAutoDirectionality<NodeTraversal>(is_deferred, this)
.value_or(TextDirection::kLtr);
if (CachedDirectionality() != text_direction && !is_deferred) {
SetCachedDirectionality(text_direction);
for (Element* element_to_adjust = this; element_to_adjust;
element_to_adjust =
FlatTreeTraversal::ParentElement(*element_to_adjust)) {
if (ElementAffectsDirectionality(element_to_adjust)) {
DynamicTo<HTMLElement>(element_to_adjust)
->UpdateDirectionalityAndDescendant(text_direction);
const ComputedStyle* style = GetComputedStyle();
if (style && style->Direction() != text_direction) {
element_to_adjust->SetNeedsStyleRecalc(
kLocalStyleChange, StyleChangeReasonForTracing::Create(
style_change_reason::kWritingModeChange));
}
return;
}
}
}
}
bool HTMLElement::CalculateAndAdjustAutoDirectionality(Node* stay_within) {
bool is_deferred = false;
TextDirection text_direction =
ResolveAutoDirectionality<NodeTraversal>(is_deferred, stay_within)
.value_or(TextDirection::kLtr);
if (CachedDirectionality() != text_direction && !is_deferred) {
UpdateDirectionalityAndDescendant(text_direction);
const ComputedStyle* style = GetComputedStyle();
if (style && style->Direction() != text_direction) {
SetNeedsStyleRecalc(kLocalStyleChange,
StyleChangeReasonForTracing::Create(
style_change_reason::kWritingModeChange));
return true;
}
}
return false;
}
void HTMLElement::AdjustDirectionalityIfNeededAfterChildrenChanged(
const ChildrenChange& change) {
if (!SelfOrAncestorHasDirAutoAttribute())
return;
Node* stay_within = nullptr;
bool has_strong_directionality;
if (change.type == ChildrenChangeType::kTextChanged) {
TextDirection old_text_direction =
DetermineDirectionality(change.old_text, &has_strong_directionality);
auto* character_data = DynamicTo<CharacterData>(change.sibling_changed);
DCHECK(character_data);
TextDirection new_text_direction = DetermineDirectionality(
character_data->data(), &has_strong_directionality);
if (old_text_direction == new_text_direction)
return;
stay_within = change.sibling_changed;
} else if (change.IsChildInsertion()) {
if (change.sibling_changed->IsTextNode()) {
TextDirection new_text_direction =
DetermineDirectionality(change.sibling_changed->textContent(true),
&has_strong_directionality);
if (!has_strong_directionality ||
new_text_direction == CachedDirectionality())
return;
}
stay_within = change.sibling_changed;
}
UpdateDescendantHasDirAutoAttribute(true /* has_dir_auto */);
for (Element* element_to_adjust = this; element_to_adjust;
element_to_adjust =
FlatTreeTraversal::ParentElement(*element_to_adjust)) {
if (ElementAffectsDirectionality(element_to_adjust)) {
if (To<HTMLElement>(element_to_adjust)
->CalculateAndAdjustAutoDirectionality(
stay_within ? stay_within : element_to_adjust)) {
SetNeedsStyleRecalc(kLocalStyleChange,
StyleChangeReasonForTracing::Create(
style_change_reason::kPseudoClass));
}
if (RuntimeEnabledFeatures::CSSPseudoDirEnabled())
element_to_adjust->PseudoStateChanged(CSSSelector::kPseudoDir);
return;
}
}
}
void HTMLElement::AdjustDirectionalityIfNeededAfterShadowRootChanged() {
DCHECK(IsShadowHost(this));
if (SelfOrAncestorHasDirAutoAttribute()) {
for (auto* element_to_adjust = this; element_to_adjust;
element_to_adjust = DynamicTo<HTMLElement>(
FlatTreeTraversal::ParentElement(*element_to_adjust))) {
if (ElementAffectsDirectionality(element_to_adjust)) {
element_to_adjust->CalculateAndAdjustAutoDirectionality(
element_to_adjust);
return;
}
}
} else if (!NeedsInheritDirectionalityFromParent()) {
UpdateDescendantDirectionality(CachedDirectionality());
}
}
void HTMLElement::AdjustCandidateDirectionalityForSlot(
HeapHashSet<Member<Node>> candidate_set) {
HeapHashSet<Member<HTMLElement>> directionality_set;
// Transfer a candidate directionality set to |directionality_set| to avoid
// the tree walk to the duplicated parent node for the directionality.
for (auto& node : candidate_set) {
Node* node_to_adjust = node.Get();
if (!node->SelfOrAncestorHasDirAutoAttribute()) {
if (ElementAffectsDirectionality(node))
continue;
auto* slot = node->AssignedSlot();
if (slot && slot->SelfOrAncestorHasDirAutoAttribute()) {
node_to_adjust = slot;
} else {
if (slot && !slot->NeedsInheritDirectionalityFromParent()) {
node->SetCachedDirectionality(slot->CachedDirectionality());
}
continue;
}
}
bool needs_slot_assignment_recalc = false;
for (auto* element_to_adjust = DynamicTo<HTMLElement>(node_to_adjust);
element_to_adjust;
element_to_adjust = GetParentForDirectionality(
*element_to_adjust, needs_slot_assignment_recalc)) {
if (ElementAffectsDirectionality(element_to_adjust)) {
directionality_set.insert(element_to_adjust);
continue;
}
}
}
for (auto& element : directionality_set) {
if (element->CalculateAndAdjustAutoDirectionality(element) &&
RuntimeEnabledFeatures::CSSPseudoDirEnabled()) {
element->SetNeedsStyleRecalc(kLocalStyleChange,
StyleChangeReasonForTracing::Create(
style_change_reason::kPseudoClass));
}
}
}
Node::InsertionNotificationRequest HTMLElement::InsertedInto(
ContainerNode& insertion_point) {
// Process the superclass first to ensure that `InActiveDocument()` is
// updated.
Element::InsertedInto(insertion_point);
HideNonce();
if (IsFormAssociatedCustomElement())
EnsureElementInternals().InsertedInto(insertion_point);
return kInsertionDone;
}
void HTMLElement::RemovedFrom(ContainerNode& insertion_point) {
Element::RemovedFrom(insertion_point);
if (IsFormAssociatedCustomElement())
EnsureElementInternals().RemovedFrom(insertion_point);
}
void HTMLElement::DidMoveToNewDocument(Document& old_document) {
if (IsFormAssociatedCustomElement())
EnsureElementInternals().DidMoveToNewDocument(old_document);
Element::DidMoveToNewDocument(old_document);
}
void HTMLElement::AddHTMLLengthToStyle(MutableCSSPropertyValueSet* style,
CSSPropertyID property_id,
const String& value,
AllowPercentage allow_percentage,
AllowZero allow_zero) {
HTMLDimension dimension;
if (!ParseDimensionValue(value, dimension))
return;
if (property_id == CSSPropertyID::kWidth &&
(dimension.IsPercentage() || dimension.IsRelative())) {
UseCounter::Count(GetDocument(), WebFeature::kHTMLElementDeprecatedWidth);
}
if (dimension.IsRelative())
return;
if (dimension.IsPercentage() &&
allow_percentage == kDontAllowPercentageValues)
return;
if (dimension.Value() == 0 && allow_zero == kDontAllowZeroValues)
return;
CSSPrimitiveValue::UnitType unit =
dimension.IsPercentage() ? CSSPrimitiveValue::UnitType::kPercentage
: CSSPrimitiveValue::UnitType::kPixels;
AddPropertyToPresentationAttributeStyle(style, property_id, dimension.Value(),
unit);
}
static RGBA32 ParseColorStringWithCrazyLegacyRules(const String& color_string) {
// Per spec, only look at the first 128 digits of the string.
const size_t kMaxColorLength = 128;
// We'll pad the buffer with two extra 0s later, so reserve two more than the
// max.
Vector<char, kMaxColorLength + 2> digit_buffer;
wtf_size_t i = 0;
// Skip a leading #.
if (color_string[0] == '#')
i = 1;
// Grab the first 128 characters, replacing non-hex characters with 0.
// Non-BMP characters are replaced with "00" due to them appearing as two
// "characters" in the String.
for (; i < color_string.length() && digit_buffer.size() < kMaxColorLength;
i++) {
if (!IsASCIIHexDigit(color_string[i]))
digit_buffer.push_back('0');
else
digit_buffer.push_back(color_string[i]);
}
if (!digit_buffer.size())
return Color::kBlack;
// Pad the buffer out to at least the next multiple of three in size.
digit_buffer.push_back('0');
digit_buffer.push_back('0');
if (digit_buffer.size() < 6)
return MakeRGB(ToASCIIHexValue(digit_buffer[0]),
ToASCIIHexValue(digit_buffer[1]),
ToASCIIHexValue(digit_buffer[2]));
// Split the digits into three components, then search the last 8 digits of
// each component.
DCHECK_GE(digit_buffer.size(), 6u);
wtf_size_t component_length = digit_buffer.size() / 3;
wtf_size_t component_search_window_length =
std::min<wtf_size_t>(component_length, 8);
wtf_size_t red_index = component_length - component_search_window_length;
wtf_size_t green_index =
component_length * 2 - component_search_window_length;
wtf_size_t blue_index = component_length * 3 - component_search_window_length;
// Skip digits until one of them is non-zero, or we've only got two digits
// left in the component.
while (digit_buffer[red_index] == '0' && digit_buffer[green_index] == '0' &&
digit_buffer[blue_index] == '0' &&
(component_length - red_index) > 2) {
red_index++;
green_index++;
blue_index++;
}
DCHECK_LT(red_index + 1, component_length);
DCHECK_GE(green_index, component_length);
DCHECK_LT(green_index + 1, component_length * 2);
DCHECK_GE(blue_index, component_length * 2);
SECURITY_DCHECK(blue_index + 1 < digit_buffer.size());
int red_value =
ToASCIIHexValue(digit_buffer[red_index], digit_buffer[red_index + 1]);
int green_value =
ToASCIIHexValue(digit_buffer[green_index], digit_buffer[green_index + 1]);
int blue_value =
ToASCIIHexValue(digit_buffer[blue_index], digit_buffer[blue_index + 1]);
return MakeRGB(red_value, green_value, blue_value);
}
// Color parsing that matches HTML's "rules for parsing a legacy color value"
bool HTMLElement::ParseColorWithLegacyRules(const String& attribute_value,
Color& parsed_color) {
// An empty string doesn't apply a color. (One containing only whitespace
// does, which is why this check occurs before stripping.)
if (attribute_value.IsEmpty())
return false;
String color_string = attribute_value.StripWhiteSpace();
// "transparent" doesn't apply a color either.
if (EqualIgnoringASCIICase(color_string, "transparent"))
return false;
// If the string is a 3/6-digit hex color or a named CSS color, use that.
// Apply legacy rules otherwise. Note color.setFromString() accepts 4/8-digit
// hex color, so restrict its use with length checks here to support legacy
// HTML attributes.
bool success = false;
if ((color_string.length() == 4 || color_string.length() == 7) &&
color_string[0] == '#')
success = parsed_color.SetFromString(color_string);
if (!success)
success = parsed_color.SetNamedColor(color_string);
if (!success) {
parsed_color.SetRGB(ParseColorStringWithCrazyLegacyRules(color_string));
success = true;
}
return success;
}
void HTMLElement::AddHTMLColorToStyle(MutableCSSPropertyValueSet* style,
CSSPropertyID property_id,
const String& attribute_value) {
Color parsed_color;
if (!ParseColorWithLegacyRules(attribute_value, parsed_color))
return;
style->SetProperty(property_id,
*cssvalue::CSSColor::Create(parsed_color.Rgb()));
}
LabelsNodeList* HTMLElement::labels() {
if (!IsLabelable())
return nullptr;
return EnsureCachedCollection<LabelsNodeList>(kLabelsNodeListType);
}
bool HTMLElement::IsInteractiveContent() const {
return false;
}
void HTMLElement::DefaultEventHandler(Event& event) {
auto* keyboard_event = DynamicTo<KeyboardEvent>(event);
if (event.type() == event_type_names::kKeypress && keyboard_event) {
HandleKeypressEvent(*keyboard_event);
if (event.DefaultHandled())
return;
}
Element::DefaultEventHandler(event);
}
bool HTMLElement::HandleKeyboardActivation(Event& event) {
auto* keyboard_event = DynamicTo<KeyboardEvent>(event);
if (keyboard_event) {
if (event.type() == event_type_names::kKeydown &&
keyboard_event->key() == " ") {
SetActive(true);
// No setDefaultHandled() - IE dispatches a keypress in this case.
return true;
}
if (event.type() == event_type_names::kKeypress) {
switch (keyboard_event->charCode()) {
case '\r':
DispatchSimulatedClick(&event);
event.SetDefaultHandled();
return true;
case ' ':
// Prevent scrolling down the page.
event.SetDefaultHandled();
return true;
}
}
if (event.type() == event_type_names::kKeyup &&
keyboard_event->key() == " ") {
if (IsActive())
DispatchSimulatedClick(&event);
event.SetDefaultHandled();
return true;
}
}
return false;
}
bool HTMLElement::MatchesReadOnlyPseudoClass() const {
return !MatchesReadWritePseudoClass();
}
// https://html.spec.whatwg.org/multipage/semantics-other.html#selector-read-write
// The :read-write pseudo-class must match ... elements that are editing hosts
// or editable and are neither input elements nor textarea elements
bool HTMLElement::MatchesReadWritePseudoClass() const {
return IsEditableOrEditingHost(*this);
}
void HTMLElement::HandleKeypressEvent(KeyboardEvent& event) {
if (!IsSpatialNavigationEnabled(GetDocument().GetFrame()) || !SupportsFocus())
return;
if (RuntimeEnabledFeatures::FocuslessSpatialNavigationEnabled())
return;
GetDocument().UpdateStyleAndLayoutTree();
// if the element is a text form control (like <input type=text> or
// <textarea>) or has contentEditable attribute on, we should enter a space or
// newline even in spatial navigation mode instead of handling it as a "click"
// action.
if (IsTextControl() || HasEditableStyle(*this))
return;
int char_code = event.charCode();
if (char_code == '\r' || char_code == ' ') {
DispatchSimulatedClick(&event);
event.SetDefaultHandled();
}
}
int HTMLElement::offsetLeftForBinding() {
GetDocument().EnsurePaintLocationDataValidForNode(
this, DocumentUpdateReason::kJavaScript);
Element* offset_parent = unclosedOffsetParent();
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(layout_object->PixelSnappedOffsetLeft(offset_parent)),
layout_object->StyleRef())
.Round();
return 0;
}
int HTMLElement::offsetTopForBinding() {
GetDocument().EnsurePaintLocationDataValidForNode(
this, DocumentUpdateReason::kJavaScript);
Element* offset_parent = unclosedOffsetParent();
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject())
return AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(layout_object->PixelSnappedOffsetTop(offset_parent)),
layout_object->StyleRef())
.Round();
return 0;
}
int HTMLElement::offsetWidthForBinding() {
GetDocument().EnsurePaintLocationDataValidForNode(
this, DocumentUpdateReason::kJavaScript);
Element* offset_parent = unclosedOffsetParent();
int result = 0;
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject()) {
result =
AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(layout_object->PixelSnappedOffsetWidth(offset_parent)),
layout_object->StyleRef())
.Round();
RecordScrollbarSizeForStudy(result, /* isWidth= */ true,
/* is_offset= */ true);
}
return result;
}
DISABLE_CFI_PERF
int HTMLElement::offsetHeightForBinding() {
GetDocument().EnsurePaintLocationDataValidForNode(
this, DocumentUpdateReason::kJavaScript);
Element* offset_parent = unclosedOffsetParent();
int result = 0;
if (LayoutBoxModelObject* layout_object = GetLayoutBoxModelObject()) {
result =
AdjustForAbsoluteZoom::AdjustLayoutUnit(
LayoutUnit(layout_object->PixelSnappedOffsetHeight(offset_parent)),
layout_object->StyleRef())
.Round();
RecordScrollbarSizeForStudy(result, /* is_width= */ false,
/* is_offset= */ true);
}
return result;
}
Element* HTMLElement::unclosedOffsetParent() {
GetDocument().UpdateStyleAndLayoutForNode(this,
DocumentUpdateReason::kJavaScript);
LayoutObject* layout_object = GetLayoutObject();
if (!layout_object)
return nullptr;
return layout_object->OffsetParent(this);
}
void HTMLElement::UpdateDescendantHasDirAutoAttribute(bool has_dir_auto) {
Node* node = FlatTreeTraversal::FirstChild(*this);
while (node) {
if (auto* element = DynamicTo<Element>(node)) {
AtomicString dir_attribute_value =
element->FastGetAttribute(html_names::kDirAttr);
if (IsValidDirAttribute(dir_attribute_value)) {
node = FlatTreeTraversal::NextSkippingChildren(*node, this);
continue;
}
if (auto* slot = ToHTMLSlotElementIfSupportsAssignmentOrNull(node)) {
ShadowRoot* root = slot->ContainingShadowRoot();
// Defer to adjust the directionality to avoid recalcuating slot
// assignment in FlatTreeTraversal when updating slot.
// Slot and its children will be updated after recalculating children.
if (root->NeedsSlotAssignmentRecalc()) {
root->SetNeedsDirAutoAttributeUpdate(true);
node = FlatTreeTraversal::NextSkippingChildren(*node, this);
continue;
}
}
if (!has_dir_auto) {
if (!element->SelfOrAncestorHasDirAutoAttribute()) {
node = FlatTreeTraversal::NextSkippingChildren(*node, this);
continue;
}
element->ClearSelfOrAncestorHasDirAutoAttribute();
} else {
if (element->SelfOrAncestorHasDirAutoAttribute()) {
node = FlatTreeTraversal::NextSkippingChildren(*node, this);
continue;
}
element->SetSelfOrAncestorHasDirAutoAttribute();
}
}
node = FlatTreeTraversal::Next(*node, this);
}
}
void HTMLElement::UpdateDirectionalityAndDescendant(TextDirection direction) {
SetCachedDirectionality(direction);
UpdateDescendantDirectionality(direction);
}
void HTMLElement::UpdateDescendantDirectionality(TextDirection direction) {
Node* node = FlatTreeTraversal::FirstChild(*this);
while (node) {
if (IsA<HTMLElement>(node)) {
if (ElementAffectsDirectionality(node) ||
node->CachedDirectionality() == direction) {
node = FlatTreeTraversal::NextSkippingChildren(*node, this);
continue;
}
node->SetCachedDirectionality(direction);
}
node = FlatTreeTraversal::Next(*node, this);
}
}
void HTMLElement::OnDirAttrChanged(const AttributeModificationParams& params) {
// If an ancestor has dir=auto, and this node has the first character,
// changes to dir attribute may affect the ancestor.
if (!IsValidDirAttribute(params.old_value) &&
!IsValidDirAttribute(params.new_value))
return;
GetDocument().SetDirAttributeDirty();
bool is_old_auto = SelfOrAncestorHasDirAutoAttribute();
bool is_new_auto = HasDirectionAuto();
bool needs_slot_assignment_recalc = false;
auto* parent =
GetParentForDirectionality(*this, needs_slot_assignment_recalc);
if (!is_old_auto || !is_new_auto) {
if (parent && parent->SelfOrAncestorHasDirAutoAttribute()) {
parent->AdjustDirectionalityIfNeededAfterChildAttributeChanged(this);
}
}
if (is_old_auto && !is_new_auto) {
ClearSelfOrAncestorHasDirAutoAttribute();
UpdateDescendantHasDirAutoAttribute(false /* has_dir_auto */);
} else if (!is_old_auto && is_new_auto) {
SetSelfOrAncestorHasDirAutoAttribute();
UpdateDescendantHasDirAutoAttribute(true /* has_dir_auto */);
}
if (is_new_auto) {
CalculateAndAdjustAutoDirectionality(this);
} else {
absl::optional<TextDirection> text_direction;
if (EqualIgnoringASCIICase(params.new_value, "ltr")) {
text_direction = TextDirection::kLtr;
} else if (EqualIgnoringASCIICase(params.new_value, "rtl")) {
text_direction = TextDirection::kRtl;
}
if (!text_direction.has_value()) {
if (parent) {
text_direction = parent->CachedDirectionality();
} else {
text_direction = TextDirection::kLtr;
}
}
if (needs_slot_assignment_recalc) {
SetNeedsInheritDirectionalityFromParent();
} else {
UpdateDirectionalityAndDescendant(*text_direction);
}
}
if (RuntimeEnabledFeatures::CSSPseudoDirEnabled()) {
SetNeedsStyleRecalc(
kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(style_change_reason::kPseudoClass));
PseudoStateChanged(CSSSelector::kPseudoDir);
}
}
void HTMLElement::OnFormAttrChanged(const AttributeModificationParams& params) {
if (IsFormAssociatedCustomElement())
EnsureElementInternals().FormAttributeChanged();
}
void HTMLElement::OnInertAttrChanged(
const AttributeModificationParams& params) {
// The |PropagateInertToChildFrames()| function might need to walk the flat
// tree to check for inert parent elements. So update slot assignments here.
GetDocument().GetSlotAssignmentEngine().RecalcSlotAssignments();
if (GetDocument().GetFrame()) {
GetDocument().GetFrame()->SetIsInert(GetDocument().LocalOwner() &&
GetDocument().LocalOwner()->IsInert());
}
}
void HTMLElement::OnLangAttrChanged(const AttributeModificationParams& params) {
PseudoStateChanged(CSSSelector::kPseudoLang);
}
void HTMLElement::OnNonceAttrChanged(
const AttributeModificationParams& params) {
if (params.new_value != g_empty_atom)
setNonce(params.new_value);
}
void HTMLElement::OnTabIndexAttrChanged(
const AttributeModificationParams& params) {
Element::ParseAttribute(params);
}
void HTMLElement::OnXMLLangAttrChanged(
const AttributeModificationParams& params) {
Element::ParseAttribute(params);
}
ElementInternals* HTMLElement::attachInternals(
ExceptionState& exception_state) {
// 1. If this's is value is not null, then throw a "NotSupportedError"
// DOMException.
if (IsValue()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"Unable to attach ElementInternals to a customized built-in element.");
return nullptr;
}
// 2. Let definition be the result of looking up a custom element definition
// given this's node document, its namespace, its local name, and null as the
// is value.
CustomElementRegistry* registry = CustomElement::Registry(*this);
auto* definition =
registry ? registry->DefinitionForName(localName()) : nullptr;
// 3. If definition is null, then throw an "NotSupportedError" DOMException.
if (!definition) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"Unable to attach ElementInternals to non-custom elements.");
return nullptr;
}
// 4. If definition's disable internals is true, then throw a
// "NotSupportedError" DOMException.
if (definition->DisableInternals()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"ElementInternals is disabled by disabledFeature static field.");
return nullptr;
}
// 5. If this's attached internals is true, then throw an "NotSupportedError"
// DOMException.
if (DidAttachInternals()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"ElementInternals for the specified element was already attached.");
return nullptr;
}
// 6. If this's custom element state is not "precustomized" or "custom", then
// throw a "NotSupportedError" DOMException.
if (GetCustomElementState() != CustomElementState::kCustom &&
GetCustomElementState() != CustomElementState::kPreCustomized) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"The attachInternals() function cannot be called prior to the "
"execution of the custom element constructor.");
return nullptr;
}
// 7. Set this's attached internals to true.
SetDidAttachInternals();
// 8. Return a new ElementInternals instance whose target element is this.
UseCounter::Count(GetDocument(), WebFeature::kElementAttachInternals);
return &EnsureElementInternals();
}
bool HTMLElement::IsFormAssociatedCustomElement() const {
return GetCustomElementState() == CustomElementState::kCustom &&
GetCustomElementDefinition()->IsFormAssociated();
}
bool HTMLElement::SupportsFocus() const {
return Element::SupportsFocus() && !IsDisabledFormControl();
}
bool HTMLElement::IsDisabledFormControl() const {
if (!IsFormAssociatedCustomElement())
return false;
return const_cast<HTMLElement*>(this)
->EnsureElementInternals()
.IsActuallyDisabled();
}
bool HTMLElement::MatchesEnabledPseudoClass() const {
return IsFormAssociatedCustomElement() && !const_cast<HTMLElement*>(this)
->EnsureElementInternals()
.IsActuallyDisabled();
}
bool HTMLElement::MatchesValidityPseudoClasses() const {
return IsFormAssociatedCustomElement();
}
bool HTMLElement::willValidate() const {
return IsFormAssociatedCustomElement() && const_cast<HTMLElement*>(this)
->EnsureElementInternals()
.WillValidate();
}
bool HTMLElement::IsValidElement() {
return IsFormAssociatedCustomElement() &&
EnsureElementInternals().IsValidElement();
}
bool HTMLElement::IsLabelable() const {
return IsFormAssociatedCustomElement();
}
void HTMLElement::FinishParsingChildren() {
Element::FinishParsingChildren();
if (IsFormAssociatedCustomElement())
EnsureElementInternals().TakeStateAndRestore();
}
void HTMLElement::BeginParsingChildren() {
Element::BeginParsingChildren();
if (GetDocument().IsDirAttributeDirty() && !HasDirectionAuto() &&
!ElementAffectsDirectionality(this)) {
bool needs_slot_assignment_recalc = false;
auto* parent =
GetParentForDirectionality(*this, needs_slot_assignment_recalc);
if (needs_slot_assignment_recalc)
SetNeedsInheritDirectionalityFromParent();
else if (parent)
SetCachedDirectionality(parent->CachedDirectionality());
}
}
} // namespace blink
#ifndef NDEBUG
// For use in the debugger
void dumpInnerHTML(blink::HTMLElement*);
void dumpInnerHTML(blink::HTMLElement* element) {
printf("%s\n", element->innerHTML().Ascii().c_str());
}
#endif