diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
index a2378476d0a819..259199fe2adc02 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
@@ -391,22 +391,11 @@ void HTMLInputElement::did_edit_text_node(Badge)
update_placeholder_visibility();
- // NOTE: This is a bit ad-hoc, but basically implements part of "4.10.5.5 Common event behaviors"
- // https://html.spec.whatwg.org/multipage/input.html#common-input-element-events
- queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
- auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
- input_event->set_bubbles(true);
- input_event->set_composed(true);
- dispatch_event(*input_event);
- });
+ user_interaction_did_change_input_value();
}
void HTMLInputElement::did_pick_color(Optional picked_color, ColorPickerUpdateState state)
{
- // https://html.spec.whatwg.org/multipage/input.html#common-input-element-events
- // For input elements without a defined input activation behavior, but to which these events apply
- // and for which the user interface involves both interactive manipulation and an explicit commit action
-
if (type_state() == TypeAttributeState::Color && picked_color.has_value()) {
// then when the user changes the element's value
m_value = value_sanitization_algorithm(picked_color.value().to_string_without_alpha());
@@ -415,15 +404,10 @@ void HTMLInputElement::did_pick_color(Optional picked_color, ColorPickerU
update_color_well_element();
// the user agent must queue an element task on the user interaction task source
- queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
- // given the input element to fire an event named input at the input element, with the bubbles and composed attributes initialized to true
- auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
- input_event->set_bubbles(true);
- input_event->set_composed(true);
- dispatch_event(*input_event);
- });
+ user_interaction_did_change_input_value();
- // and any time the user commits the change, the user agent must queue an element task on the user interaction task source
+ // https://html.spec.whatwg.org/multipage/input.html#common-input-element-events
+ // [...] any time the user commits the change, the user agent must queue an element task on the user interaction task source
if (state == ColorPickerUpdateState::Closed) {
queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
// given the input element
@@ -824,14 +808,26 @@ void HTMLInputElement::create_text_input_shadow_tree()
MUST(up_button->set_inner_html(""sv));
MUST(element->append_child(up_button));
+ auto mouseup_callback_function = JS::NativeFunction::create(
+ realm(), [this](JS::VM&) {
+ commit_pending_changes();
+ return JS::js_undefined();
+ },
+ 0, "", &realm());
+ auto mouseup_callback = realm().heap().allocate_without_realm(*mouseup_callback_function, Bindings::host_defined_environment_settings_object(realm()));
+ DOM::AddEventListenerOptions mouseup_listener_options;
+ mouseup_listener_options.once = true;
+
auto up_callback_function = JS::NativeFunction::create(
realm(), [this](JS::VM&) {
MUST(step_up());
+ user_interaction_did_change_input_value();
return JS::js_undefined();
},
0, "", &realm());
- auto up_callback = realm().heap().allocate_without_realm(*up_callback_function, Bindings::host_defined_environment_settings_object(realm()));
- up_button->add_event_listener_without_options(UIEvents::EventNames::click, DOM::IDLEventListener::create(realm(), up_callback));
+ auto step_up_callback = realm().heap().allocate_without_realm(*up_callback_function, Bindings::host_defined_environment_settings_object(realm()));
+ up_button->add_event_listener_without_options(UIEvents::EventNames::mousedown, DOM::IDLEventListener::create(realm(), step_up_callback));
+ up_button->add_event_listener_without_options(UIEvents::EventNames::mouseup, DOM::IDLEventListener::create(realm(), mouseup_callback));
// Down button
auto down_button = MUST(DOM::create_element(document(), HTML::TagNames::button, Namespace::HTML));
@@ -845,11 +841,13 @@ void HTMLInputElement::create_text_input_shadow_tree()
auto down_callback_function = JS::NativeFunction::create(
realm(), [this](JS::VM&) {
MUST(step_down());
+ user_interaction_did_change_input_value();
return JS::js_undefined();
},
0, "", &realm());
- auto down_callback = realm().heap().allocate_without_realm(*down_callback_function, Bindings::host_defined_environment_settings_object(realm()));
- down_button->add_event_listener_without_options(UIEvents::EventNames::click, DOM::IDLEventListener::create(realm(), down_callback));
+ auto step_down_callback = realm().heap().allocate_without_realm(*down_callback_function, Bindings::host_defined_environment_settings_object(realm()));
+ down_button->add_event_listener_without_options(UIEvents::EventNames::mousedown, DOM::IDLEventListener::create(realm(), step_down_callback));
+ down_button->add_event_listener_without_options(UIEvents::EventNames::mouseup, DOM::IDLEventListener::create(realm(), mouseup_callback));
}
}
@@ -951,18 +949,8 @@ void HTMLInputElement::create_range_input_shadow_tree()
MUST(slider_runnable_track->append_child(*m_slider_thumb));
update_slider_thumb_element();
- // User event listeners
- auto dispatch_input_event = [this]() {
- queue_an_element_task(HTML::Task::Source::UserInteraction, [&] {
- auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
- input_event->set_bubbles(true);
- input_event->set_composed(true);
- dispatch_event(*input_event);
- });
- };
-
auto keydown_callback_function = JS::NativeFunction::create(
- realm(), [this, dispatch_input_event](JS::VM& vm) {
+ realm(), [this](JS::VM& vm) {
auto key = MUST(vm.argument(0).get(vm, "key")).as_string().utf8_string();
if (key == "ArrowLeft" || key == "ArrowDown")
@@ -975,7 +963,7 @@ void HTMLInputElement::create_range_input_shadow_tree()
if (key == "PageUp")
MUST(step_up(10));
- dispatch_input_event();
+ user_interaction_did_change_input_value();
return JS::js_undefined();
},
0, "", &realm());
@@ -983,28 +971,28 @@ void HTMLInputElement::create_range_input_shadow_tree()
add_event_listener_without_options(UIEvents::EventNames::keydown, DOM::IDLEventListener::create(realm(), keydown_callback));
auto wheel_callback_function = JS::NativeFunction::create(
- realm(), [this, dispatch_input_event](JS::VM& vm) {
+ realm(), [this](JS::VM& vm) {
auto deltaY = MUST(vm.argument(0).get(vm, "deltaY")).as_i32();
if (deltaY > 0) {
MUST(step_down());
} else {
MUST(step_up());
}
- dispatch_input_event();
+ user_interaction_did_change_input_value();
return JS::js_undefined();
},
0, "", &realm());
auto wheel_callback = realm().heap().allocate_without_realm(*wheel_callback_function, Bindings::host_defined_environment_settings_object(realm()));
add_event_listener_without_options(UIEvents::EventNames::wheel, DOM::IDLEventListener::create(realm(), wheel_callback));
- auto update_slider_by_mouse = [this, dispatch_input_event](JS::VM& vm) {
+ auto update_slider_by_mouse = [this](JS::VM& vm) {
auto client_x = MUST(vm.argument(0).get(vm, "clientX")).as_double();
auto rect = get_bounding_client_rect();
double minimum = *min();
double maximum = *max();
// FIXME: Snap new value to input steps
MUST(set_value_as_number(clamp(round(((client_x - rect->left()) / rect->width()) * (maximum - minimum) + minimum), minimum, maximum)));
- dispatch_input_event();
+ user_interaction_did_change_input_value();
};
auto mousedown_callback_function = JS::NativeFunction::create(
@@ -1041,6 +1029,23 @@ void HTMLInputElement::create_range_input_shadow_tree()
add_event_listener_without_options(UIEvents::EventNames::mousedown, DOM::IDLEventListener::create(realm(), mousedown_callback));
}
+void HTMLInputElement::user_interaction_did_change_input_value()
+{
+ // https://html.spec.whatwg.org/multipage/input.html#common-input-element-events
+ // For input elements without a defined input activation behavior, but to which these events apply,
+ // and for which the user interface involves both interactive manipulation and an explicit commit action,
+ // then when the user changes the element's value, the user agent must queue an element task on the user interaction task source
+ // given the input element to fire an event named input at the input element, with the bubbles and composed attributes initialized to true,
+ // and any time the user commits the change, the user agent must queue an element task on the user interaction task source given the input
+ // element to set its user validity to true and fire an event named change at the input element, with the bubbles attribute initialized to true.
+ queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
+ auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
+ input_event->set_bubbles(true);
+ input_event->set_composed(true);
+ dispatch_event(*input_event);
+ });
+}
+
void HTMLInputElement::update_slider_thumb_element()
{
if (!m_slider_thumb)
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
index a9dca6c56c33d3..c17beb0580e24b 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
@@ -252,6 +252,8 @@ class HTMLInputElement final
void handle_readonly_attribute(Optional const& value);
WebIDL::ExceptionOr handle_src_attribute(String const& value);
+ void user_interaction_did_change_input_value();
+
// https://html.spec.whatwg.org/multipage/input.html#value-sanitization-algorithm
String value_sanitization_algorithm(String const&) const;