mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
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.
This commit is contained in:
parent
ed3d0d76ec
commit
7874f325a8
Notes:
github-actions[bot]
2025-07-20 00:35:27 +00:00
Author: https://github.com/tcl3
Commit: 7874f325a8
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5505
Reviewed-by: https://github.com/shannonbooth
6 changed files with 67 additions and 12 deletions
|
|
@ -127,7 +127,7 @@ WebIDL::ExceptionOr<bool> Document::exec_command(FlyString const& command, [[may
|
||||||
if (command == Editing::CommandNames::insertText)
|
if (command == Editing::CommandNames::insertText)
|
||||||
event_init.data = value;
|
event_init.data = value;
|
||||||
|
|
||||||
auto event = realm().create<UIEvents::InputEvent>(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);
|
event->set_is_trusted(true);
|
||||||
affected_editing_host->dispatch_event(event);
|
affected_editing_host->dispatch_event(event);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1117,6 +1117,18 @@ static bool is_enter_key_or_interoperable_enter_key_combo(UIEvents::KeyCode key,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static GC::RootVector<GC::Ref<DOM::StaticRange>> target_ranges_for_input_event(DOM::Document const& document)
|
||||||
|
{
|
||||||
|
GC::RootVector<GC::Ref<DOM::StaticRange>> 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<DOM::StaticRange>(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)
|
EventResult EventHandler::input_event(FlyString const& event_name, FlyString const& input_type, HTML::Navigable& navigable, u32 code_point)
|
||||||
{
|
{
|
||||||
auto document = navigable.active_document();
|
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);
|
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;
|
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())
|
if (auto* body = document->body())
|
||||||
return body->dispatch_event(event) ? EventResult::Accepted : EventResult::Cancelled;
|
return body->dispatch_event(event) ? EventResult::Accepted : EventResult::Cancelled;
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ namespace Web::UIEvents {
|
||||||
|
|
||||||
GC_DEFINE_ALLOCATOR(InputEvent);
|
GC_DEFINE_ALLOCATOR(InputEvent);
|
||||||
|
|
||||||
GC::Ref<InputEvent> InputEvent::create_from_platform_event(JS::Realm& realm, FlyString const& event_name, InputEventInit const& event_init)
|
GC::Ref<InputEvent> InputEvent::create_from_platform_event(JS::Realm& realm, FlyString const& event_name, InputEventInit const& event_init, Vector<GC::Ref<DOM::StaticRange>> const& target_ranges)
|
||||||
{
|
{
|
||||||
auto event = realm.create<InputEvent>(realm, event_name, event_init);
|
auto event = realm.create<InputEvent>(realm, event_name, event_init, target_ranges);
|
||||||
event->set_bubbles(true);
|
event->set_bubbles(true);
|
||||||
if (event_name == "beforeinput"_fly_string) {
|
if (event_name == "beforeinput"_fly_string) {
|
||||||
event->set_cancelable(true);
|
event->set_cancelable(true);
|
||||||
|
|
@ -27,11 +27,12 @@ WebIDL::ExceptionOr<GC::Ref<InputEvent>> InputEvent::construct_impl(JS::Realm& r
|
||||||
return realm.create<InputEvent>(realm, event_name, event_init);
|
return realm.create<InputEvent>(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<GC::Ref<DOM::StaticRange>> const& target_ranges)
|
||||||
: UIEvent(realm, event_name, event_init)
|
: UIEvent(realm, event_name, event_init)
|
||||||
, m_data(event_init.data)
|
, m_data(event_init.data)
|
||||||
, m_is_composing(event_init.is_composing)
|
, m_is_composing(event_init.is_composing)
|
||||||
, m_input_type(event_init.input_type)
|
, 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);
|
Base::initialize(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<DOM::StaticRange> InputEvent::get_target_ranges() const
|
void InputEvent::visit_edges(Visitor& visitor)
|
||||||
{
|
{
|
||||||
dbgln("FIXME: Implement InputEvent::get_target_ranges()");
|
Base::visit_edges(visitor);
|
||||||
return {};
|
visitor.visit(m_target_ranges);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://w3c.github.io/input-events/#dom-inputevent-gettargetranges
|
||||||
|
ReadonlySpan<GC::Ref<DOM::StaticRange>> 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class InputEvent final : public UIEvent {
|
||||||
GC_DECLARE_ALLOCATOR(InputEvent);
|
GC_DECLARE_ALLOCATOR(InputEvent);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] static GC::Ref<InputEvent> create_from_platform_event(JS::Realm&, FlyString const& type, InputEventInit const& event_init);
|
[[nodiscard]] static GC::Ref<InputEvent> create_from_platform_event(JS::Realm&, FlyString const& type, InputEventInit const& event_init, Vector<GC::Ref<DOM::StaticRange>> const& target_ranges = {});
|
||||||
static WebIDL::ExceptionOr<GC::Ref<InputEvent>> construct_impl(JS::Realm&, FlyString const& event_name, InputEventInit const& event_init);
|
static WebIDL::ExceptionOr<GC::Ref<InputEvent>> construct_impl(JS::Realm&, FlyString const& event_name, InputEventInit const& event_init);
|
||||||
|
|
||||||
virtual ~InputEvent() override;
|
virtual ~InputEvent() override;
|
||||||
|
|
@ -36,16 +36,18 @@ public:
|
||||||
// https://w3c.github.io/uievents/#dom-inputevent-inputtype
|
// https://w3c.github.io/uievents/#dom-inputevent-inputtype
|
||||||
FlyString input_type() const { return m_input_type; }
|
FlyString input_type() const { return m_input_type; }
|
||||||
|
|
||||||
Vector<DOM::StaticRange> get_target_ranges() const;
|
ReadonlySpan<GC::Ref<DOM::StaticRange>> get_target_ranges() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
InputEvent(JS::Realm&, FlyString const& event_name, InputEventInit const&);
|
InputEvent(JS::Realm&, FlyString const& event_name, InputEventInit const&, Vector<GC::Ref<DOM::StaticRange>> const& target_ranges = {});
|
||||||
|
|
||||||
virtual void initialize(JS::Realm&) override;
|
virtual void initialize(JS::Realm&) override;
|
||||||
|
virtual void visit_edges(Visitor&) override;
|
||||||
|
|
||||||
Optional<String> m_data;
|
Optional<String> m_data;
|
||||||
bool m_is_composing;
|
bool m_is_composing;
|
||||||
FlyString m_input_type;
|
FlyString m_input_type;
|
||||||
|
Vector<GC::Ref<DOM::StaticRange>> m_target_ranges;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Target range count: 1. Start offset: 1, End offset: 3
|
||||||
|
No target ranges
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<div contenteditable>text</div>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
const editableElement = document.querySelector("div");
|
||||||
|
editableElement.addEventListener("beforeinput", e => {
|
||||||
|
const targetRanges = e.getTargetRanges();
|
||||||
|
if (targetRanges.length === 0) {
|
||||||
|
println("No target ranges");
|
||||||
|
} else {
|
||||||
|
println(`Target range count: ${targetRanges.length}. Start offset: ${targetRanges[0].startOffset}, End offset: ${targetRanges[0].endOffset}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
editableElement.focus();
|
||||||
|
const range = document.createRange();
|
||||||
|
range.setStart(editableElement.firstChild, 1);
|
||||||
|
range.setEnd(editableElement.firstChild, 3);
|
||||||
|
const selection = window.getSelection();
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
// GetTargetRanges should return a single range representing the selection.
|
||||||
|
internals.sendKey(editableElement, "Backspace");
|
||||||
|
// An input event shouldn't be dispatched for events that wouldn't affect the DOM.
|
||||||
|
internals.sendKey(editableElement, "End");
|
||||||
|
// GetTargetRanges should be empty because there are no ranges.
|
||||||
|
internals.sendKey(editableElement, "Backspace");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue