mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-01-22 17:24:48 -05:00
LibWeb: Fix selectionchange
event dispatch on text control elements
With a8077f79cc
Selection object is no
longer aware of selection state inside text controls (<textarea> and
<input>), so this change makes them responsible for dispatching
`selectionchange` if their selection state was changed.
This commit is contained in:
parent
3a6b698572
commit
e915143593
Notes:
github-actions[bot]
2024-11-01 14:07:07 +00:00
Author: https://github.com/kalenikaliaksandr Commit: https://github.com/LadybirdBrowser/ladybird/commit/e915143593b Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2094 Reviewed-by: https://github.com/gmta ✅
7 changed files with 91 additions and 70 deletions
|
@ -0,0 +1,2 @@
|
|||
selectionchange event dispatched on textarea should bubble: true
|
||||
Selected range: 6 - 10
|
16
Tests/LibWeb/Text/input/selectionchange-on-textarea.html
Normal file
16
Tests/LibWeb/Text/input/selectionchange-on-textarea.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="include.js"></script>
|
||||
<textarea id="textarea">hello hmmm</textarea>
|
||||
<script>
|
||||
asyncTest(done => {
|
||||
const textarea = document.getElementById("textarea");
|
||||
textarea.addEventListener("selectionchange", event => {
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
println(`selectionchange event dispatched on textarea should bubble: ${event.bubbles}`);
|
||||
println(`Selected range: ${start} - ${end}`);
|
||||
done();
|
||||
});
|
||||
textarea.setSelectionRange(6, 10);
|
||||
});
|
||||
</script>
|
|
@ -18,14 +18,12 @@
|
|||
#include <LibWeb/DOM/Node.h>
|
||||
#include <LibWeb/DOM/ProcessingInstruction.h>
|
||||
#include <LibWeb/DOM/Range.h>
|
||||
#include <LibWeb/DOM/SelectionchangeEventDispatching.h>
|
||||
#include <LibWeb/DOM/Text.h>
|
||||
#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>
|
||||
#include <LibWeb/Painting/ViewportPaintable.h>
|
||||
|
||||
|
@ -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<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;
|
||||
|
||||
// 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);
|
||||
schedule_a_selectionchange_event(document, document);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-range-root
|
||||
|
|
|
@ -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<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);
|
||||
|
@ -115,10 +108,6 @@ 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,
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/DOM/EventTarget.h>
|
||||
|
||||
namespace Web::DOM {
|
||||
|
||||
// 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>;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/selection-api/#scheduling-selectionhange-event
|
||||
template<SelectionChangeTarget T>
|
||||
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<SelectionChangeTarget T>
|
||||
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<T, Element>;
|
||||
event_init.cancelable = false;
|
||||
|
||||
auto event = DOM::Event::create(document.realm(), HTML::EventNames::selectionchange, event_init);
|
||||
target.dispatch_event(event);
|
||||
}
|
||||
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/DOM/Event.h>
|
||||
#include <LibWeb/DOM/Position.h>
|
||||
#include <LibWeb/DOM/SelectionchangeEventDispatching.h>
|
||||
#include <LibWeb/HTML/FormAssociatedElement.h>
|
||||
#include <LibWeb/HTML/HTMLButtonElement.h>
|
||||
#include <LibWeb/HTML/HTMLFieldSetElement.h>
|
||||
|
@ -580,9 +581,7 @@ void FormAssociatedTextControlElement::set_the_selection_range(Optional<WebIDL::
|
|||
});
|
||||
}
|
||||
|
||||
// AD-HOC: Notify the element that the selection was changed, so it can perform
|
||||
// element-specific updates.
|
||||
selection_was_changed(m_selection_start, m_selection_end);
|
||||
selection_was_changed();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -657,6 +656,15 @@ void FormAssociatedTextControlElement::collapse_selection_to_offset(size_t posit
|
|||
|
||||
void FormAssociatedTextControlElement::selection_was_changed()
|
||||
{
|
||||
auto& element = form_associated_element_to_html_element();
|
||||
if (is<HTML::HTMLInputElement>(element)) {
|
||||
schedule_a_selectionchange_event(static_cast<HTML::HTMLInputElement&>(element), element.document());
|
||||
} else if (is<HTML::HTMLTextAreaElement>(element)) {
|
||||
schedule_a_selectionchange_event(static_cast<HTML::HTMLTextAreaElement&>(element), element.document());
|
||||
} else {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
auto text_node = form_associated_element_to_text_node();
|
||||
if (!text_node)
|
||||
return;
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue