diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.cpp b/Userland/Libraries/LibWeb/Page/EventHandler.cpp index d29cbf9372d..92d2a920ea2 100644 --- a/Userland/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Userland/Libraries/LibWeb/Page/EventHandler.cpp @@ -10,12 +10,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -354,8 +356,10 @@ bool EventHandler::handle_mouseup(CSSPixelPoint viewport_position, CSSPixelPoint } after_node_use: - if (button == UIEvents::MouseButton::Primary) + if (button == UIEvents::MouseButton::Primary) { m_in_mouse_selection = false; + update_selection_range_for_input_or_textarea(); + } return handled_event; } @@ -433,25 +437,17 @@ bool EventHandler::handle_mousedown(CSSPixelPoint viewport_position, CSSPixelPoi auto dom_node = paintable->dom_node(); if (dom_node) { // See if we want to focus something. - bool did_focus_something = false; + JS::GCPtr focus_candidate; for (auto candidate = node; candidate; candidate = candidate->parent_or_shadow_host()) { if (candidate->is_focusable()) { - // When a user activates a click focusable focusable area, the user agent must run the focusing steps on the focusable area with focus trigger set to "click". - // Spec Note: Note that focusing is not an activation behavior, i.e. calling the click() method on an element or dispatching a synthetic click event on it won't cause the element to get focused. - HTML::run_focusing_steps(candidate.ptr(), nullptr, "click"sv); - did_focus_something = true; + focus_candidate = candidate; break; } } - if (!did_focus_something) { - if (auto* focused_element = document->focused_element()) - HTML::run_unfocusing_steps(focused_element); - } - // If we didn't focus anything, place the document text cursor at the mouse position. // FIXME: This is all rather strange. Find a better solution. - if (!did_focus_something || dom_node->is_editable()) { + if (!focus_candidate || dom_node->is_editable()) { auto& realm = document->realm(); document->set_cursor_position(DOM::Position::create(realm, *dom_node, result->index_in_node)); if (auto selection = document->get_selection()) { @@ -462,8 +458,16 @@ bool EventHandler::handle_mousedown(CSSPixelPoint viewport_position, CSSPixelPoi (void)selection->set_base_and_extent(*dom_node, result->index_in_node, *dom_node, result->index_in_node); } } + update_selection_range_for_input_or_textarea(); m_in_mouse_selection = true; } + + // When a user activates a click focusable focusable area, the user agent must run the focusing steps on the focusable area with focus trigger set to "click". + // Spec Note: Note that focusing is not an activation behavior, i.e. calling the click() method on an element or dispatching a synthetic click event on it won't cause the element to get focused. + if (focus_candidate) + HTML::run_focusing_steps(focus_candidate, nullptr, "click"sv); + else if (auto* focused_element = document->focused_element()) + HTML::run_unfocusing_steps(focused_element); } } } @@ -706,6 +710,7 @@ bool EventHandler::handle_doubleclick(CSSPixelPoint viewport_position, CSSPixelP if (auto selection = node->document().get_selection()) { (void)selection->set_base_and_extent(hit_dom_node, first_word_break_before, hit_dom_node, first_word_break_after); } + update_selection_range_for_input_or_textarea(); } } @@ -978,6 +983,8 @@ bool EventHandler::handle_keydown(UIEvents::KeyCode key, u32 modifiers, u32 code } } + update_selection_range_for_input_or_textarea(); + // FIXME: Implement scroll by line and by page instead of approximating the behavior of other browsers. auto arrow_key_scroll_distance = 100; auto page_scroll_distance = document->window()->inner_height() - (document->window()->outer_height() - document->window()->inner_height()); @@ -1113,4 +1120,53 @@ void EventHandler::visit_edges(JS::Cell::Visitor& visitor) const visitor.visit(m_mouse_event_tracking_paintable); } +// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:set-the-selection-range +void EventHandler::update_selection_range_for_input_or_textarea() +{ + // Where possible, user interface features for changing the text selection in input and + // textarea elements must be implemented using the set the selection range algorithm so that, + // e.g., all the same events fire. + + // NOTE: It seems like only new selections are registered with the respective elements. I.e. + // existing selections in other elements are not cleared, so we only need to set the + // selection range for the element with the current selection. + + // Get the active selection + auto active_document = m_navigable->active_document(); + if (!active_document) + return; + auto selection = active_document->get_selection(); + if (!selection) + return; + + // Do we have a range within the same node? + auto range = selection->range(); + if (!range || range->start_container() != range->end_container()) + return; + + // We are only interested in text nodes with a shadow root + auto& node = *range->start_container(); + if (!node.is_text()) + return; + auto& root = node.root(); + if (!root.is_shadow_root()) + return; + auto& shadow_host = *root.parent_or_shadow_host(); + + // Invoke "set the selection range" on the form associated element + auto selection_start = range->start_offset(); + auto selection_end = range->end_offset(); + // FIXME: support selection directions other than ::Forward + auto direction = HTML::SelectionDirection::Forward; + + Optional target {}; + if (is(shadow_host)) + target = static_cast(shadow_host); + else if (is(shadow_host)) + target = static_cast(shadow_host); + + if (target.has_value()) + target.value().set_the_selection_range(selection_start, selection_end, direction); +} + } diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.h b/Userland/Libraries/LibWeb/Page/EventHandler.h index c76e95fa1c8..d6c5392c186 100644 --- a/Userland/Libraries/LibWeb/Page/EventHandler.h +++ b/Userland/Libraries/LibWeb/Page/EventHandler.h @@ -60,6 +60,7 @@ private: Painting::PaintableBox const* paint_root() const; bool should_ignore_device_input_event() const; + void update_selection_range_for_input_or_textarea(); JS::NonnullGCPtr m_navigable;