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
+
+
+
+
+