diff --git a/Libraries/LibWeb/Selection/Selection.cpp b/Libraries/LibWeb/Selection/Selection.cpp index 86078c0447e..3a4024a67b1 100644 --- a/Libraries/LibWeb/Selection/Selection.cpp +++ b/Libraries/LibWeb/Selection/Selection.cpp @@ -399,6 +399,70 @@ WebIDL::ExceptionOr Selection::select_all_children(GC::Ref node return {}; } +// https://w3c.github.io/selection-api/#dom-selection-modify +WebIDL::ExceptionOr Selection::modify(Optional alter, Optional direction, Optional granularity) +{ + auto anchor_node = this->anchor_node(); + if (!anchor_node || !is(*anchor_node)) + return {}; + + auto& text_node = static_cast(*anchor_node); + + // 1. If alter is not ASCII case-insensitive match with "extend" or "move", abort these steps. + if (!alter.has_value() || !alter.value().bytes_as_string_view().is_one_of_ignoring_ascii_case("extend"sv, "move"sv)) + return {}; + + // 2. If direction is not ASCII case-insensitive match with "forward", "backward", "left", or "right", abort these steps. + if (!direction.has_value() || !direction.value().bytes_as_string_view().is_one_of_ignoring_ascii_case("forward"sv, "backward"sv, "left"sv, "right"sv)) + return {}; + + // 3. If granularity is not ASCII case-insensitive match with "character", "word", "sentence", "line", "paragraph", + // "lineboundary", "sentenceboundary", "paragraphboundary", "documentboundary", abort these steps. + if (!granularity.has_value() || !granularity.value().bytes_as_string_view().is_one_of_ignoring_ascii_case("character"sv, "word"sv, "sentence"sv, "line"sv, "paragraph"sv, "lineboundary"sv, "sentenceboundary"sv, "paragraphboundary"sv, "documentboundary"sv)) + return {}; + + // 4. If this selection is empty, abort these steps. + if (is_empty()) + return {}; + + // 5. Let effectiveDirection be backwards. + auto effective_direction = Direction::Backwards; + + // 6. If direction is ASCII case-insensitive match with "forward", set effectiveDirection to forwards. + if (direction.value().equals_ignoring_ascii_case("forward"sv)) + effective_direction = Direction::Forwards; + + // 7. If direction is ASCII case-insensitive match with "right" and inline base direction of this selection's focus is ltr, set effectiveDirection to forwards. + if (direction.value().equals_ignoring_ascii_case("right"sv) && text_node.directionality() == DOM::Element::Directionality::Ltr) + effective_direction = Direction::Forwards; + + // 8. If direction is ASCII case-insensitive match with "left" and inline base direction of this selection's focus is rtl, set effectiveDirection to forwards. + if (direction.value().equals_ignoring_ascii_case("left"sv) && text_node.directionality() == DOM::Element::Directionality::Rtl) + effective_direction = Direction::Forwards; + + // 9. Set this selection's direction to effectiveDirection. + // NOTE: This is handled by calls to move_offset_to_* later on + + // 10. If alter is ASCII case-insensitive match with "extend", set this selection's focus to the location as if the user had requested to extend selection by granularity. + // 11. Otherwise, set this selection's focus and anchor to the location as if the user had requested to move selection by granularity. + auto collapse_selection = alter.value().equals_ignoring_ascii_case("move"sv); + + // TODO: Implement the other granularity options. + if (effective_direction == Direction::Forwards) { + if (granularity.value().equals_ignoring_ascii_case("character"sv)) + move_offset_to_next_character(collapse_selection); + if (granularity.value().equals_ignoring_ascii_case("word"sv)) + move_offset_to_next_word(collapse_selection); + } else { + if (granularity.value().equals_ignoring_ascii_case("character"sv)) + move_offset_to_previous_character(collapse_selection); + if (granularity.value().equals_ignoring_ascii_case("word"sv)) + move_offset_to_previous_word(collapse_selection); + } + + return {}; +} + // https://w3c.github.io/selection-api/#dom-selection-deletefromdocument WebIDL::ExceptionOr Selection::delete_from_document() { diff --git a/Libraries/LibWeb/Selection/Selection.h b/Libraries/LibWeb/Selection/Selection.h index dfeab275ff6..bd95acecf62 100644 --- a/Libraries/LibWeb/Selection/Selection.h +++ b/Libraries/LibWeb/Selection/Selection.h @@ -46,6 +46,7 @@ public: WebIDL::ExceptionOr extend(GC::Ref, unsigned offset); WebIDL::ExceptionOr set_base_and_extent(GC::Ref anchor_node, unsigned anchor_offset, GC::Ref focus_node, unsigned focus_offset); WebIDL::ExceptionOr select_all_children(GC::Ref); + WebIDL::ExceptionOr modify(Optional alter, Optional direction, Optional granularity); WebIDL::ExceptionOr delete_from_document(); bool contains_node(GC::Ref, bool allow_partial_containment) const; diff --git a/Libraries/LibWeb/Selection/Selection.idl b/Libraries/LibWeb/Selection/Selection.idl index 4f9c93e74dc..3c986340071 100644 --- a/Libraries/LibWeb/Selection/Selection.idl +++ b/Libraries/LibWeb/Selection/Selection.idl @@ -25,6 +25,7 @@ interface Selection { undefined extend(Node node, optional unsigned long offset = 0); undefined setBaseAndExtent(Node anchorNode, unsigned long anchorOffset, Node focusNode, unsigned long focusOffset); undefined selectAllChildren(Node node); + undefined modify(optional DOMString alter, optional DOMString direction, optional DOMString granularity); [CEReactions] undefined deleteFromDocument(); boolean containsNode(Node node, optional boolean allowPartialContainment = false); stringifier; diff --git a/Tests/LibWeb/Text/expected/selection-modify.txt b/Tests/LibWeb/Text/expected/selection-modify.txt new file mode 100644 index 00000000000..7ef22e9a431 --- /dev/null +++ b/Tests/LibWeb/Text/expected/selection-modify.txt @@ -0,0 +1 @@ +PASS diff --git a/Tests/LibWeb/Text/input/selection-modify.html b/Tests/LibWeb/Text/input/selection-modify.html new file mode 100644 index 00000000000..f472cf822e0 --- /dev/null +++ b/Tests/LibWeb/Text/input/selection-modify.html @@ -0,0 +1,39 @@ + + +

Well Hello Friends

+ +