mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-08 06:09:58 +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)
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ namespace Web::UIEvents {
|
|||
|
||||
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);
|
||||
if (event_name == "beforeinput"_fly_string) {
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
, 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<DOM::StaticRange> 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<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);
|
||||
|
||||
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);
|
||||
|
||||
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<DOM::StaticRange> get_target_ranges() const;
|
||||
ReadonlySpan<GC::Ref<DOM::StaticRange>> get_target_ranges() const;
|
||||
|
||||
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 visit_edges(Visitor&) override;
|
||||
|
||||
Optional<String> m_data;
|
||||
bool m_is_composing;
|
||||
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