diff --git a/Tests/LibWeb/Text/expected/selectionchange-on-textarea.txt b/Tests/LibWeb/Text/expected/selectionchange-on-textarea.txt
new file mode 100644
index 00000000000..02bafbdb507
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/selectionchange-on-textarea.txt
@@ -0,0 +1,2 @@
+selectionchange event dispatched on textarea should bubble: true
+Selected range: 6 - 10
diff --git a/Tests/LibWeb/Text/input/selectionchange-on-textarea.html b/Tests/LibWeb/Text/input/selectionchange-on-textarea.html
new file mode 100644
index 00000000000..3420ac1d4d0
--- /dev/null
+++ b/Tests/LibWeb/Text/input/selectionchange-on-textarea.html
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/Userland/Libraries/LibWeb/DOM/Range.cpp b/Userland/Libraries/LibWeb/DOM/Range.cpp
index 17b985e2045..137c18b4ca9 100644
--- a/Userland/Libraries/LibWeb/DOM/Range.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Range.cpp
@@ -18,14 +18,12 @@
#include
#include
#include
+#include
#include
#include
#include
#include
-#include
-#include
#include
-#include
#include
#include
@@ -109,57 +107,7 @@ void Range::update_associated_selection()
// When the selection is dissociated with its range, associated with a new range, or the
// associated range's boundary point is mutated either by the user or the content script, the
// user agent must schedule a selectionchange event on document.
- schedule_a_selectionchange_event(document);
-
- // When an input or textarea element provide a text selection and its selection changes (in
- // either extent or direction), the user agent must schedule a selectionchange event on the
- // element.
- if (m_start_container == m_end_container) {
- if (is(*m_start_container))
- schedule_a_selectionchange_event(verify_cast(*m_start_container));
- if (is(*m_start_container))
- schedule_a_selectionchange_event(verify_cast(*m_start_container));
- }
-}
-
-// https://w3c.github.io/selection-api/#scheduling-selectionhange-event
-template
-void Range::schedule_a_selectionchange_event(T& target)
-{
- // 1. If target's has scheduled selectionchange event is true, abort these steps.
- if (target.has_scheduled_selectionchange_event())
- return;
-
- // AD-HOC (https://github.com/w3c/selection-api/issues/338):
- // Set target's has scheduled selectionchange event to true
- target.set_scheduled_selectionchange_event(true);
-
- // 2. Queue a task on the user interaction task source to fire a selectionchange event on
- // target.
- JS::NonnullGCPtr document = m_start_container->document();
- queue_global_task(HTML::Task::Source::UserInteraction, relevant_global_object(*document), JS::create_heap_function(document->heap(), [&] {
- fire_a_selectionchange_event(target);
- }));
-}
-
-// https://w3c.github.io/selection-api/#firing-selectionhange-event
-template
-void Range::fire_a_selectionchange_event(T& target)
-{
- // 1. Set target's has scheduled selectionchange event to false.
- target.set_scheduled_selectionchange_event(false);
-
- // 2. If target is an element, fire an event named selectionchange, which bubbles and not
- // cancelable, at target.
- // 3. Otherwise, if target is a document, fire an event named selectionchange, which does not
- // bubble and not cancelable, at target.
- EventInit event_init;
- event_init.bubbles = SameAs;
- event_init.cancelable = false;
-
- auto& realm = m_start_container->document().realm();
- auto event = DOM::Event::create(realm, HTML::EventNames::selectionchange, event_init);
- target.dispatch_event(event);
+ schedule_a_selectionchange_event(document, document);
}
// https://dom.spec.whatwg.org/#concept-range-root
diff --git a/Userland/Libraries/LibWeb/DOM/Range.h b/Userland/Libraries/LibWeb/DOM/Range.h
index bbcc78e86ee..33c6c657ab5 100644
--- a/Userland/Libraries/LibWeb/DOM/Range.h
+++ b/Userland/Libraries/LibWeb/DOM/Range.h
@@ -23,13 +23,6 @@ enum class RelativeBoundaryPointPosition {
// https://dom.spec.whatwg.org/#concept-range-bp-position
RelativeBoundaryPointPosition position_of_boundary_point_relative_to_other_boundary_point(Node const& node_a, u32 offset_a, Node const& node_b, u32 offset_b);
-// https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event
-template
-concept SelectionChangeTarget = DerivedFrom && requires(T t) {
- { t.has_scheduled_selectionchange_event() } -> SameAs;
- { t.set_scheduled_selectionchange_event(bool()) } -> SameAs;
-};
-
class Range final : public AbstractRange {
WEB_PLATFORM_OBJECT(Range, AbstractRange);
JS_DECLARE_ALLOCATOR(Range);
@@ -115,10 +108,6 @@ private:
Node const& root() const;
void update_associated_selection();
- template
- void schedule_a_selectionchange_event(T&);
- template
- void fire_a_selectionchange_event(T&);
enum class StartOrEnd {
Start,
diff --git a/Userland/Libraries/LibWeb/DOM/SelectionchangeEventDispatching.h b/Userland/Libraries/LibWeb/DOM/SelectionchangeEventDispatching.h
new file mode 100644
index 00000000000..11ab6427557
--- /dev/null
+++ b/Userland/Libraries/LibWeb/DOM/SelectionchangeEventDispatching.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2024, Aliaksandr Kalenik
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace Web::DOM {
+
+// https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event
+template
+concept SelectionChangeTarget = DerivedFrom && requires(T t) {
+ { t.has_scheduled_selectionchange_event() } -> SameAs;
+ { t.set_scheduled_selectionchange_event(bool()) } -> SameAs;
+};
+
+// https://w3c.github.io/selection-api/#scheduling-selectionhange-event
+template
+void schedule_a_selectionchange_event(T& target, Document& document)
+{
+ // 1. If target's has scheduled selectionchange event is true, abort these steps.
+ if (target.has_scheduled_selectionchange_event())
+ return;
+
+ // AD-HOC (https://github.com/w3c/selection-api/issues/338):
+ // Set target's has scheduled selectionchange event to true
+ target.set_scheduled_selectionchange_event(true);
+
+ // 2. Queue a task on the user interaction task source to fire a selectionchange event on
+ // target.
+ queue_global_task(HTML::Task::Source::UserInteraction, relevant_global_object(document), JS::create_heap_function(document.heap(), [&] {
+ fire_a_selectionchange_event(target, document);
+ }));
+}
+
+// https://w3c.github.io/selection-api/#firing-selectionhange-event
+template
+void fire_a_selectionchange_event(T& target, Document& document)
+{
+ // 1. Set target's has scheduled selectionchange event to false.
+ target.set_scheduled_selectionchange_event(false);
+
+ // 2. If target is an element, fire an event named selectionchange, which bubbles and not
+ // cancelable, at target.
+ // 3. Otherwise, if target is a document, fire an event named selectionchange, which does not
+ // bubble and not cancelable, at target.
+ EventInit event_init;
+ event_init.bubbles = DerivedFrom;
+ event_init.cancelable = false;
+
+ auto event = DOM::Event::create(document.realm(), HTML::EventNames::selectionchange, event_init);
+ target.dispatch_event(event);
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp
index 763b02d6066..012931cb1ce 100644
--- a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -580,9 +581,7 @@ void FormAssociatedTextControlElement::set_the_selection_range(Optional(element)) {
+ schedule_a_selectionchange_event(static_cast(element), element.document());
+ } else if (is(element)) {
+ schedule_a_selectionchange_event(static_cast(element), element.document());
+ } else {
+ VERIFY_NOT_REACHED();
+ }
+
auto text_node = form_associated_element_to_text_node();
if (!text_node)
return;
diff --git a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h
index 630135b735f..16f2540a55d 100644
--- a/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h
+++ b/Userland/Libraries/LibWeb/HTML/FormAssociatedElement.h
@@ -192,8 +192,6 @@ protected:
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
void relevant_value_was_changed();
- virtual void selection_was_changed([[maybe_unused]] size_t selection_start, [[maybe_unused]] size_t selection_end) { }
-
private:
void collapse_selection_to_offset(size_t);
void selection_was_changed();