diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp index 7428a42ecc8..d306c967b77 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.cpp +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.cpp @@ -890,6 +890,18 @@ void FormAssociatedTextControlElement::handle_delete(DeleteDirection direction) did_edit_text_node(); } +Optional FormAssociatedTextControlElement::selected_text_for_stringifier() const +{ + // https://w3c.github.io/selection-api/#dom-selection-stringifier + // Used for clipboard copy and window.getSelection().toString() when this element is active. + size_t start = this->selection_start(); + size_t end = this->selection_end(); + if (start >= end) + return {}; + + return Utf16String::from_utf16(relevant_value().substring_view(start, end - start)); +} + void FormAssociatedTextControlElement::collapse_selection_to_offset(size_t position) { m_selection_start = position; diff --git a/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Libraries/LibWeb/HTML/FormAssociatedElement.h index 83af2605df4..2339edb5543 100644 --- a/Libraries/LibWeb/HTML/FormAssociatedElement.h +++ b/Libraries/LibWeb/HTML/FormAssociatedElement.h @@ -197,8 +197,9 @@ class WEB_API FormAssociatedTextControlElement , public InputEventsTarget { public: // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value - virtual Utf16String relevant_value() = 0; + virtual Utf16String relevant_value() const = 0; virtual WebIDL::ExceptionOr set_relevant_value(Utf16String const&) = 0; + virtual Optional selected_text_for_stringifier() const; virtual void set_dirty_value_flag(bool flag) = 0; diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Libraries/LibWeb/HTML/HTMLInputElement.cpp index 71085d2e117..8c6c57509f4 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -1447,6 +1447,9 @@ void HTMLInputElement::did_receive_focus() if (m_placeholder_text_node) m_placeholder_text_node->invalidate_style(DOM::StyleInvalidationReason::DidReceiveFocus); + + if (has_selectable_text()) + document().get_selection()->remove_all_ranges(); } void HTMLInputElement::did_lose_focus() @@ -3298,6 +3301,7 @@ bool HTMLInputElement::has_selectable_text() const case TypeAttributeState::Time: case TypeAttributeState::LocalDateAndTime: case TypeAttributeState::Number: + case TypeAttributeState::Email: return true; default: return false; @@ -3790,4 +3794,31 @@ bool HTMLInputElement::uses_button_layout() const TypeAttributeState::Button, TypeAttributeState::Color, TypeAttributeState::FileUpload); } +Optional HTMLInputElement::selected_text_for_stringifier() const +{ + // https://w3c.github.io/selection-api/#dom-selection-stringifier + // Used for clipboard copy and window.getSelection().toString() when this element is active. + if (!has_selectable_text()) + return {}; + + size_t start = this->selection_start(); + size_t end = this->selection_end(); + if (start >= end) + return {}; + + switch (type_state()) { + case TypeAttributeState::Text: + case TypeAttributeState::Search: + case TypeAttributeState::Telephone: + case TypeAttributeState::URL: + case TypeAttributeState::Email: + return Utf16String::from_utf16(relevant_value().substring_view(start, end - start)); + + case TypeAttributeState::Password: + return Utf16String::repeated(0x2022, end - start); // 0x2022 is BULLET character + default: + return {}; + } +} + } diff --git a/Libraries/LibWeb/HTML/HTMLInputElement.h b/Libraries/LibWeb/HTML/HTMLInputElement.h index 37097a673b5..3ab407f3aa7 100644 --- a/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -85,8 +85,9 @@ public: WebIDL::ExceptionOr set_value(Utf16String const&); // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value - virtual Utf16String relevant_value() override { return value(); } + virtual Utf16String relevant_value() const override { return value(); } WebIDL::ExceptionOr set_relevant_value(Utf16String const& value) override { return set_value(value); } + virtual Optional selected_text_for_stringifier() const override; virtual void set_dirty_value_flag(bool flag) override { m_dirty_value = flag; } diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp index 1f8e6be0300..af6a1ea7bb1 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp @@ -81,6 +81,8 @@ void HTMLTextAreaElement::did_receive_focus() if (m_placeholder_text_node) m_placeholder_text_node->invalidate_style(DOM::StyleInvalidationReason::DidReceiveFocus); + + document().get_selection()->remove_all_ranges(); } void HTMLTextAreaElement::did_lose_focus() diff --git a/Libraries/LibWeb/HTML/HTMLTextAreaElement.h b/Libraries/LibWeb/HTML/HTMLTextAreaElement.h index 5310d615369..91f81a6807c 100644 --- a/Libraries/LibWeb/HTML/HTMLTextAreaElement.h +++ b/Libraries/LibWeb/HTML/HTMLTextAreaElement.h @@ -88,7 +88,7 @@ public: Utf16String api_value() const; // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value - virtual Utf16String relevant_value() override { return api_value(); } + virtual Utf16String relevant_value() const override { return api_value(); } virtual WebIDL::ExceptionOr set_relevant_value(Utf16String const& value) override; virtual void set_dirty_value_flag(bool flag) override { m_dirty_value = flag; } diff --git a/Libraries/LibWeb/HTML/Navigable.cpp b/Libraries/LibWeb/HTML/Navigable.cpp index f5b24357de4..d7701b42907 100644 --- a/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Libraries/LibWeb/HTML/Navigable.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -2630,7 +2631,16 @@ String Navigable::selected_text() const auto document = active_document(); if (!document) return String {}; + + auto const* input_element = as_if(document->active_element()); + if (input_element && input_element->type_state() == HTML::HTMLInputElement::TypeAttributeState::Password) { + // Apparently nobody wants bullet characters. We leave the clipboard alone here like other browsers. + return String {}; + } auto selection = document->get_selection(); + if (auto form_text = selection->try_form_control_selected_text_for_stringifier(); form_text.has_value()) + return form_text->to_utf8(); + auto range = selection->range(); if (!range) return String {}; diff --git a/Libraries/LibWeb/Selection/Selection.cpp b/Libraries/LibWeb/Selection/Selection.cpp index b399944fdef..2127f407b8d 100644 --- a/Libraries/LibWeb/Selection/Selection.cpp +++ b/Libraries/LibWeb/Selection/Selection.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace Web::Selection { @@ -505,8 +506,27 @@ bool Selection::contains_node(GC::Ref node, bool allow_partial_contai && (end_relative_position == DOM::RelativeBoundaryPointPosition::Equal || end_relative_position == DOM::RelativeBoundaryPointPosition::After); } +Optional Selection::try_form_control_selected_text_for_stringifier() const +{ + // FIXME: According to https://bugzilla.mozilla.org/show_bug.cgi?id=85686#c69, + // sometimes you want the selection from a previously focused form text, probably + // when a button or context menu has temporarily stolen focus but page scripts + // still expect window.getSelection() to have the goodies. + + auto const* form_element = as_if(m_document->active_element()); + if (!form_element) + return {}; + return form_element->selected_text_for_stringifier(); +} + Utf16String Selection::to_string() const { + // https://w3c.github.io/selection-api/#dom-selection-stringifier + // If the selection is within a textarea or input element, it must return the + // selected substring in its value. + if (auto form_text = try_form_control_selected_text_for_stringifier(); form_text.has_value()) + return form_text.release_value(); + // FIXME: This needs more work to be compatible with other engines. // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=10583 if (!m_range) diff --git a/Libraries/LibWeb/Selection/Selection.h b/Libraries/LibWeb/Selection/Selection.h index 0bcaf8576a7..9c368647546 100644 --- a/Libraries/LibWeb/Selection/Selection.h +++ b/Libraries/LibWeb/Selection/Selection.h @@ -56,6 +56,7 @@ public: // Non-standard convenience accessor for the selection's range. GC::Ptr range() const; + Optional try_form_control_selected_text_for_stringifier() const; // Non-standard accessor for the selection's document. GC::Ref document() const; diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index 02aefee98c4..11dd671f1ba 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -763,7 +763,8 @@ void Application::initialize_actions() m_copy_selection_action = Action::create("Copy"sv, ActionID::CopySelection, [this]() { if (auto view = active_web_view(); view.has_value()) - insert_clipboard_entry({ view->selected_text(), "text/plain"_string }); + if (!view->selected_text().is_empty()) + insert_clipboard_entry({ view->selected_text(), "text/plain"_string }); }); m_paste_action = Action::create("Paste"sv, ActionID::Paste, [this]() { if (auto view = active_web_view(); view.has_value()) diff --git a/Tests/LibWeb/Text/expected/selection-toString-focused-text-control-delegation.txt b/Tests/LibWeb/Text/expected/selection-toString-focused-text-control-delegation.txt new file mode 100644 index 00000000000..46ca0154739 --- /dev/null +++ b/Tests/LibWeb/Text/expected/selection-toString-focused-text-control-delegation.txt @@ -0,0 +1,6 @@ +initial, window.getSelection().toString() is "" +after selecting foo, window.getSelection().toString() is "foo" +after selecting bar, window.getSelection().toString() is "bar" +after unselecting bar, window.getSelection().toString() is "" +after selecting 'z' from normal text input, window.getSelection().toString() is "z" +after selecting 'boo' from password input, window.getSelection().toString() is "•••" diff --git a/Tests/LibWeb/Text/input/selection-toString-focused-text-control-delegation.html b/Tests/LibWeb/Text/input/selection-toString-focused-text-control-delegation.html new file mode 100644 index 00000000000..e444df231c3 --- /dev/null +++ b/Tests/LibWeb/Text/input/selection-toString-focused-text-control-delegation.html @@ -0,0 +1,51 @@ + + + + + Selection reflects textarea selection + + + + +

foo

+ + + + +