diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index aa26166e039..05b085561ff 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2018-2023, Andreas Kling + * Copyright (c) 2022-2023, San Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -42,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -391,6 +395,16 @@ void Element::attribute_changed(DeprecatedFlyString const& name, DeprecatedStrin m_inline_style = parse_css_style_attribute(CSS::Parser::ParsingContext(document()), value, *this); set_needs_style_update(true); } + } else if (name == HTML::AttributeNames::dir) { + // https://html.spec.whatwg.org/multipage/dom.html#attr-dir + if (value.equals_ignoring_ascii_case("ltr"sv)) + m_dir = Dir::Ltr; + else if (value.equals_ignoring_ascii_case("rtl"sv)) + m_dir = Dir::Rtl; + else if (value.equals_ignoring_ascii_case("auto"sv)) + m_dir = Dir::Auto; + else + m_dir = {}; } } @@ -1833,4 +1847,140 @@ IntersectionObserver::IntersectionObserverRegistration& Element::get_intersectio return *registration_iterator; } +// https://html.spec.whatwg.org/multipage/dom.html#the-directionality +Element::Directionality Element::directionality() const +{ + // The directionality of an element (any element, not just an HTML element) is either 'ltr' or 'rtl', + // and is determined as per the first appropriate set of steps from the following list: + + auto dir = this->dir(); + + // -> If the element's dir attribute is in the ltr state + // -> If the element is a document element and the dir attribute is not in a defined state + // (i.e. it is not present or has an invalid value) + // -> If the element is an input element whose type attribute is in the Telephone state, and the + // dir attribute is not in a defined state (i.e. it is not present or has an invalid value) + if (dir == Dir::Ltr + || (is_document_element() && !dir.has_value()) + || (is(this) + && static_cast(*this).type_state() == HTML::HTMLInputElement::TypeAttributeState::Telephone + && !dir.has_value())) { + // The directionality of the element is 'ltr'. + return Directionality::Ltr; + } + + // -> If the element's dir attribute is in the rtl state + if (dir == Dir::Rtl) { + // The directionality of the element is 'rtl'. + return Directionality::Rtl; + } + + // -> If the element is an input element whose type attribute is in the Text, Search, Telephone, + // URL, or Email state, and the dir attribute is in the auto state + // -> If the element is a textarea element and the dir attribute is in the auto state + if ((is(this) + && first_is_one_of(static_cast(*this).type_state(), + HTML::HTMLInputElement::TypeAttributeState::Text, HTML::HTMLInputElement::TypeAttributeState::Search, + HTML::HTMLInputElement::TypeAttributeState::Telephone, HTML::HTMLInputElement::TypeAttributeState::URL, + HTML::HTMLInputElement::TypeAttributeState::Email) + && dir == Dir::Auto) + || (is(this) && dir == Dir::Auto)) { + + auto value = is(this) + ? static_cast(*this).value() + : static_cast(*this).value(); + + // If the element's value contains a character of bidirectional character type AL or R, and + // there is no character of bidirectional character type L anywhere before it in the element's + // value, then the directionality of the element is 'rtl'. [BIDI] + for (auto code_point : Utf8View(value)) { + auto bidi_class = Unicode::bidirectional_class(code_point); + if (bidi_class == Unicode::BidirectionalClass::L) + break; + if (bidi_class == Unicode::BidirectionalClass::AL || bidi_class == Unicode::BidirectionalClass::R) + return Directionality::Rtl; + } + // Otherwise, if the element's value is not the empty string, or if the element is a document element, + // the directionality of the element is 'ltr'. + if (!value.is_empty() || is_document_element()) { + return Directionality::Ltr; + } + // Otherwise, the directionality of the element is the same as the element's parent element's directionality. + else { + return parent_element()->directionality(); + } + } + + // -> If the element's dir attribute is in the auto state + // FIXME: -> If the element is a bdi element and the dir attribute is not in a defined state + // (i.e. it is not present or has an invalid value) + if (dir == Dir::Auto) { + // Find the first character in tree order that matches the following criteria: + // - The character is from a Text node that is a descendant of the element whose directionality is being determined. + // - The character is of bidirectional character type L, AL, or R. [BIDI] + // - The character is not in a Text node that has an ancestor element that is a descendant of + // the element whose directionality is being determined and that is either: + // - FIXME: A bdi element. + // - A script element. + // - A style element. + // - A textarea element. + // - An element with a dir attribute in a defined state. + Optional found_character; + Optional found_character_bidi_class; + for_each_in_subtree_of_type([&](Text const& text_node) { + // Discard not-allowed ancestors + for (auto* ancestor = text_node.parent(); ancestor && ancestor != this; ancestor = ancestor->parent()) { + if (is(*ancestor) || is(*ancestor) || is(*ancestor)) + return IterationDecision::Continue; + if (ancestor->is_element()) { + auto ancestor_element = static_cast(ancestor); + if (ancestor_element->dir().has_value()) + return IterationDecision::Continue; + } + } + + // Look for matching characters + for (auto code_point : Utf8View(text_node.data())) { + auto bidi_class = Unicode::bidirectional_class(code_point); + if (first_is_one_of(bidi_class, Unicode::BidirectionalClass::L, Unicode::BidirectionalClass::AL, Unicode::BidirectionalClass::R)) { + found_character = code_point; + found_character_bidi_class = bidi_class; + return IterationDecision::Break; + } + } + + return IterationDecision::Continue; + }); + + // If such a character is found and it is of bidirectional character type AL or R, + // the directionality of the element is 'rtl'. + if (found_character.has_value() + && first_is_one_of(found_character_bidi_class.value(), Unicode::BidirectionalClass::AL, Unicode::BidirectionalClass::R)) { + return Directionality::Rtl; + } + // If such a character is found and it is of bidirectional character type L, + // the directionality of the element is 'ltr'. + if (found_character.has_value() && found_character_bidi_class.value() == Unicode::BidirectionalClass::L) { + return Directionality::Ltr; + } + // Otherwise, if the element is a document element, the directionality of the element is 'ltr'. + else if (is_document_element()) { + return Directionality::Ltr; + } + // Otherwise, the directionality of the element is the same as the element's parent element's directionality. + else { + return parent_element()->directionality(); + } + } + + // If the element has a parent element and the dir attribute is not in a defined state + // (i.e. it is not present or has an invalid value) + if (parent_element() && !dir.has_value()) { + // The directionality of the element is the same as the element's parent element's directionality. + return parent_element()->directionality(); + } + + VERIFY_NOT_REACHED(); +} + } diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index f25897fcd7e..221bf125926 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -325,6 +325,19 @@ public: CSSPixelPoint scroll_offset(ScrollOffsetFor type) const { return m_scroll_offset[to_underlying(type)]; } void set_scroll_offset(ScrollOffsetFor type, CSSPixelPoint offset) { m_scroll_offset[to_underlying(type)] = offset; } + enum class Dir { + Ltr, + Rtl, + Auto, + }; + Optional dir() const { return m_dir; } + + enum class Directionality { + Ltr, + Rtl, + }; + Directionality directionality() const; + protected: Element(Document&, DOM::QualifiedName); virtual void initialize(JS::Realm&) override; @@ -358,6 +371,7 @@ private: Array, to_underlying(CSS::Selector::PseudoElement::PseudoElementCount)> m_pseudo_element_custom_properties; Vector m_classes; + Optional m_dir; Array, to_underlying(CSS::Selector::PseudoElement::PseudoElementCount)> m_pseudo_element_nodes;