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
This commit is contained in:
simonkrauter 2024-08-19 17:06:13 -03:00 committed by Sam Atkins
commit 6c9adf3dbc
Notes: github-actions[bot] 2024-08-23 08:31:56 +00:00
5 changed files with 24 additions and 3 deletions

View file

@ -70,6 +70,8 @@ public:
virtual bool has_activation_behavior() const override; virtual bool has_activation_behavior() const override;
virtual void activation_behavior(DOM::Event const&) override; virtual void activation_behavior(DOM::Event const&) override;
virtual bool is_child_node_selectable(DOM::Node const&) const override { return false; }
private: private:
virtual bool is_html_button_element() const override { return true; } virtual bool is_html_button_element() const override { return true; }

View file

@ -80,6 +80,8 @@ public:
WebIDL::ExceptionOr<void> set_popover(Optional<String> value); WebIDL::ExceptionOr<void> set_popover(Optional<String> value);
Optional<String> popover() const; Optional<String> popover() const;
virtual bool is_child_node_selectable(DOM::Node const&) const { return true; }
protected: protected:
HTMLElement(DOM::Document&, DOM::QualifiedName); HTMLElement(DOM::Document&, DOM::QualifiedName);

View file

@ -2368,4 +2368,9 @@ HTMLInputElement::ValueAttributeMode HTMLInputElement::value_attribute_mode() co
VERIFY_NOT_REACHED(); 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));
}
} }

View file

@ -204,6 +204,8 @@ public:
WebIDL::ExceptionOr<void> set_selection_end_for_bindings(Optional<WebIDL::UnsignedLong> const&); WebIDL::ExceptionOr<void> set_selection_end_for_bindings(Optional<WebIDL::UnsignedLong> const&);
Optional<WebIDL::UnsignedLong> selection_end_for_bindings() const; Optional<WebIDL::UnsignedLong> selection_end_for_bindings() const;
virtual bool is_child_node_selectable(DOM::Node const&) const override;
private: private:
HTMLInputElement(DOM::Document&, DOM::QualifiedName); HTMLInputElement(DOM::Document&, DOM::QualifiedName);

View file

@ -449,9 +449,19 @@ bool EventHandler::handle_mousedown(CSSPixelPoint viewport_position, CSSPixelPoi
HTML::run_unfocusing_steps(focused_element); HTML::run_unfocusing_steps(focused_element);
} }
// If we didn't focus anything, place the document text cursor at the mouse position. // Ask the next non-shadow parent element whether the node at the mouse position is selectable.
// FIXME: This is all rather strange. Find a better solution. auto& root_node = dom_node->root();
if (!did_focus_something || dom_node->is_editable()) { 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<HTML::HTMLElement*>(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(); auto& realm = document->realm();
document->set_cursor_position(DOM::Position::create(realm, *dom_node, result->index_in_node)); document->set_cursor_position(DOM::Position::create(realm, *dom_node, result->index_in_node));
if (auto selection = document->get_selection()) { if (auto selection = document->get_selection()) {