From 6c9adf3dbc641445a03da9cd1083f89c911504be Mon Sep 17 00:00:00 2001 From: simonkrauter Date: Mon, 19 Aug 2024 17:06:13 -0300 Subject: [PATCH] LibWeb: Change where content selection via mouse is allowed Previously, only DOM nodes with `is_editable()` allowed selection via the mouse. This had the unwanted consequence, that read-only input/textarea elements did not allow selection. Now, `EventHandler::handle_mousedown()` asks the node's non-shadow parent element over the new virtual method `is_child_node_selectable()`, if selection of the node is allowed. This method is overridden for `HTMLButtonElement` and `HTMLInputElement`, to disallow selection of buttons and placeholders. Fixes #579 --- .../Libraries/LibWeb/HTML/HTMLButtonElement.h | 2 ++ Userland/Libraries/LibWeb/HTML/HTMLElement.h | 2 ++ .../Libraries/LibWeb/HTML/HTMLInputElement.cpp | 5 +++++ .../Libraries/LibWeb/HTML/HTMLInputElement.h | 2 ++ Userland/Libraries/LibWeb/Page/EventHandler.cpp | 16 +++++++++++++--- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.h b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.h index 1f7c0f27aeb..c18b443e3a7 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLButtonElement.h @@ -70,6 +70,8 @@ public: virtual bool has_activation_behavior() const override; virtual void activation_behavior(DOM::Event const&) override; + virtual bool is_child_node_selectable(DOM::Node const&) const override { return false; } + private: virtual bool is_html_button_element() const override { return true; } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLElement.h b/Userland/Libraries/LibWeb/HTML/HTMLElement.h index 230c7d85ede..f48c90c571e 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLElement.h @@ -80,6 +80,8 @@ public: WebIDL::ExceptionOr set_popover(Optional value); Optional popover() const; + virtual bool is_child_node_selectable(DOM::Node const&) const { return true; } + protected: HTMLElement(DOM::Document&, DOM::QualifiedName); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp index 23e5a3143b0..62762cce04c 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -2368,4 +2368,9 @@ HTMLInputElement::ValueAttributeMode HTMLInputElement::value_attribute_mode() co VERIFY_NOT_REACHED(); } +bool HTMLInputElement::is_child_node_selectable(DOM::Node const& node) const +{ + return !is_button() && (!m_placeholder_element || !m_placeholder_element->is_inclusive_ancestor_of(node)); +} + } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h index 28a1569a50b..88978e1a80b 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -204,6 +204,8 @@ public: WebIDL::ExceptionOr set_selection_end_for_bindings(Optional const&); Optional selection_end_for_bindings() const; + virtual bool is_child_node_selectable(DOM::Node const&) const override; + private: HTMLInputElement(DOM::Document&, DOM::QualifiedName); diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.cpp b/Userland/Libraries/LibWeb/Page/EventHandler.cpp index d29cbf9372d..c5746e876fa 100644 --- a/Userland/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Userland/Libraries/LibWeb/Page/EventHandler.cpp @@ -449,9 +449,19 @@ bool EventHandler::handle_mousedown(CSSPixelPoint viewport_position, CSSPixelPoi 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()) { + // Ask the next non-shadow parent element whether the node at the mouse position is selectable. + auto& root_node = dom_node->root(); + DOM::Element* non_shadow_parent_element; + if (root_node.is_shadow_root()) + non_shadow_parent_element = root_node.parent_or_shadow_host_element(); + else + non_shadow_parent_element = dom_node->parent_element(); + bool is_selectable = true; + if (non_shadow_parent_element && non_shadow_parent_element->is_html_element()) + is_selectable = static_cast(non_shadow_parent_element)->is_child_node_selectable(*dom_node); + + // If it is selectable, place the document text cursor at the mouse position. + if (is_selectable) { auto& realm = document->realm(); document->set_cursor_position(DOM::Position::create(realm, *dom_node, result->index_in_node)); if (auto selection = document->get_selection()) {