From 7874f325a894854f4c795e4f948d84a1f0e3cabf Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Fri, 18 Jul 2025 13:03:46 +0100 Subject: [PATCH] LibWeb: Implement `InputEvent.getTargetRanges()` This returns a list of ranges that would be affected by a change to the DOM if the input event is not cancelled. --- Libraries/LibWeb/Editing/ExecCommand.cpp | 2 +- Libraries/LibWeb/Page/EventHandler.cpp | 16 ++++++++-- Libraries/LibWeb/UIEvents/InputEvent.cpp | 22 ++++++++++---- Libraries/LibWeb/UIEvents/InputEvent.h | 8 +++-- .../UIEvents/inputevent-gettargetranges.txt | 2 ++ .../UIEvents/inputevent-gettargetranges.html | 29 +++++++++++++++++++ 6 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/UIEvents/inputevent-gettargetranges.txt create mode 100644 Tests/LibWeb/Text/input/UIEvents/inputevent-gettargetranges.html diff --git a/Libraries/LibWeb/Editing/ExecCommand.cpp b/Libraries/LibWeb/Editing/ExecCommand.cpp index eac449cc611..fbe340b1b28 100644 --- a/Libraries/LibWeb/Editing/ExecCommand.cpp +++ b/Libraries/LibWeb/Editing/ExecCommand.cpp @@ -127,7 +127,7 @@ WebIDL::ExceptionOr Document::exec_command(FlyString const& command, [[may if (command == Editing::CommandNames::insertText) event_init.data = value; - auto event = realm().create(realm(), HTML::EventNames::input, event_init); + auto event = UIEvents::InputEvent::create_from_platform_event(realm(), HTML::EventNames::input, event_init); event->set_is_trusted(true); affected_editing_host->dispatch_event(event); } diff --git a/Libraries/LibWeb/Page/EventHandler.cpp b/Libraries/LibWeb/Page/EventHandler.cpp index f0e50da35f8..7af8af46284 100644 --- a/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Libraries/LibWeb/Page/EventHandler.cpp @@ -1117,6 +1117,18 @@ static bool is_enter_key_or_interoperable_enter_key_combo(UIEvents::KeyCode key, return false; } +static GC::RootVector> target_ranges_for_input_event(DOM::Document const& document) +{ + GC::RootVector> target_ranges { document.heap() }; + if (auto selection = document.get_selection(); selection && !selection->is_collapsed()) { + if (auto range = selection->range()) { + auto static_range = document.realm().create(range->start_container(), range->start_offset(), range->end_container(), range->end_offset()); + target_ranges.append(static_range); + } + } + return target_ranges; +} + EventResult EventHandler::input_event(FlyString const& event_name, FlyString const& input_type, HTML::Navigable& navigable, u32 code_point) { auto document = navigable.active_document(); @@ -1138,11 +1150,11 @@ EventResult EventHandler::input_event(FlyString const& event_name, FlyString con return input_event(event_name, input_type, *navigable_container.content_navigable(), code_point); } - auto event = UIEvents::InputEvent::create_from_platform_event(document->realm(), event_name, input_event_init); + auto event = UIEvents::InputEvent::create_from_platform_event(document->realm(), event_name, input_event_init, target_ranges_for_input_event(*document)); return focused_element->dispatch_event(event) ? EventResult::Accepted : EventResult::Cancelled; } - auto event = UIEvents::InputEvent::create_from_platform_event(document->realm(), event_name, input_event_init); + auto event = UIEvents::InputEvent::create_from_platform_event(document->realm(), event_name, input_event_init, target_ranges_for_input_event(*document)); if (auto* body = document->body()) return body->dispatch_event(event) ? EventResult::Accepted : EventResult::Cancelled; diff --git a/Libraries/LibWeb/UIEvents/InputEvent.cpp b/Libraries/LibWeb/UIEvents/InputEvent.cpp index 5aab89af644..45538a8bd16 100644 --- a/Libraries/LibWeb/UIEvents/InputEvent.cpp +++ b/Libraries/LibWeb/UIEvents/InputEvent.cpp @@ -12,9 +12,9 @@ namespace Web::UIEvents { GC_DEFINE_ALLOCATOR(InputEvent); -GC::Ref InputEvent::create_from_platform_event(JS::Realm& realm, FlyString const& event_name, InputEventInit const& event_init) +GC::Ref InputEvent::create_from_platform_event(JS::Realm& realm, FlyString const& event_name, InputEventInit const& event_init, Vector> const& target_ranges) { - auto event = realm.create(realm, event_name, event_init); + auto event = realm.create(realm, event_name, event_init, target_ranges); event->set_bubbles(true); if (event_name == "beforeinput"_fly_string) { event->set_cancelable(true); @@ -27,11 +27,12 @@ WebIDL::ExceptionOr> InputEvent::construct_impl(JS::Realm& r return realm.create(realm, event_name, event_init); } -InputEvent::InputEvent(JS::Realm& realm, FlyString const& event_name, InputEventInit const& event_init) +InputEvent::InputEvent(JS::Realm& realm, FlyString const& event_name, InputEventInit const& event_init, Vector> const& target_ranges) : UIEvent(realm, event_name, event_init) , m_data(event_init.data) , m_is_composing(event_init.is_composing) , m_input_type(event_init.input_type) + , m_target_ranges(target_ranges) { } @@ -43,10 +44,19 @@ void InputEvent::initialize(JS::Realm& realm) Base::initialize(realm); } -Vector InputEvent::get_target_ranges() const +void InputEvent::visit_edges(Visitor& visitor) { - dbgln("FIXME: Implement InputEvent::get_target_ranges()"); - return {}; + Base::visit_edges(visitor); + visitor.visit(m_target_ranges); +} + +// https://w3c.github.io/input-events/#dom-inputevent-gettargetranges +ReadonlySpan> InputEvent::get_target_ranges() const +{ + // getTargetRanges() returns an array of StaticRanges representing the content that the event will modify if it is + // not canceled. The returned StaticRanges MUST cover only the code points that the browser would normally replace, + // even if they are only part of a grapheme cluster. + return m_target_ranges; } } diff --git a/Libraries/LibWeb/UIEvents/InputEvent.h b/Libraries/LibWeb/UIEvents/InputEvent.h index c46e204dd6d..bdecfd2b26a 100644 --- a/Libraries/LibWeb/UIEvents/InputEvent.h +++ b/Libraries/LibWeb/UIEvents/InputEvent.h @@ -22,7 +22,7 @@ class InputEvent final : public UIEvent { GC_DECLARE_ALLOCATOR(InputEvent); public: - [[nodiscard]] static GC::Ref create_from_platform_event(JS::Realm&, FlyString const& type, InputEventInit const& event_init); + [[nodiscard]] static GC::Ref create_from_platform_event(JS::Realm&, FlyString const& type, InputEventInit const& event_init, Vector> const& target_ranges = {}); static WebIDL::ExceptionOr> construct_impl(JS::Realm&, FlyString const& event_name, InputEventInit const& event_init); virtual ~InputEvent() override; @@ -36,16 +36,18 @@ public: // https://w3c.github.io/uievents/#dom-inputevent-inputtype FlyString input_type() const { return m_input_type; } - Vector get_target_ranges() const; + ReadonlySpan> get_target_ranges() const; private: - InputEvent(JS::Realm&, FlyString const& event_name, InputEventInit const&); + InputEvent(JS::Realm&, FlyString const& event_name, InputEventInit const&, Vector> const& target_ranges = {}); virtual void initialize(JS::Realm&) override; + virtual void visit_edges(Visitor&) override; Optional m_data; bool m_is_composing; FlyString m_input_type; + Vector> m_target_ranges; }; } diff --git a/Tests/LibWeb/Text/expected/UIEvents/inputevent-gettargetranges.txt b/Tests/LibWeb/Text/expected/UIEvents/inputevent-gettargetranges.txt new file mode 100644 index 00000000000..c524c6d024d --- /dev/null +++ b/Tests/LibWeb/Text/expected/UIEvents/inputevent-gettargetranges.txt @@ -0,0 +1,2 @@ +Target range count: 1. Start offset: 1, End offset: 3 +No target ranges diff --git a/Tests/LibWeb/Text/input/UIEvents/inputevent-gettargetranges.html b/Tests/LibWeb/Text/input/UIEvents/inputevent-gettargetranges.html new file mode 100644 index 00000000000..33714a65267 --- /dev/null +++ b/Tests/LibWeb/Text/input/UIEvents/inputevent-gettargetranges.html @@ -0,0 +1,29 @@ + +
text
+ +