diff --git a/Tests/LibWeb/Text/expected/css/dir-pseudo-on-input-element.txt b/Tests/LibWeb/Text/expected/css/dir-pseudo-on-input-element.txt new file mode 100644 index 00000000000..b7b6c5025a5 --- /dev/null +++ b/Tests/LibWeb/Text/expected/css/dir-pseudo-on-input-element.txt @@ -0,0 +1,14 @@ +Input matches :dir(ltr): true +Input matches :dir(rtl): false +Input matches :dir(ltr): true +Input matches :dir(rtl): false +Input matches :dir(ltr): false +Input matches :dir(rtl): true +Input matches :dir(ltr): true +Input matches :dir(rtl): false +Input matches :dir(ltr): false +Input matches :dir(rtl): true +Input matches :dir(ltr): true +Input matches :dir(rtl): false +Input matches :dir(ltr): true +Input matches :dir(rtl): false diff --git a/Tests/LibWeb/Text/input/css/dir-pseudo-on-input-element.html b/Tests/LibWeb/Text/input/css/dir-pseudo-on-input-element.html new file mode 100644 index 00000000000..1c628038a5b --- /dev/null +++ b/Tests/LibWeb/Text/input/css/dir-pseudo-on-input-element.html @@ -0,0 +1,44 @@ + + + + diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index 038e379ec61..f50bb78476d 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -2200,9 +2201,65 @@ IntersectionObserver::IntersectionObserverRegistration& Element::get_intersectio // 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: + // The directionality of an element (any element, not just an HTML element) is either 'ltr' or 'rtl'. + // To compute the directionality given an element element, switch on element's dir attribute state: + auto maybe_dir = this->dir(); + if (maybe_dir.has_value()) { + auto dir = maybe_dir.release_value(); + switch (dir) { + // -> ltr + case Dir::Ltr: + // Return 'ltr'. + return Directionality::Ltr; + // -> rtl + case Dir::Rtl: + // Return 'rtl'. + return Directionality::Rtl; + // -> auto + case Dir::Auto: + // 1. Let result be the auto directionality of element. + auto result = auto_directionality(); + // 2. If result is null, then return 'ltr'. + if (!result.has_value()) + return Directionality::Ltr; + + // 3. Return result. + return result.release_value(); + } + } + // -> undefined + VERIFY(!maybe_dir.has_value()); + + // FIXME: If element is a bdi element: + // FIXME: 1. Let result be the auto directionality of element. + // FIXME: 2. If result is null, then return 'ltr'. + // FIXME: 3. Return result. + + // If element is an input element whose type attribute is in the Telephone state: + if (is(this) && static_cast(*this).type_state() == HTML::HTMLInputElement::TypeAttributeState::Telephone) { + // Return 'ltr'. + return Directionality::Ltr; + } + + // Otherwise: + // Return the parent directionality of element. + return parent_directionality(); +} + +// https://html.spec.whatwg.org/multipage/dom.html#auto-directionality-form-associated-elements +bool Element::is_auto_directionality_form_associated_element() const +{ + // The auto-directionality form-associated elements are: + // input elements whose type attribute is in the Hidden, Text, Search, Telephone, URL, Email, Password, Submit Button, Reset Button, or Button state, + // and textarea elements. + return is(this) + || (is(this) && first_is_one_of(static_cast(*this).type_state(), HTML::HTMLInputElement::TypeAttributeState::Hidden, HTML::HTMLInputElement::TypeAttributeState::Text, HTML::HTMLInputElement::TypeAttributeState::Search, HTML::HTMLInputElement::TypeAttributeState::Telephone, HTML::HTMLInputElement::TypeAttributeState::URL, HTML::HTMLInputElement::TypeAttributeState::Email, HTML::HTMLInputElement::TypeAttributeState::Password, HTML::HTMLInputElement::TypeAttributeState::SubmitButton, HTML::HTMLInputElement::TypeAttributeState::ResetButton, HTML::HTMLInputElement::TypeAttributeState::Button)); +} + +// https://html.spec.whatwg.org/multipage/dom.html#auto-directionality +Optional Element::auto_directionality() const +{ static auto bidirectional_class_L = Unicode::bidirectional_class_from_string("L"sv); static auto bidirectional_class_AL = Unicode::bidirectional_class_from_string("AL"sv); static auto bidirectional_class_R = Unicode::bidirectional_class_from_string("R"sv); @@ -2211,46 +2268,37 @@ Element::Directionality Element::directionality() const if (!bidirectional_class_L.has_value()) return Directionality::Ltr; - auto dir = this->dir(); + // https://html.spec.whatwg.org/multipage/dom.html#text-node-directionality + auto text_node_directionality = [](Text const& text_node) -> Optional { + // 1. If text's data does not contain a code point whose bidirectional character type is L, AL, or R, then return null. + // 2. Let codePoint be the first code point in text's data whose bidirectional character type is L, AL, or R. + Optional found_character_bidi_class; + for (auto code_point : Utf8View(text_node.data())) { + auto bidi_class = Unicode::bidirectional_class(code_point); + if (first_is_one_of(bidi_class, bidirectional_class_L, bidirectional_class_AL, bidirectional_class_R)) { + found_character_bidi_class = bidi_class; + break; + } + } + if (!found_character_bidi_class.has_value()) + return {}; - // -> 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'. + // 3. If codePoint is of bidirectional character type AL or R, then return 'rtl'. + if (first_is_one_of(*found_character_bidi_class, bidirectional_class_AL, bidirectional_class_R)) + return Directionality::Rtl; + + // 4. If codePoint is of bidirectional character type L, then return 'ltr'. + // NOTE: codePoint should always be of bidirectional character type L by this point, so we can just return 'ltr' here. + VERIFY(*found_character_bidi_class == bidirectional_class_L); 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; - } + // 1. If element is an auto-directionality form-associated element: + if (is_auto_directionality_form_associated_element()) { + auto const& value = static_cast(*this).value(); - // -> 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] + // 1. If 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 return 'rtl'. for (auto code_point : Utf8View(value)) { auto bidi_class = Unicode::bidirectional_class(code_point); if (bidi_class == bidirectional_class_L) @@ -2259,87 +2307,114 @@ Element::Directionality Element::directionality() const 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()) { + // 2. If element's value is not the empty string, then return 'ltr'. + if (value.is_empty()) 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(); + + // 3. Return null. + return {}; + } + + // 2. If element is a slot element whose root is a shadow root and element's assigned nodes are not empty: + if (is(this)) { + auto const& slot = static_cast(*this); + if (slot.root().is_shadow_root() && !slot.assigned_nodes().is_empty()) { + // 1 . For each node child of element's assigned nodes: + for (auto const& child : slot.assigned_nodes()) { + // 1. Let childDirection be null. + Optional child_direction; + + // 2. If child is a Text node, then set childDirection to the text node directionality of child. + if (child->is_text()) + child_direction = text_node_directionality(static_cast(*child)); + + // 3. Otherwise: + else { + // 1. Assert: child is an Element node. + VERIFY(child->is_element()); + + // 2. Set childDirection to the auto directionality of child. + child_direction = static_cast(*this).auto_directionality(); + } + + // 4. If childDirection is not null, then return childDirection. + if (child_direction.has_value()) + return child_direction; + } + + // 2. Return null. + return {}; } } - // -> 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 TraversalDecision::Continue; - if (ancestor->is_element()) { - auto ancestor_element = static_cast(ancestor); - if (ancestor_element->dir().has_value()) - return TraversalDecision::Continue; - } - } + // 3. For each node descendant of element's descendants, in tree order: + Optional result; + for_each_in_subtree([&](auto& descendant) { + // 1. If descendant, or any of its ancestor elements that are descendants of element, is one of + // - FIXME: a bdi element + // - a script element + // - a style element + // - a textarea element + // - an element whose dir attribute is not in the undefined state + // then continue. + if (is(descendant) + || is(descendant) + || is(descendant) + || (is(descendant) && static_cast(descendant).dir().has_value())) { + return TraversalDecision::SkipChildrenAndContinue; + } - // 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, bidirectional_class_L, bidirectional_class_AL, bidirectional_class_R)) { - found_character = code_point; - found_character_bidi_class = bidi_class; - return TraversalDecision::Break; - } + // 2. If descendant is a slot element whose root is a shadow root, then return the directionality of that shadow root's host. + if (is(descendant)) { + auto const& root = static_cast(descendant).root(); + if (root.is_shadow_root()) { + auto const& host = static_cast(root).host(); + VERIFY(host); + result = host->directionality(); + return TraversalDecision::Break; } + } + // 3. If descendant is not a Text node, then continue. + if (!descendant.is_text()) return TraversalDecision::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(), bidirectional_class_AL, bidirectional_class_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() == bidirectional_class_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(); - } + // 4. Let result be the text node directionality of descendant. + result = text_node_directionality(static_cast(descendant)); + + // 5. If result is not null, then return result. + if (result.has_value()) + return TraversalDecision::Break; + + return TraversalDecision::Continue; + }); + + if (result.has_value()) + return result; + + // 4. Return null. + return {}; +} + +// https://html.spec.whatwg.org/multipage/dom.html#parent-directionality +Element::Directionality Element::parent_directionality() const +{ + // 1. Let parentNode be element's parent node. + auto const* parent_node = this->parent_node(); + + // 2. If parentNode is a shadow root, then return the directionality of parentNode's host. + if (is(parent_node)) { + auto const& host = static_cast(*parent_node).host(); + VERIFY(host); + return host->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(); - } + // 3. If parentNode is an element, then return the directionality of parentNode. + if (is(parent_node)) + return static_cast(*parent_node).directionality(); - VERIFY_NOT_REACHED(); + // 4. Return 'ltr'. + return Directionality::Ltr; } // https://dom.spec.whatwg.org/#ref-for-concept-element-attributes-change-ext① diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index 031dee015ca..141f8172bf1 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -400,6 +400,10 @@ private: void enqueue_an_element_on_the_appropriate_element_queue(); + Optional auto_directionality() const; + Directionality parent_directionality() const; + bool is_auto_directionality_form_associated_element() const; + QualifiedName m_qualified_name; FlyString m_html_uppercased_qualified_name;