LibWeb: Implement selectionchange event according to spec
This commit is contained in:
parent
aab5a9e944
commit
a58f39c9e2
Notes:
github-actions[bot]
2024-10-09 17:09:45 +00:00
Author: https://github.com/gmta Commit: https://github.com/LadybirdBrowser/ladybird/commit/a58f39c9e24 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1696 Reviewed-by: https://github.com/tcl3
4 changed files with 82 additions and 14 deletions
Userland/Libraries/LibWeb
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue