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();