Explorar o código

LibWeb: Implement "directionality" of an Element

Sam Atkins hai 1 ano
pai
achega
9522f761a3

+ 150 - 0
Userland/Libraries/LibWeb/DOM/Element.cpp

@@ -1,5 +1,6 @@
 /*
 /*
  * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
  * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2022-2023, San Atkins <atkinssj@serenityos.org>
  *
  *
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
@@ -7,6 +8,8 @@
 #include <AK/AnyOf.h>
 #include <AK/AnyOf.h>
 #include <AK/Debug.h>
 #include <AK/Debug.h>
 #include <AK/StringBuilder.h>
 #include <AK/StringBuilder.h>
+#include <LibUnicode/CharacterTypes.h>
+#include <LibUnicode/UnicodeData.h>
 #include <LibWeb/Bindings/ElementPrototype.h>
 #include <LibWeb/Bindings/ElementPrototype.h>
 #include <LibWeb/Bindings/ExceptionOrUtils.h>
 #include <LibWeb/Bindings/ExceptionOrUtils.h>
 #include <LibWeb/Bindings/MainThreadVM.h>
 #include <LibWeb/Bindings/MainThreadVM.h>
@@ -42,6 +45,7 @@
 #include <LibWeb/HTML/HTMLOptGroupElement.h>
 #include <LibWeb/HTML/HTMLOptGroupElement.h>
 #include <LibWeb/HTML/HTMLOptionElement.h>
 #include <LibWeb/HTML/HTMLOptionElement.h>
 #include <LibWeb/HTML/HTMLSelectElement.h>
 #include <LibWeb/HTML/HTMLSelectElement.h>
+#include <LibWeb/HTML/HTMLStyleElement.h>
 #include <LibWeb/HTML/HTMLTextAreaElement.h>
 #include <LibWeb/HTML/HTMLTextAreaElement.h>
 #include <LibWeb/HTML/Parser/HTMLParser.h>
 #include <LibWeb/HTML/Parser/HTMLParser.h>
 #include <LibWeb/HTML/Window.h>
 #include <LibWeb/HTML/Window.h>
@@ -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);
             m_inline_style = parse_css_style_attribute(CSS::Parser::ParsingContext(document()), value, *this);
             set_needs_style_update(true);
             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;
     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<HTML::HTMLInputElement>(this)
+            && static_cast<HTML::HTMLInputElement const&>(*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<HTML::HTMLInputElement>(this)
+            && first_is_one_of(static_cast<HTML::HTMLInputElement const&>(*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<HTML::HTMLTextAreaElement>(this) && dir == Dir::Auto)) {
+
+        auto value = is<HTML::HTMLInputElement>(this)
+            ? static_cast<HTML::HTMLInputElement const&>(*this).value()
+            : static_cast<HTML::HTMLTextAreaElement const&>(*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<u32> found_character;
+        Optional<Unicode::BidirectionalClass> found_character_bidi_class;
+        for_each_in_subtree_of_type<Text>([&](Text const& text_node) {
+            // Discard not-allowed ancestors
+            for (auto* ancestor = text_node.parent(); ancestor && ancestor != this; ancestor = ancestor->parent()) {
+                if (is<HTML::HTMLScriptElement>(*ancestor) || is<HTML::HTMLStyleElement>(*ancestor) || is<HTML::HTMLTextAreaElement>(*ancestor))
+                    return IterationDecision::Continue;
+                if (ancestor->is_element()) {
+                    auto ancestor_element = static_cast<Element const*>(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();
+}
+
 }
 }

+ 14 - 0
Userland/Libraries/LibWeb/DOM/Element.h

@@ -325,6 +325,19 @@ public:
     CSSPixelPoint scroll_offset(ScrollOffsetFor type) const { return m_scroll_offset[to_underlying(type)]; }
     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; }
     void set_scroll_offset(ScrollOffsetFor type, CSSPixelPoint offset) { m_scroll_offset[to_underlying(type)] = offset; }
 
 
+    enum class Dir {
+        Ltr,
+        Rtl,
+        Auto,
+    };
+    Optional<Dir> dir() const { return m_dir; }
+
+    enum class Directionality {
+        Ltr,
+        Rtl,
+    };
+    Directionality directionality() const;
+
 protected:
 protected:
     Element(Document&, DOM::QualifiedName);
     Element(Document&, DOM::QualifiedName);
     virtual void initialize(JS::Realm&) override;
     virtual void initialize(JS::Realm&) override;
@@ -358,6 +371,7 @@ private:
     Array<HashMap<DeprecatedFlyString, CSS::StyleProperty>, to_underlying(CSS::Selector::PseudoElement::PseudoElementCount)> m_pseudo_element_custom_properties;
     Array<HashMap<DeprecatedFlyString, CSS::StyleProperty>, to_underlying(CSS::Selector::PseudoElement::PseudoElementCount)> m_pseudo_element_custom_properties;
 
 
     Vector<FlyString> m_classes;
     Vector<FlyString> m_classes;
+    Optional<Dir> m_dir;
 
 
     Array<JS::GCPtr<Layout::Node>, to_underlying(CSS::Selector::PseudoElement::PseudoElementCount)> m_pseudo_element_nodes;
     Array<JS::GCPtr<Layout::Node>, to_underlying(CSS::Selector::PseudoElement::PseudoElementCount)> m_pseudo_element_nodes;