Fix timing of change events for <select> elements

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
Simon Wülker 2025-06-06 13:31:43 +02:00
parent 30731dd5ae
commit e63a344d91

View file

@ -16,6 +16,8 @@ use embedder_traits::{SelectElementOptionOrOptgroup, SelectElementOption};
use euclid::{Size2D, Point2D, Rect}; use euclid::{Size2D, Point2D, Rect};
use embedder_traits::{FormControl as EmbedderFormControl, EmbedderMsg}; use embedder_traits::{FormControl as EmbedderFormControl, EmbedderMsg};
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::event::{EventBubbles, EventCancelable, EventComposed};
use crate::dom::bindings::codegen::GenericBindings::HTMLOptGroupElementBinding::HTMLOptGroupElement_Binding::HTMLOptGroupElementMethods; use crate::dom::bindings::codegen::GenericBindings::HTMLOptGroupElementBinding::HTMLOptGroupElement_Binding::HTMLOptGroupElementMethods;
use crate::dom::activation::Activatable; use crate::dom::activation::Activatable;
use crate::dom::attr::Attr; use crate::dom::attr::Attr;
@ -346,13 +348,6 @@ impl HTMLSelectElement {
.SetData(displayed_text.trim().into()); .SetData(displayed_text.trim().into());
} }
pub(crate) fn selection_changed(&self, can_gc: CanGc) {
self.update_shadow_tree(can_gc);
self.upcast::<EventTarget>()
.fire_bubbling_event(atom!("change"), can_gc);
}
pub(crate) fn selected_option(&self) -> Option<DomRoot<HTMLOptionElement>> { pub(crate) fn selected_option(&self) -> Option<DomRoot<HTMLOptionElement>> {
self.list_of_options() self.list_of_options()
.find(|opt_elem| opt_elem.Selected()) .find(|opt_elem| opt_elem.Selected())
@ -417,12 +412,39 @@ impl HTMLSelectElement {
return None; return None;
}; };
if response.is_some() && response != selected_index {
self.selection_changed(can_gc);
}
response response
} }
/// <https://html.spec.whatwg.org/multipage/#send-select-update-notifications>
fn send_update_notifications(&self) {
// > When the user agent is to send select update notifications, queue an element task on the
// > user interaction task source given the select element to run these steps:
let this = Trusted::new(self);
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue(task!(send_select_update_notification: move || {
let this = this.root();
// TODO: Step 1. Set the select element's user validity to true.
// Step 2. Fire an event named input at the select element, with the bubbles and composed
// attributes initialized to true.
this.upcast::<EventTarget>()
.fire_event_with_params(
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
EventComposed::Composed,
CanGc::note(),
);
// Step 3. Fire an event named change at the select element, with the bubbles attribute initialized
// to true.
this.upcast::<EventTarget>()
.fire_bubbling_event(atom!("change"), CanGc::note());
}));
}
} }
impl HTMLSelectElementMethods<crate::DomTypeHolder> for HTMLSelectElement { impl HTMLSelectElementMethods<crate::DomTypeHolder> for HTMLSelectElement {
@ -578,21 +600,28 @@ impl HTMLSelectElementMethods<crate::DomTypeHolder> for HTMLSelectElement {
/// <https://html.spec.whatwg.org/multipage/#dom-select-selectedindex> /// <https://html.spec.whatwg.org/multipage/#dom-select-selectedindex>
fn SetSelectedIndex(&self, index: i32, can_gc: CanGc) { fn SetSelectedIndex(&self, index: i32, can_gc: CanGc) {
let mut selection_did_change = false;
let mut opt_iter = self.list_of_options(); let mut opt_iter = self.list_of_options();
for opt in opt_iter.by_ref().take(index as usize) { for opt in opt_iter.by_ref().take(index as usize) {
selection_did_change |= opt.Selected();
opt.set_selectedness(false); opt.set_selectedness(false);
} }
if let Some(opt) = opt_iter.next() { if let Some(selected_option) = opt_iter.next() {
opt.set_selectedness(true); selection_did_change |= !selected_option.Selected();
opt.set_dirtiness(true); selected_option.set_selectedness(true);
selected_option.set_dirtiness(true);
// Reset remaining <option> elements // Reset remaining <option> elements
for opt in opt_iter { for opt in opt_iter {
selection_did_change |= opt.Selected();
opt.set_selectedness(false); opt.set_selectedness(false);
} }
} }
// TODO: Track whether the selected element actually changed if selection_did_change {
self.update_shadow_tree(can_gc); self.update_shadow_tree(can_gc);
}
} }
/// <https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate> /// <https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate>
@ -767,7 +796,6 @@ impl Activatable for HTMLSelectElement {
true true
} }
/// <https://html.spec.whatwg.org/multipage/#input-activation-behavior>
fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) { fn activation_behavior(&self, _event: &Event, _target: &EventTarget, can_gc: CanGc) {
let Some(selected_value) = self.show_menu(can_gc) else { let Some(selected_value) = self.show_menu(can_gc) else {
// The user did not select a value // The user did not select a value
@ -775,6 +803,7 @@ impl Activatable for HTMLSelectElement {
}; };
self.SetSelectedIndex(selected_value as i32, can_gc); self.SetSelectedIndex(selected_value as i32, can_gc);
self.send_update_notifications();
} }
} }