LibWeb: Implement selectionchange event according to spec

This commit is contained in:
Jelle Raaijmakers 2024-10-09 13:50:06 +02:00 committed by Andreas Kling
parent aab5a9e944
commit a58f39c9e2
Notes: github-actions[bot] 2024-10-09 17:09:45 +00:00
4 changed files with 82 additions and 14 deletions

View file

@ -568,6 +568,10 @@ public:
bool query_command_supported(String const& command);
String query_command_value(String const& command);
// https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event
bool has_scheduled_selectionchange_event() const { return m_has_scheduled_selectionchange_event; }
void set_scheduled_selectionchange_event(bool value) { m_has_scheduled_selectionchange_event = value; }
bool is_allowed_to_use_feature(PolicyControlledFeature) const;
void did_stop_being_active_document_in_navigable();
@ -1000,6 +1004,9 @@ private:
// https://dom.spec.whatwg.org/#document-allow-declarative-shadow-roots
bool m_allow_declarative_shadow_roots { false };
// https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event
bool m_has_scheduled_selectionchange_event { false };
JS::GCPtr<JS::ConsoleClient> m_console_client;
JS::GCPtr<DOM::Position> m_cursor_position;

View file

@ -2,6 +2,7 @@
* Copyright (c) 2020, the SerenityOS developers.
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2024, Jelle Raaijmakers <jelle@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -21,6 +22,8 @@
#include <LibWeb/Geometry/DOMRect.h>
#include <LibWeb/Geometry/DOMRectList.h>
#include <LibWeb/HTML/HTMLHtmlElement.h>
#include <LibWeb/HTML/HTMLInputElement.h>
#include <LibWeb/HTML/HTMLTextAreaElement.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Namespace.h>
@ -96,30 +99,70 @@ void Range::set_associated_selection(Badge<Selection::Selection>, JS::GCPtr<Sele
void Range::update_associated_selection()
{
if (auto* viewport = m_start_container->document().paintable()) {
auto& document = m_start_container->document();
if (auto* viewport = document.paintable()) {
viewport->recompute_selection_states(*this);
viewport->update_selection();
viewport->set_needs_display();
}
if (!m_associated_selection)
// https://w3c.github.io/selection-api/#selectionchange-event
// 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<HTML::HTMLInputElement>(*m_start_container))
schedule_a_selectionchange_event(verify_cast<HTML::HTMLInputElement>(*m_start_container));
if (is<HTML::HTMLTextAreaElement>(*m_start_container))
schedule_a_selectionchange_event(verify_cast<HTML::HTMLTextAreaElement>(*m_start_container));
}
}
// https://w3c.github.io/selection-api/#scheduling-selectionhange-event
template<SelectionChangeTarget T>
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;
// https://w3c.github.io/selection-api/#selectionchange-event
// 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 queue a task on the user interaction
// task source to fire an event named selectionchange, which does not bubble and is not cancelable, at the document
// associated with the selection.
auto document = m_associated_selection->document();
queue_global_task(HTML::Task::Source::UserInteraction, relevant_global_object(*document), JS::create_heap_function(document->heap(), [document] {
EventInit event_init;
event_init.bubbles = false;
event_init.cancelable = false;
auto event = DOM::Event::create(document->realm(), HTML::EventNames::selectionchange, event_init);
document->dispatch_event(event);
// 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> 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<SelectionChangeTarget T>
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<T, Element>;
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);
}
// https://dom.spec.whatwg.org/#concept-range-root
Node& Range::root()
{

View file

@ -23,6 +23,13 @@ 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<typename T>
concept SelectionChangeTarget = DerivedFrom<T, EventTarget> && requires(T t) {
{ t.has_scheduled_selectionchange_event() } -> SameAs<bool>;
{ t.set_scheduled_selectionchange_event(bool()) } -> SameAs<void>;
};
class Range final : public AbstractRange {
WEB_PLATFORM_OBJECT(Range, AbstractRange);
JS_DECLARE_ALLOCATOR(Range);
@ -108,6 +115,10 @@ private:
Node const& root() const;
void update_associated_selection();
template<SelectionChangeTarget T>
void schedule_a_selectionchange_event(T&);
template<SelectionChangeTarget T>
void fire_a_selectionchange_event(T&);
enum class StartOrEnd {
Start,

View file

@ -162,6 +162,10 @@ public:
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setselectionrange
WebIDL::ExceptionOr<void> set_selection_range(Optional<WebIDL::UnsignedLong> start, Optional<WebIDL::UnsignedLong> end, Optional<String> direction);
// https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event
bool has_scheduled_selectionchange_event() const { return m_has_scheduled_selectionchange_event; }
void set_scheduled_selectionchange_event(bool value) { m_has_scheduled_selectionchange_event = value; }
protected:
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
void relevant_value_was_changed(JS::GCPtr<DOM::Text>);
@ -173,6 +177,9 @@ private:
WebIDL::UnsignedLong m_selection_start { 0 };
WebIDL::UnsignedLong m_selection_end { 0 };
SelectionDirection m_selection_direction { SelectionDirection::None };
// https://w3c.github.io/selection-api/#dfn-has-scheduled-selectionchange-event
bool m_has_scheduled_selectionchange_event { false };
};
}