From 32cffbc985d5f2a910a650c819ea985a04e326fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=BClker?= Date: Fri, 6 Jun 2025 16:01:12 +0200 Subject: [PATCH] Fix timing of change events for `` elements. * We did not fire `input` events * We fired `change` events *before* updating the selected value Both of these are only relevant when the embedder selects a value, so due to our lack of webdriver support we can't test them. With these changes it is possible to switch the language on https://toolbox.googleapps.com/apps/main/. --------- Signed-off-by: Simon Wülker --- components/script/dom/event.rs | 8 ++- components/script/dom/eventtarget.rs | 10 +++- components/script/dom/htmlselectelement.rs | 63 ++++++++++++++++------ 3 files changed, 61 insertions(+), 20 deletions(-) diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index 743ced42a4b..345038a08da 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -1124,7 +1124,13 @@ impl TaskOnce for EventTask { let target = self.target.root(); let bubbles = self.bubbles; let cancelable = self.cancelable; - target.fire_event_with_params(self.name, bubbles, cancelable, CanGc::note()); + target.fire_event_with_params( + self.name, + bubbles, + cancelable, + EventComposed::NotComposed, + CanGc::note(), + ); } } diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs index 1a5aafb0ae7..15f77f5fcd5 100644 --- a/components/script/dom/eventtarget.rs +++ b/components/script/dom/eventtarget.rs @@ -59,7 +59,7 @@ use crate::dom::bindings::trace::HashMapTracedValues; use crate::dom::document::Document; use crate::dom::element::Element; use crate::dom::errorevent::ErrorEvent; -use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed, EventStatus}; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlformelement::FormControlElementHelpers; use crate::dom::node::{Node, NodeTraits}; @@ -767,6 +767,7 @@ impl EventTarget { name, EventBubbles::DoesNotBubble, EventCancelable::NotCancelable, + EventComposed::NotComposed, can_gc, ) } @@ -777,6 +778,7 @@ impl EventTarget { name, EventBubbles::Bubbles, EventCancelable::NotCancelable, + EventComposed::NotComposed, can_gc, ) } @@ -787,6 +789,7 @@ impl EventTarget { name, EventBubbles::DoesNotBubble, EventCancelable::Cancelable, + EventComposed::NotComposed, can_gc, ) } @@ -801,19 +804,22 @@ impl EventTarget { name, EventBubbles::Bubbles, EventCancelable::Cancelable, + EventComposed::NotComposed, can_gc, ) } - // https://dom.spec.whatwg.org/#concept-event-fire + /// pub(crate) fn fire_event_with_params( &self, name: Atom, bubbles: EventBubbles, cancelable: EventCancelable, + composed: EventComposed, can_gc: CanGc, ) -> DomRoot { let event = Event::new(&self.global(), name, bubbles, cancelable, can_gc); + event.set_composed(composed.into()); event.fire(self, can_gc); event } diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index 0f48c8e923f..2fd8f35c4d8 100644 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -16,6 +16,8 @@ use embedder_traits::{SelectElementOptionOrOptgroup, SelectElementOption}; use euclid::{Size2D, Point2D, Rect}; 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::activation::Activatable; use crate::dom::attr::Attr; @@ -346,13 +348,6 @@ impl HTMLSelectElement { .SetData(displayed_text.trim().into()); } - pub(crate) fn selection_changed(&self, can_gc: CanGc) { - self.update_shadow_tree(can_gc); - - self.upcast::() - .fire_bubbling_event(atom!("change"), can_gc); - } - pub(crate) fn selected_option(&self) -> Option> { self.list_of_options() .find(|opt_elem| opt_elem.Selected()) @@ -417,12 +412,39 @@ impl HTMLSelectElement { return None; }; - if response.is_some() && response != selected_index { - self.selection_changed(can_gc); - } - response } + + /// + 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::() + .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::() + .fire_bubbling_event(atom!("change"), CanGc::note()); + })); + } } impl HTMLSelectElementMethods for HTMLSelectElement { @@ -578,21 +600,28 @@ impl HTMLSelectElementMethods for HTMLSelectElement { /// fn SetSelectedIndex(&self, index: i32, can_gc: CanGc) { + let mut selection_did_change = false; + let mut opt_iter = self.list_of_options(); for opt in opt_iter.by_ref().take(index as usize) { + selection_did_change |= opt.Selected(); opt.set_selectedness(false); } - if let Some(opt) = opt_iter.next() { - opt.set_selectedness(true); - opt.set_dirtiness(true); + if let Some(selected_option) = opt_iter.next() { + selection_did_change |= !selected_option.Selected(); + selected_option.set_selectedness(true); + selected_option.set_dirtiness(true); + // Reset remaining