| /* |
| * 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/stl_util.h" |
| #include "third_party/blink/renderer/bindings/core/v8/js_event_handler_for_content_attribute.h" |
| #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_script.h" |
| #include "third_party/blink/renderer/bindings/core/v8/string_treat_null_as_empty_string_or_trusted_script.h" |
| #include "third_party/blink/renderer/core/css/css_color_value.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_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_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_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://w3c.github.io/editing/execCommand.html#editing-host |
| bool IsEditingHost(const Node& node) { |
| auto* html_element = DynamicTo<HTMLElement>(node); |
| if (!html_element) |
| return false; |
| String normalized_value = html_element->contentEditable(); |
| if (normalized_value == "true" || normalized_value == "plaintext-only") |
| return true; |
| return node.GetDocument().InDesignMode() && |
| node.GetDocument().documentElement() == &node; |
| } |
| |
| // https://w3c.github.io/editing/execCommand.html#editable |
| bool IsEditable(const Node& node) { |
| if (IsEditingHost(node)) |
| return false; |
| auto* html_element = DynamicTo<HTMLElement>(node); |
| if (html_element && html_element->contentEditable() == "false") |
| return false; |
| if (!node.parentNode()) |
| return false; |
| if (!IsEditingHost(*node.parentNode()) && !IsEditable(*node.parentNode())) |
| return false; |
| if (html_element) |
| return true; |
| if (IsA<SVGSVGElement>(node)) |
| return true; |
| auto* element = DynamicTo<Element>(node); |
| if (element && element->HasTagName(mathml_names::kMathTag)) |
| return true; |
| return !element && node.parentNode()->IsHTMLElement(); |
| } |
| |
| const WebFeature kNoWebFeature = static_cast<WebFeature>(0); |
| |
| HTMLElement* GetParentForDirectionality(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::kWebkitPlaintext; |
| // 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::kWebkitIsolate; |
| } |
| |
| 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) { |
| 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::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}, |
| |
| {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::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::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; |
| } |
| |
| void HTMLElement::setInnerText( |
| const StringOrTrustedScript& string_or_trusted_script, |
| ExceptionState& exception_state) { |
| String value; |
| if (string_or_trusted_script.IsString()) |
| value = string_or_trusted_script.GetAsString(); |
| else if (string_or_trusted_script.IsTrustedScript()) |
| value = string_or_trusted_script.GetAsTrustedScript()->toString(); |
| setInnerText(value, exception_state); |
| } |
| |
| void HTMLElement::setInnerText( |
| const StringTreatNullAsEmptyStringOrTrustedScript& string_or_trusted_script, |
| ExceptionState& exception_state) { |
| StringOrTrustedScript tmp; |
| if (string_or_trusted_script.IsString()) |
| tmp.SetString(string_or_trusted_script.GetAsString()); |
| else if (string_or_trusted_script.IsTrustedScript()) |
| tmp.SetTrustedScript(string_or_trusted_script.GetAsTrustedScript()); |
| setInnerText(tmp, exception_state); |
| } |
| |
| void HTMLElement::innerText( |
| StringTreatNullAsEmptyStringOrTrustedScript& result) { |
| result.SetString(innerText()); |
| } |
| |
| void HTMLElement::innerText(StringOrTrustedScript& result) { |
| result.SetString(innerText()); |
| } |
| |
| 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); |
| |
| // textToFragment might cause mutation events. |
| if (!parentNode()) |
| exception_state.ThrowDOMException(DOMExceptionCode::kHierarchyRequestError, |
| "The element has no parent."); |
| |
| 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, height_dim; |
| if (!ParseDimensionValue(width, width_dim)) |
| return; |
| if (!ParseDimensionValue(height, height_dim)) |
| return; |
| if (!width_dim.IsAbsolute() || !height_dim.IsAbsolute()) |
| return; |
| CSSValue* width_val = CSSNumericLiteralValue::Create( |
| width_dim.Value(), CSSPrimitiveValue::UnitType::kNumber); |
| CSSValue* height_val = CSSNumericLiteralValue::Create( |
| height_dim.Value(), CSSPrimitiveValue::UnitType::kNumber); |
| CSSValueList* ratio_list = CSSValueList::CreateSlashSeparated(); |
| ratio_list->Append(*width_val); |
| ratio_list->Append(*height_val); |
| |
| CSSValueList* list = CSSValueList::CreateSpaceSeparated(); |
| list->Append(*CSSIdentifierValue::Create(CSSValueID::kAuto)); |
| list->Append(*ratio_list); |
| |
| 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")) { |
| 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; |
| } |
| |
| String HTMLElement::contentEditable() const { |
| const AtomicString& value = |
| FastGetAttribute(html_names::kContenteditableAttr); |
| |
| if (value.IsNull()) |
| return "inherit"; |
| if (value.IsEmpty() || EqualIgnoringASCIICase(value, "true")) |
| return "true"; |
| if (EqualIgnoringASCIICase(value, "false")) |
| return "false"; |
| if (EqualIgnoringASCIICase(value, "plaintext-only")) |
| return "plaintext-only"; |
| |
| return "inherit"; |
| } |
| |
| 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 IsEditingHost(*this) || IsEditable(*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 (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"); |
| } |
| |
| 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); |
| } |
| |
| Node* node = FlatTreeTraversal::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 = FlatTreeTraversal::NextSkippingChildren(*node, stay_within); |
| 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. |
| // 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 = FlatTreeTraversal::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; |
| } |
| node = FlatTreeTraversal::Next(*node, stay_within); |
| } |
| return TextDirection::kLtr; |
| } |
| |
| void HTMLElement::AdjustDirectionalityIfNeededAfterChildAttributeChanged( |
| Element* child) { |
| DCHECK(SelfOrAncestorHasDirAutoAttribute()); |
| bool is_deferred; |
| TextDirection text_direction = ResolveAutoDirectionality(is_deferred, this); |
| 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(is_deferred, stay_within); |
| 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::AdjustCandidateDirectionalityForSlot( |
| HeapHashSet<Member<HTMLElement>> 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& element : candidate_set) { |
| if (!element->SelfOrAncestorHasDirAutoAttribute()) { |
| auto* parent = |
| DynamicTo<HTMLElement>(FlatTreeTraversal::ParentElement(*element)); |
| if (parent && !parent->NeedsInheritDirectionalityFromParent() && |
| element->NeedsInheritDirectionalityFromParent()) { |
| element->UpdateDirectionalityAndDescendant( |
| parent->CachedDirectionality()); |
| } |
| continue; |
| } |
| |
| for (auto* element_to_adjust = element.Get(); element_to_adjust; |
| element_to_adjust = DynamicTo<HTMLElement>( |
| FlatTreeTraversal::ParentElement(*element_to_adjust))) { |
| 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)); |
| } |
| } |
| } |
| |
| void HTMLElement::AddCandidateDirectionalityForSlot() { |
| ShadowRoot* root = ShadowRootOfParent(); |
| if (!root || !root->HasSlotAssignment()) |
| return; |
| |
| root->GetSlotAssignment().GetCandidateDirectionality().insert(this); |
| } |
| |
| 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::CSSColorValue::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(); |
| } |
| |
| bool HTMLElement::MatchesReadWritePseudoClass() const { |
| if (FastHasAttribute(html_names::kContenteditableAttr)) { |
| const AtomicString& value = |
| FastGetAttribute(html_names::kContenteditableAttr); |
| |
| if (value.IsEmpty() || EqualIgnoringASCIICase(value, "true") || |
| EqualIgnoringASCIICase(value, "plaintext-only")) |
| return true; |
| if (EqualIgnoringASCIICase(value, "false")) |
| return false; |
| // All other values should be treated as "inherit". |
| } |
| |
| return parentElement() && HasEditableStyle(*parentElement()); |
| } |
| |
| 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 { |
| base::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) { |
| UpdateDistributionForUnknownReasons(); |
| 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()) { |
| if (HasDirectionAuto()) { |
| SetSelfOrAncestorHasDirAutoAttribute(); |
| } else if (!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 |