diff options
29 files changed, 463 insertions, 553 deletions
diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 8054d28f584..9f09dff5f25 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -2,6 +2,9 @@ DOMContentLoaded abort activate addtrack +animationend +animationiteration +animationstart beforeunload button canplay @@ -133,5 +136,9 @@ visibilitychange volumechange waiting webglcontextcreationerror +webkitAnimationEnd +webkitAnimationIteration +webkitAnimationStart +webkitTransitionEnd week width diff --git a/components/script/dom/activation.rs b/components/script/dom/activation.rs index a12874b9b43..4e6ac769885 100644 --- a/components/script/dom/activation.rs +++ b/components/script/dom/activation.rs @@ -2,17 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; -use crate::dom::bindings::inheritance::Castable; -use crate::dom::bindings::str::DOMString; use crate::dom::element::Element; -use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; -use crate::dom::mouseevent::MouseEvent; +use crate::dom::htmlinputelement::InputActivationState; use crate::dom::node::window_from_node; use crate::dom::window::ReflowReason; use script_layout_interface::message::ReflowGoal; -use script_traits::MouseButton; /// Trait for elements with defined activation behavior pub trait Activatable { @@ -21,13 +17,17 @@ pub trait Activatable { // Is this particular instance of the element activatable? fn is_instance_activatable(&self) -> bool; - // https://html.spec.whatwg.org/multipage/#run-pre-click-activation-steps - fn pre_click_activation(&self); + // https://dom.spec.whatwg.org/#eventtarget-legacy-pre-activation-behavior + fn legacy_pre_activation_behavior(&self) -> Option<InputActivationState> { + None + } - // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps - fn canceled_activation(&self); + // https://dom.spec.whatwg.org/#eventtarget-legacy-canceled-activation-behavior + fn legacy_canceled_activation_behavior(&self, _state_before: Option<InputActivationState>) {} - // https://html.spec.whatwg.org/multipage/#run-post-click-activation-steps + // https://dom.spec.whatwg.org/#eventtarget-activation-behavior + // event and target are used only by HTMLAnchorElement, in the case + // where the target is an <img ismap> so the href gets coordinates appended fn activation_behavior(&self, event: &Event, target: &EventTarget); // https://html.spec.whatwg.org/multipage/#concept-selector-active @@ -45,75 +45,3 @@ pub trait Activatable { win.reflow(ReflowGoal::Full, ReflowReason::ElementStateChanged); } } - -/// Whether an activation was initiated via the click() method -#[derive(PartialEq)] -pub enum ActivationSource { - FromClick, - NotFromClick, -} - -// https://html.spec.whatwg.org/multipage/#run-synthetic-click-activation-steps -pub fn synthetic_click_activation( - element: &Element, - ctrl_key: bool, - shift_key: bool, - alt_key: bool, - meta_key: bool, - source: ActivationSource, -) { - // Step 1 - if element.click_in_progress() { - return; - } - // Step 2 - element.set_click_in_progress(true); - // Step 3 - let activatable = element.as_maybe_activatable(); - if let Some(a) = activatable { - a.pre_click_activation(); - } - - // Step 4 - // https://html.spec.whatwg.org/multipage/#fire-a-synthetic-mouse-event - let win = window_from_node(element); - let target = element.upcast::<EventTarget>(); - let mouse = MouseEvent::new( - &win, - DOMString::from("click"), - EventBubbles::DoesNotBubble, - EventCancelable::NotCancelable, - Some(&win), - 1, - 0, - 0, - 0, - 0, - ctrl_key, - shift_key, - alt_key, - meta_key, - 0, - MouseButton::Left as u16, - None, - None, - ); - let event = mouse.upcast::<Event>(); - if source == ActivationSource::FromClick { - event.set_trusted(false); - } - target.dispatch_event(event); - - // Step 5 - if let Some(a) = activatable { - if event.DefaultPrevented() { - a.canceled_activation(); - } else { - // post click activation - a.activation_behavior(event, target); - } - } - - // Step 6 - element.set_click_in_progress(false); -} diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 84a63d16238..902463b7d10 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -3,7 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::document_loader::{DocumentLoader, LoadType}; -use crate::dom::activation::{synthetic_click_activation, ActivationSource}; use crate::dom::attr::Attr; use crate::dom::beforeunloadevent::BeforeUnloadEvent; use crate::dom::bindings::callback::ExceptionHandling; @@ -557,8 +556,7 @@ impl Document { let event = event.upcast::<Event>(); event.set_trusted(true); // FIXME(nox): Why are errors silenced here? - let _ = window.upcast::<EventTarget>().dispatch_event_with_target( - document.upcast(), + let _ = window.dispatch_event_with_target_override( &event, ); }), @@ -1016,7 +1014,11 @@ impl Document { // https://html.spec.whatwg.org/multipage/#run-authentic-click-activation-steps let activatable = el.as_maybe_activatable(); match mouse_event_type { - MouseEventType::Click => el.authentic_click_activation(event), + MouseEventType::Click => { + el.set_click_in_progress(true); + event.fire(node.upcast()); + el.set_click_in_progress(false); + }, MouseEventType::MouseDown => { if let Some(a) = activatable { a.enter_formal_activation_state(); @@ -1478,16 +1480,9 @@ impl Document { if (keyboard_event.key == Key::Enter || keyboard_event.code == Code::Space) && keyboard_event.state == KeyState::Up { - let maybe_elem = target.downcast::<Element>(); - if let Some(el) = maybe_elem { - synthetic_click_activation( - el, - false, - false, - false, - false, - ActivationSource::NotFromClick, - ) + if let Some(elem) = target.downcast::<Element>() { + elem.upcast::<Node>() + .fire_synthetic_mouse_event_not_trusted(DOMString::from("click")); } } } @@ -1803,7 +1798,6 @@ impl Document { // Step 2 self.incr_ignore_opens_during_unload_counter(); //Step 3-5. - let document = Trusted::new(self); let beforeunload_event = BeforeUnloadEvent::new( &self.window, atom!("beforeunload"), @@ -1814,7 +1808,7 @@ impl Document { event.set_trusted(true); let event_target = self.window.upcast::<EventTarget>(); let has_listeners = event.has_listeners_for(&event_target, &atom!("beforeunload")); - event_target.dispatch_event_with_target(document.root().upcast(), &event); + self.window.dispatch_event_with_target_override(&event); // TODO: Step 6, decrease the event loop's termination nesting level by 1. // Step 7 if has_listeners { @@ -1858,7 +1852,6 @@ impl Document { // TODO: Step 1, increase the event loop's termination nesting level by 1. // Step 2 self.incr_ignore_opens_during_unload_counter(); - let document = Trusted::new(self); // Step 3-6 if self.page_showing.get() { self.page_showing.set(false); @@ -1871,10 +1864,7 @@ impl Document { ); let event = event.upcast::<Event>(); event.set_trusted(true); - let _ = self - .window - .upcast::<EventTarget>() - .dispatch_event_with_target(document.root().upcast(), &event); + let _ = self.window.dispatch_event_with_target_override(&event); // TODO Step 6, document visibility steps. } // Step 7 @@ -1888,7 +1878,7 @@ impl Document { event.set_trusted(true); let event_target = self.window.upcast::<EventTarget>(); let has_listeners = event.has_listeners_for(&event_target, &atom!("unload")); - let _ = event_target.dispatch_event_with_target(document.root().upcast(), &event); + let _ = self.window.dispatch_event_with_target_override(&event); self.fired_unload.set(true); // Step 9 if has_listeners { @@ -1984,8 +1974,7 @@ impl Document { debug!("About to dispatch load for {:?}", document.url()); // FIXME(nox): Why are errors silenced here? - let _ = window.upcast::<EventTarget>().dispatch_event_with_target( - document.upcast(), + let _ = window.dispatch_event_with_target_override( &event, ); @@ -2029,8 +2018,7 @@ impl Document { event.set_trusted(true); // FIXME(nox): Why are errors silenced here? - let _ = window.upcast::<EventTarget>().dispatch_event_with_target( - document.upcast(), + let _ = window.dispatch_event_with_target_override( &event, ); }), diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index a7c45562209..e6485bec5af 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -11,7 +11,6 @@ use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use crate::dom::bindings::codegen::Bindings::ElementBinding; use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; -use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; @@ -39,7 +38,6 @@ use crate::dom::document::{determine_policy_for_token, Document, LayoutDocumentH use crate::dom::documentfragment::DocumentFragment; use crate::dom::domrect::DOMRect; use crate::dom::domtokenlist::DOMTokenList; -use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::htmlanchorelement::HTMLAnchorElement; use crate::dom::htmlbodyelement::{HTMLBodyElement, HTMLBodyElementLayoutHelpers}; @@ -3283,52 +3281,6 @@ impl Element { } } - /// Please call this method *only* for real click events - /// - /// <https://html.spec.whatwg.org/multipage/#run-authentic-click-activation-steps> - /// - /// Use an element's synthetic click activation (or handle_event) for any script-triggered clicks. - /// If the spec says otherwise, check with Manishearth first - pub fn authentic_click_activation(&self, event: &Event) { - // Not explicitly part of the spec, however this helps enforce the invariants - // required to save state between pre-activation and post-activation - // since we cannot nest authentic clicks (unlike synthetic click activation, where - // the script can generate more click events from the handler) - assert!(!self.click_in_progress()); - - let target = self.upcast(); - // Step 2 (requires canvas support) - // Step 3 - self.set_click_in_progress(true); - // Step 4 - let e = self.nearest_activable_element(); - match e { - Some(ref el) => match el.as_maybe_activatable() { - Some(elem) => { - // Step 5-6 - elem.pre_click_activation(); - event.fire(target); - if !event.DefaultPrevented() { - // post click activation - elem.activation_behavior(event, target); - } else { - elem.canceled_activation(); - } - }, - // Step 6 - None => { - event.fire(target); - }, - }, - // Step 6 - None => { - event.fire(target); - }, - } - // Step 7 - self.set_click_in_progress(false); - } - // https://html.spec.whatwg.org/multipage/#language pub fn get_lang(&self) -> String { self.upcast::<Node>() diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index 3c6bf956e9e..cc960c7ba1b 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -8,16 +8,20 @@ use crate::dom::bindings::codegen::Bindings::EventBinding; use crate::dom::bindings::codegen::Bindings::EventBinding::{EventConstants, EventMethods}; use crate::dom::bindings::codegen::Bindings::PerformanceBinding::DOMHighResTimeStamp; use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceBinding::PerformanceMethods; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::error::Fallible; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; -use crate::dom::bindings::root::{DomRoot, DomSlice, MutNullableDom}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::document::Document; +use crate::dom::element::Element; use crate::dom::eventtarget::{CompiledEventListener, EventTarget, ListenerPhase}; use crate::dom::globalscope::GlobalScope; -use crate::dom::node::Node; +use crate::dom::htmlinputelement::InputActivationState; +use crate::dom::mouseevent::MouseEvent; +use crate::dom::node::{Node, ShadowIncluding}; use crate::dom::performance::reduce_timing_resolution; use crate::dom::virtualmethods::vtable_for; use crate::dom::window::Window; @@ -122,14 +126,24 @@ impl Event { } // https://dom.spec.whatwg.org/#event-path + // TODO: shadow roots put special flags in the path, + // and it will stop just being a list of bare EventTargets fn construct_event_path(&self, target: &EventTarget) -> Vec<DomRoot<EventTarget>> { let mut event_path = vec![]; - // The "invoke" algorithm is only used on `target` separately, - // so we don't put it in the path. if let Some(target_node) = target.downcast::<Node>() { - for ancestor in target_node.ancestors() { + // ShadowIncluding::Yes might be closer to right than ::No, + // but still wrong since things about the path change when crossing + // shadow attachments; getting it right needs to change + // more than just that. + for ancestor in target_node.inclusive_ancestors(ShadowIncluding::No) { event_path.push(DomRoot::from_ref(ancestor.upcast::<EventTarget>())); } + // Most event-target-to-parent relationships are node parent + // relationships, but the document-to-global one is not, + // so that's handled separately here. + // (an EventTarget.get_parent_event_target could save + // some redundancy, especially when shadow DOM relationships + // also need to be respected) let top_most_ancestor_or_target = event_path .last() .cloned() @@ -139,6 +153,11 @@ impl Event { event_path.push(DomRoot::from_ref(document.window().upcast())); } } + } else { + // a non-node EventTarget, likely a global. + // No parent to propagate up to, but we still + // need it on the path. + event_path.push(DomRoot::from_ref(target)); } event_path } @@ -147,49 +166,185 @@ impl Event { pub fn dispatch( &self, target: &EventTarget, - target_override: Option<&EventTarget>, + legacy_target_override: bool, + // TODO legacy_did_output_listeners_throw_flag for indexeddb ) -> EventStatus { - assert!(!self.dispatching()); - assert!(self.initialized()); - assert_eq!(self.phase.get(), EventPhase::None); - assert!(self.GetCurrentTarget().is_none()); - // Step 1. self.dispatching.set(true); // Step 2. - self.target.set(Some(target_override.unwrap_or(target))); + let target_override_document; // upcasted EventTarget's lifetime depends on this + let target_override = if legacy_target_override { + target_override_document = target + .downcast::<Window>() + .expect("legacy_target_override must be true only when target is a Window") + .Document(); + target_override_document.upcast::<EventTarget>() + } else { + target + }; - if self.stop_propagation.get() { - // If the event's stop propagation flag is set, we can skip everything because - // it prevents the calls of the invoke algorithm in the spec. + // Step 3 - since step 5 always happens, we can wait until 5.5 - // Step 10-12. - self.clear_dispatching_flags(); + // Step 4 TODO: "retargeting" concept depends on shadow DOM - // Step 14. - return self.status(); - } + // Step 5, outer if-statement, is always true until step 4 is implemented + // Steps 5.1-5.2 TODO: touch target lists don't exist yet - // Step 3-4. + // Steps 5.3 and most of 5.9 + // A change in whatwg/dom#240 specifies that + // the event path belongs to the event itself, rather than being + // a local variable of the dispatch algorithm, but this is mostly + // related to shadow DOM requirements that aren't otherwise + // implemented right now. The path also needs to contain + // various flags instead of just bare event targets. let path = self.construct_event_path(&target); rooted_vec!(let event_path <- path.into_iter()); - // Steps 5-9. In a separate function to short-circuit various things easily. - dispatch_to_listeners(self, target, event_path.r()); - - // Default action. - if let Some(target) = self.GetTarget() { - if let Some(node) = target.downcast::<Node>() { - let vtable = vtable_for(&node); - vtable.handle_event(self); + + // Step 5.4 + let is_activation_event = self.is::<MouseEvent>() && self.type_() == atom!("click"); + + // Step 5.5 + let mut activation_target = if is_activation_event { + target + .downcast::<Element>() + .and_then(|e| e.as_maybe_activatable()) + } else { + // Step 3 + None + }; + + // Steps 5-6 - 5.7 are shadow DOM slot things + + // Step 5.9.8.1, not covered in construct_event_path + // This what makes sure that clicking on e.g. an <img> inside + // an <a> will cause activation of the activatable ancestor. + if is_activation_event && activation_target.is_none() && self.bubbles.get() { + for object in event_path.iter() { + if let Some(activatable_ancestor) = object + .downcast::<Element>() + .and_then(|e| e.as_maybe_activatable()) + { + activation_target = Some(activatable_ancestor); + // once activation_target isn't null, we stop + // looking at ancestors for it. + break; + } + } + } + + // Steps 5.10-5.11 are shadow DOM + + // Not specified in dispatch spec overtly; this is because + // the legacy canceled activation behavior of a checkbox + // or radio button needs to know what happened in the + // corresponding pre-activation behavior. + let mut pre_activation_result: Option<InputActivationState> = None; + + // Step 5.12 + if is_activation_event { + if let Some(maybe_checkbox) = activation_target { + pre_activation_result = maybe_checkbox.legacy_pre_activation_behavior(); + } + } + + let timeline_window = match DomRoot::downcast::<Window>(target.global()) { + Some(window) => { + if window.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { + Some(window) + } else { + None + } + }, + _ => None, + }; + + // Step 5.13 + for object in event_path.iter().rev() { + if &**object == &*target { + self.phase.set(EventPhase::AtTarget); + } else { + self.phase.set(EventPhase::Capturing); + } + + // setting self.target is step 1 of invoke, + // but done here because our event_path isn't a member of self + // (without shadow DOM, target_override is always the + // target to set to) + self.target.set(Some(target_override)); + invoke( + timeline_window.as_deref(), + object, + self, + Some(ListenerPhase::Capturing), + ); + } + + // Step 5.14 + for object in event_path.iter() { + let at_target = &**object == &*target; + if at_target || self.bubbles.get() { + self.phase.set(if at_target { + EventPhase::AtTarget + } else { + EventPhase::Bubbling + }); + + self.target.set(Some(target_override)); + invoke( + timeline_window.as_deref(), + object, + self, + Some(ListenerPhase::Bubbling), + ); + } + } + + // Step 6 + self.phase.set(EventPhase::None); + + // FIXME: The UIEvents spec still expects firing an event + // to carry a "default action" semantic, but the HTML spec + // has removed this concept. Nothing in either spec currently + // (as of Jan 11 2020) says that, e.g., a keydown event on an + // input element causes a character to be typed; the UIEvents + // spec assumes the HTML spec is covering it, and the HTML spec + // no longer specifies any UI event other than mouse click as + // causing an element to perform an action. + // Compare: + // https://w3c.github.io/uievents/#default-action + // https://dom.spec.whatwg.org/#action-versus-occurance + if !self.DefaultPrevented() { + if let Some(target) = self.GetTarget() { + if let Some(node) = target.downcast::<Node>() { + let vtable = vtable_for(&node); + vtable.handle_event(self); + } } } - // Step 10-12. - self.clear_dispatching_flags(); + // Step 7 + self.current_target.set(None); + + // Step 8 TODO: if path were in the event struct, we'd clear it now + + // Step 9 + self.dispatching.set(false); + self.stop_propagation.set(false); + self.stop_immediate.set(false); + + // Step 10 TODO: condition is always false until there's shadow DOM - // Step 14. - self.status() + // Step 11 + if let Some(activation_target) = activation_target { + if self.DefaultPrevented() { + activation_target.legacy_canceled_activation_behavior(pre_activation_result); + } else { + activation_target.activation_behavior(self, target); + } + } + + return self.status(); } pub fn status(&self) -> EventStatus { @@ -206,18 +361,6 @@ impl Event { } #[inline] - // https://dom.spec.whatwg.org/#concept-event-dispatch Steps 10-12. - fn clear_dispatching_flags(&self) { - assert!(self.dispatching.get()); - - self.dispatching.set(false); - self.stop_propagation.set(false); - self.stop_immediate.set(false); - self.phase.set(EventPhase::None); - self.current_target.set(None); - } - - #[inline] pub fn initialized(&self) -> bool { self.initialized.get() } @@ -468,98 +611,55 @@ impl TaskOnce for SimpleEventTask { } } -// See dispatch_event. -// https://dom.spec.whatwg.org/#concept-event-dispatch -fn dispatch_to_listeners(event: &Event, target: &EventTarget, event_path: &[&EventTarget]) { - assert!(!event.stop_propagation.get()); - assert!(!event.stop_immediate.get()); - - let window = match DomRoot::downcast::<Window>(target.global()) { - Some(window) => { - if window.need_emit_timeline_marker(TimelineMarkerType::DOMEvent) { - Some(window) - } else { - None - } - }, - _ => None, - }; - - // Step 5. - event.phase.set(EventPhase::Capturing); - - // Step 6. - for object in event_path.iter().rev() { - invoke( - window.as_deref(), - object, - event, - Some(ListenerPhase::Capturing), - ); - if event.stop_propagation.get() { - return; - } - } - assert!(!event.stop_propagation.get()); - assert!(!event.stop_immediate.get()); - - // Step 7. - event.phase.set(EventPhase::AtTarget); - - // Step 8. - invoke(window.as_deref(), target, event, None); - if event.stop_propagation.get() { - return; - } - assert!(!event.stop_propagation.get()); - assert!(!event.stop_immediate.get()); - - if !event.bubbles.get() { - return; - } - - // Step 9.1. - event.phase.set(EventPhase::Bubbling); - - // Step 9.2. - for object in event_path { - invoke( - window.as_deref(), - object, - event, - Some(ListenerPhase::Bubbling), - ); - if event.stop_propagation.get() { - return; - } - } -} - // https://dom.spec.whatwg.org/#concept-event-listener-invoke fn invoke( - window: Option<&Window>, + timeline_window: Option<&Window>, object: &EventTarget, event: &Event, - specific_listener_phase: Option<ListenerPhase>, + phase: Option<ListenerPhase>, + // TODO legacy_output_did_listeners_throw for indexeddb ) { - // Step 1. - assert!(!event.stop_propagation.get()); + // Step 1: Until shadow DOM puts the event path in the + // event itself, this is easier to do in dispatch before + // calling invoke. + + // Step 2 TODO: relatedTarget only matters for shadow DOM - // Steps 2-3. - let listeners = object.get_listeners_for(&event.type_(), specific_listener_phase); + // Step 3 TODO: touch target lists not implemented // Step 4. + if event.stop_propagation.get() { + return; + } + // Step 5. event.current_target.set(Some(object)); - // Step 5. - inner_invoke(window, object, event, &listeners); + // Step 6 + let listeners = object.get_listeners_for(&event.type_(), phase); - // TODO: step 6. + // Step 7. + let found = inner_invoke(timeline_window, object, event, &listeners); + + // Step 8 + if !found && event.trusted.get() { + if let Some(legacy_type) = match event.type_() { + atom!("animationend") => Some(atom!("webkitAnimationEnd")), + atom!("animationiteration") => Some(atom!("webkitAnimationIteration")), + atom!("animationstart") => Some(atom!("webkitAnimationStart")), + atom!("transitionend") => Some(atom!("webkitTransitionEnd")), + _ => None, + } { + let original_type = event.type_(); + *event.type_.borrow_mut() = legacy_type; + inner_invoke(timeline_window, object, event, &listeners); + *event.type_.borrow_mut() = original_type; + } + } } // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke fn inner_invoke( - window: Option<&Window>, + timeline_window: Option<&Window>, object: &EventTarget, event: &Event, listeners: &[CompiledEventListener], @@ -569,6 +669,15 @@ fn inner_invoke( // Step 2. for listener in listeners { + // FIXME(#25479): We need an "if !listener.removed()" here, + // but there's a subtlety. Where Servo is currently using the + // CompiledEventListener, we really need something that maps to + // https://dom.spec.whatwg.org/#concept-event-listener + // which is not the same thing as the EventListener interface. + // script::dom::eventtarget::EventListenerEntry is the closest + // match we have, and is already holding the "once" flag, + // but it's not a drop-in replacement. + // Steps 2.1 and 2.3-2.4 are not done because `listeners` contain only the // relevant ones for this invoke call during the dispatch algorithm. @@ -580,17 +689,35 @@ fn inner_invoke( object.remove_listener_if_once(&event.type_(), &event_listener); } - // Step 2.6. + // Step 2.6-2.8 + // FIXME(#25478): we need to get the global that the event + // listener is going to be called on, then if it's a Window + // set its .event to the event, remembering the previous + // value of its .event. This allows events to just use + // the word "event" instead of taking the event as an argument. + + // Step 2.9 TODO: EventListener passive option not implemented + + // Step 2.10 let marker = TimelineMarker::start("DOMEvent".to_owned()); + + // Step 2.10 listener.call_or_handle_event(object, event, ExceptionHandling::Report); - if let Some(window) = window { + + if let Some(window) = timeline_window { window.emit_timeline_marker(marker.end()); } + + // Step 2.11 TODO: passive not implemented + + // Step 2.12 + // TODO This is where we put back the .event we + // had before step 2.6. + + // Step 2.13: short-circuit instead of going to next listener if event.stop_immediate.get() { return found; } - - // TODO: step 2.7. } // Step 3. diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs index 1d80781a2fb..91d24759697 100644 --- a/components/script/dom/eventtarget.rs +++ b/components/script/dom/eventtarget.rs @@ -73,7 +73,7 @@ impl CommonEventHandler { } } -#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] +#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub enum ListenerPhase { Capturing, Bubbling, @@ -248,6 +248,8 @@ impl CompiledEventListener { } } +// https://dom.spec.whatwg.org/#concept-event-listener +// (as distinct from https://dom.spec.whatwg.org/#callbackdef-eventlistener) #[derive(Clone, DenyPublicFields, JSTraceable, MallocSizeOf)] /// A listener in a collection of event listeners. struct EventListenerEntry { @@ -367,23 +369,13 @@ impl EventTarget { }) } - pub fn dispatch_event_with_target(&self, target: &EventTarget, event: &Event) -> EventStatus { - if let Some(window) = target.global().downcast::<Window>() { - if window.has_document() { - assert!(window.Document().can_invoke_script()); - } - }; - - event.dispatch(self, Some(target)) - } - pub fn dispatch_event(&self, event: &Event) -> EventStatus { if let Some(window) = self.global().downcast::<Window>() { if window.has_document() { assert!(window.Document().can_invoke_script()); } }; - event.dispatch(self, None) + event.dispatch(self, false) } pub fn remove_all_listeners(&self) { diff --git a/components/script/dom/htmlanchorelement.rs b/components/script/dom/htmlanchorelement.rs index 47aad647f41..00a3fb5a06f 100644 --- a/components/script/dom/htmlanchorelement.rs +++ b/components/script/dom/htmlanchorelement.rs @@ -542,13 +542,6 @@ impl Activatable for HTMLAnchorElement { self.as_element().has_attribute(&local_name!("href")) } - //TODO:https://html.spec.whatwg.org/multipage/#the-a-element - fn pre_click_activation(&self) {} - - //TODO:https://html.spec.whatwg.org/multipage/#the-a-element - // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps - fn canceled_activation(&self) {} - //https://html.spec.whatwg.org/multipage/#the-a-element:activation-behaviour fn activation_behavior(&self, event: &Event, target: &EventTarget) { let element = self.as_element(); diff --git a/components/script/dom/htmlareaelement.rs b/components/script/dom/htmlareaelement.rs index 85253339347..2e6549f5939 100644 --- a/components/script/dom/htmlareaelement.rs +++ b/components/script/dom/htmlareaelement.rs @@ -322,10 +322,6 @@ impl Activatable for HTMLAreaElement { self.as_element().has_attribute(&local_name!("href")) } - fn pre_click_activation(&self) {} - - fn canceled_activation(&self) {} - fn activation_behavior(&self, _event: &Event, _target: &EventTarget) { follow_hyperlink(self.as_element(), None); } diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index d088e286838..db5df824fe9 100755 --- a/components/script/dom/htmlbuttonelement.rs +++ b/components/script/dom/htmlbuttonelement.rs @@ -290,13 +290,6 @@ impl Activatable for HTMLButtonElement { !self.upcast::<Element>().disabled_state() } - // https://html.spec.whatwg.org/multipage/#run-pre-click-activation-steps - // https://html.spec.whatwg.org/multipage/#the-button-element:activation-behavior - fn pre_click_activation(&self) {} - - // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps - fn canceled_activation(&self) {} - // https://html.spec.whatwg.org/multipage/#run-post-click-activation-steps fn activation_behavior(&self, _event: &Event, _target: &EventTarget) { let ty = self.button_type.get(); diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index 28aa976002e..ac0f99416c2 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -2,7 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::dom::activation::{synthetic_click_activation, ActivationSource}; use crate::dom::attr::Attr; use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull; use crate::dom::bindings::codegen::Bindings::EventHandlerBinding::OnErrorEventHandlerNonNull; @@ -389,16 +388,18 @@ impl HTMLElementMethods for HTMLElement { // https://html.spec.whatwg.org/multipage/#dom-click fn Click(&self) { - if !self.upcast::<Element>().disabled_state() { - synthetic_click_activation( - self.upcast::<Element>(), - false, - false, - false, - false, - ActivationSource::FromClick, - ) + let element = self.upcast::<Element>(); + if element.disabled_state() { + return; } + if element.click_in_progress() { + return; + } + element.set_click_in_progress(true); + + self.upcast::<Node>() + .fire_synthetic_mouse_event_not_trusted(DOMString::from("click")); + element.set_click_in_progress(false); } // https://html.spec.whatwg.org/multipage/#dom-focus diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index a8a34a7357e..441206c9dad 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::dom::activation::{synthetic_click_activation, Activatable, ActivationSource}; +use crate::dom::activation::Activatable; use crate::dom::attr::Attr; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; @@ -11,12 +11,11 @@ use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods; use crate::dom::bindings::codegen::Bindings::HTMLFormElementBinding::SelectionMode; use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding; use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods; -use crate::dom::bindings::codegen::Bindings::KeyboardEventBinding::KeyboardEventMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods}; use crate::dom::bindings::error::{Error, ErrorResult}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::DomObject; -use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::compositionevent::CompositionEvent; use crate::dom::document::Document; @@ -246,7 +245,6 @@ pub struct HTMLInputElement { minlength: Cell<i32>, #[ignore_malloc_size_of = "#7193"] textinput: DomRefCell<TextInput<ScriptToConstellationChan>>, - activation_state: DomRefCell<InputActivationState>, // https://html.spec.whatwg.org/multipage/#concept-input-value-dirty-flag value_dirty: Cell<bool>, // not specified explicitly, but implied by the fact that sanitization can't @@ -260,30 +258,13 @@ pub struct HTMLInputElement { } #[derive(JSTraceable)] -#[unrooted_must_root_lint::must_root] -#[derive(MallocSizeOf)] -struct InputActivationState { +pub struct InputActivationState { indeterminate: bool, checked: bool, - checked_changed: bool, - checked_radio: Option<Dom<HTMLInputElement>>, - // In case mutability changed - was_mutable: bool, + checked_radio: Option<DomRoot<HTMLInputElement>>, // In case the type changed old_type: InputType, -} - -impl InputActivationState { - fn new() -> InputActivationState { - InputActivationState { - indeterminate: false, - checked: false, - checked_changed: false, - checked_radio: None, - was_mutable: false, - old_type: Default::default(), - } - } + // was_mutable is implied: pre-activation would return None if it wasn't } static DEFAULT_INPUT_SIZE: u32 = 20; @@ -323,7 +304,6 @@ impl HTMLInputElement { None, SelectionDirection::None, )), - activation_state: DomRefCell::new(InputActivationState::new()), value_dirty: Cell::new(false), sanitization_flag: Cell::new(true), filelist: MutNullableDom::new(None), @@ -1756,7 +1736,7 @@ impl HTMLInputElement { // https://html.spec.whatwg.org/multipage/#implicit-submission #[allow(unsafe_code)] - fn implicit_submission(&self, ctrl_key: bool, shift_key: bool, alt_key: bool, meta_key: bool) { + fn implicit_submission(&self) { let doc = document_from_node(self); let node = doc.upcast::<Node>(); let owner = self.form_owner(); @@ -1777,14 +1757,11 @@ impl HTMLInputElement { match submit_button { Some(ref button) => { if button.is_instance_activatable() { - synthetic_click_activation( - button.as_element(), - ctrl_key, - shift_key, - alt_key, - meta_key, - ActivationSource::NotFromClick, - ) + // spec does not actually say to set the not trusted flag, + // but we can get here from synthetic keydown events + button + .upcast::<Node>() + .fire_synthetic_mouse_event_not_trusted(DOMString::from("click")); } }, None => { @@ -2199,14 +2176,19 @@ impl VirtualMethods for HTMLInputElement { } } + // This represents behavior for which the UIEvents spec and the + // DOM/HTML specs are out of sync. + // Compare: + // https://w3c.github.io/uievents/#default-action + // https://dom.spec.whatwg.org/#action-versus-occurance fn handle_event(&self, event: &Event) { if let Some(s) = self.super_type() { s.handle_event(event); } if event.type_() == atom!("click") && !event.DefaultPrevented() { - // TODO: Dispatch events for non activatable inputs - // https://html.spec.whatwg.org/multipage/#common-input-element-events + // WHATWG-specified activation behaviors are handled elsewhere; + // this is for all the other things a UI click might do //TODO: set the editing position for text inputs @@ -2242,12 +2224,7 @@ impl VirtualMethods for HTMLInputElement { let action = self.textinput.borrow_mut().handle_keydown(keyevent); match action { TriggerDefaultAction => { - self.implicit_submission( - keyevent.CtrlKey(), - keyevent.ShiftKey(), - keyevent.AltKey(), - keyevent.MetaKey(), - ); + self.implicit_submission(); }, DispatchInput => { self.value_dirty.set(true); @@ -2365,91 +2342,90 @@ impl Activatable for HTMLInputElement { } } - // https://html.spec.whatwg.org/multipage/#run-pre-click-activation-steps - #[allow(unsafe_code)] - fn pre_click_activation(&self) { - let mut cache = self.activation_state.borrow_mut(); + // https://dom.spec.whatwg.org/#eventtarget-legacy-pre-activation-behavior + fn legacy_pre_activation_behavior(&self) -> Option<InputActivationState> { + if !self.is_mutable() { + return None; + } + let ty = self.input_type(); - cache.old_type = ty; - cache.was_mutable = self.is_mutable(); - if cache.was_mutable { - match ty { - // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit):activation-behavior - // InputType::Submit => (), // No behavior defined - // https://html.spec.whatwg.org/multipage/#reset-button-state-(type=reset):activation-behavior - // InputType::Submit => (), // No behavior defined - InputType::Checkbox => { - /* - https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox):pre-click-activation-steps - cache current values of `checked` and `indeterminate` - we may need to restore them later - */ - cache.indeterminate = self.Indeterminate(); - cache.checked = self.Checked(); - cache.checked_changed = self.checked_changed.get(); - self.SetIndeterminate(false); - self.SetChecked(!cache.checked); - }, - // https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio):pre-click-activation-steps - InputType::Radio => { - let checked_member = radio_group_iter(self, self.radio_group_name().as_ref()) - .find(|r| r.Checked()); - cache.checked_radio = checked_member.as_deref().map(Dom::from_ref); - cache.checked_changed = self.checked_changed.get(); - self.SetChecked(true); - }, - _ => (), - } + match ty { + InputType::Checkbox => { + let was_checked = self.Checked(); + let was_indeterminate = self.Indeterminate(); + self.SetIndeterminate(false); + self.SetChecked(!was_checked); + return Some(InputActivationState { + checked: was_checked, + indeterminate: was_indeterminate, + checked_radio: None, + old_type: InputType::Checkbox, + }); + }, + InputType::Radio => { + let checked_member = + radio_group_iter(self, self.radio_group_name().as_ref()).find(|r| r.Checked()); + let was_checked = self.Checked(); + self.SetChecked(true); + return Some(InputActivationState { + checked: was_checked, + indeterminate: false, + checked_radio: checked_member.as_deref().map(DomRoot::from_ref), + old_type: InputType::Radio, + }); + }, + _ => (), } + return None; } - // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps - fn canceled_activation(&self) { - let cache = self.activation_state.borrow(); - let ty = self.input_type(); - if cache.old_type != ty { - // Type changed, abandon ship - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27414 + // https://dom.spec.whatwg.org/#eventtarget-legacy-canceled-activation-behavior + fn legacy_canceled_activation_behavior(&self, cache: Option<InputActivationState>) { + // Step 1 + if !self.is_mutable() { return; } + let ty = self.input_type(); + let cache = match cache { + Some(cache) => { + if cache.old_type != ty { + // Type changed, abandon ship + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27414 + return; + } + cache + }, + None => { + return; + }, + }; + match ty { - // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit):activation-behavior - // InputType::Submit => (), // No behavior defined - // https://html.spec.whatwg.org/multipage/#reset-button-state-(type=reset):activation-behavior - // InputType::Reset => (), // No behavior defined - // https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox):canceled-activation-steps + // Step 2 InputType::Checkbox => { - // We want to restore state only if the element had been changed in the first place - if cache.was_mutable { - self.SetIndeterminate(cache.indeterminate); - self.SetChecked(cache.checked); - self.checked_changed.set(cache.checked_changed); - } + self.SetIndeterminate(cache.indeterminate); + self.SetChecked(cache.checked); }, - // https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio):canceled-activation-steps + // Step 3 InputType::Radio => { - // We want to restore state only if the element had been changed in the first place - if cache.was_mutable { - if let Some(ref o) = cache.checked_radio { - let tree_root = self - .upcast::<Node>() - .GetRootNode(&GetRootNodeOptions::empty()); - // Avoiding iterating through the whole tree here, instead - // we can check if the conditions for radio group siblings apply - if in_same_group( - &o, - self.form_owner().as_deref(), - self.radio_group_name().as_ref(), - Some(&*tree_root), - ) { - o.SetChecked(true); - } else { - self.SetChecked(false); - } + if let Some(ref o) = cache.checked_radio { + let tree_root = self + .upcast::<Node>() + .GetRootNode(&GetRootNodeOptions::empty()); + // Avoiding iterating through the whole tree here, instead + // we can check if the conditions for radio group siblings apply + if in_same_group( + &o, + self.form_owner().as_deref(), + self.radio_group_name().as_ref(), + Some(&*tree_root), + ) { + o.SetChecked(true); } else { self.SetChecked(false); } - self.checked_changed.set(cache.checked_changed); + } else { + self.SetChecked(false); } }, _ => (), @@ -2459,11 +2435,6 @@ impl Activatable for HTMLInputElement { // https://html.spec.whatwg.org/multipage/#run-post-click-activation-steps fn activation_behavior(&self, _event: &Event, _target: &EventTarget) { let ty = self.input_type(); - if self.activation_state.borrow().old_type != ty || !self.is_mutable() { - // Type changed or input is immutable, abandon ship - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27414 - return; - } match ty { InputType::Submit => { // https://html.spec.whatwg.org/multipage/#submit-button-state-(type=submit):activation-behavior diff --git a/components/script/dom/htmllabelelement.rs b/components/script/dom/htmllabelelement.rs index cca8e0f03c9..3953ed585f7 100644 --- a/components/script/dom/htmllabelelement.rs +++ b/components/script/dom/htmllabelelement.rs @@ -2,10 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::dom::activation::{synthetic_click_activation, Activatable, ActivationSource}; +use crate::dom::activation::Activatable; use crate::dom::attr::Attr; use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding; use crate::dom::bindings::codegen::Bindings::HTMLLabelElementBinding::HTMLLabelElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods}; @@ -65,25 +66,14 @@ impl Activatable for HTMLLabelElement { true } - // https://html.spec.whatwg.org/multipage/#run-pre-click-activation-steps - // https://html.spec.whatwg.org/multipage/#the-button-element:activation-behavior - fn pre_click_activation(&self) {} - - // https://html.spec.whatwg.org/multipage/#run-canceled-activation-steps - fn canceled_activation(&self) {} - - // https://html.spec.whatwg.org/multipage/#run-post-click-activation-steps + // https://html.spec.whatwg.org/multipage/#the-label-element:activation_behaviour + // Basically this is telling us that if activation bubbles up to the label + // at all, we are free to do an implementation-dependent thing; + // firing a click event is an example, and the precise details of that + // click event (e.g. isTrusted) are not specified. fn activation_behavior(&self, _event: &Event, _target: &EventTarget) { if let Some(e) = self.GetControl() { - let elem = e.upcast::<Element>(); - synthetic_click_activation( - elem, - false, - false, - false, - false, - ActivationSource::NotFromClick, - ); + e.Click(); } } } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index b6d4389d441..0081e997bb9 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -37,6 +37,7 @@ use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLD use crate::dom::documentfragment::DocumentFragment; use crate::dom::documenttype::DocumentType; use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator}; +use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlbodyelement::HTMLBodyElement; @@ -51,6 +52,7 @@ use crate::dom::htmlmediaelement::{HTMLMediaElement, LayoutHTMLMediaElementHelpe use crate::dom::htmlmetaelement::HTMLMetaElement; use crate::dom::htmlstyleelement::HTMLStyleElement; use crate::dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaElementHelpers}; +use crate::dom::mouseevent::MouseEvent; use crate::dom::mutationobserver::{Mutation, MutationObserver, RegisteredObserver}; use crate::dom::nodelist::NodeList; use crate::dom::processinginstruction::ProcessingInstruction; @@ -389,6 +391,46 @@ impl Node { } }) } + + // https://html.spec.whatg.org/#fire_a_synthetic_mouse_event + pub fn fire_synthetic_mouse_event_not_trusted(&self, name: DOMString) { + // Spec says the choice of which global to create + // the mouse event on is not well-defined, + // and refers to heycam/webidl#135 + let win = window_from_node(self); + + let mouse_event = MouseEvent::new( + &win, // ambiguous in spec + name, + EventBubbles::Bubbles, // Step 3: bubbles + EventCancelable::Cancelable, // Step 3: cancelable, + Some(&win), // Step 7: view (this is unambiguous in spec) + 0, // detail uninitialized + 0, // coordinates uninitialized + 0, // coordinates uninitialized + 0, // coordinates uninitialized + 0, // coordinates uninitialized + false, + false, + false, + false, // Step 6 modifier keys TODO compositor hook needed + 0, // button uninitialized (and therefore left) + 0, // buttons uninitialized (and therefore none) + None, // related_target uninitialized, + None, // point_in_target uninitialized, + ); + + // Step 4: TODO composed flag for shadow root + + // Step 5 + mouse_event.upcast::<Event>().set_trusted(false); + + // Step 8: TODO keyboard modifiers + + mouse_event + .upcast::<Event>() + .dispatch(self.upcast::<EventTarget>(), false); + } } pub struct QuerySelectorIterator { diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 208d7f1d87e..c1a6ba0c4c4 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -33,7 +33,7 @@ use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration use crate::dom::customelementregistry::CustomElementRegistry; use crate::dom::document::{AnimationFrameCallback, Document}; use crate::dom::element::Element; -use crate::dom::event::Event; +use crate::dom::event::{Event, EventStatus}; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::hashchangeevent::HashChangeEvent; @@ -528,6 +528,14 @@ impl Window { pub fn get_event_loop_waker(&self) -> Option<Box<dyn EventLoopWaker>> { self.event_loop_waker.as_ref().map(|w| (*w).clone_box()) } + + // see note at https://dom.spec.whatwg.org/#concept-event-dispatch step 2 + pub fn dispatch_event_with_target_override(&self, event: &Event) -> EventStatus { + if self.has_document() { + assert!(self.Document().can_invoke_script()); + } + event.dispatch(self.upcast(), true) + } } // https://html.spec.whatwg.org/multipage/#atob diff --git a/tests/wpt/metadata/dom/events/Event-dispatch-click.html.ini b/tests/wpt/metadata/dom/events/Event-dispatch-click.html.ini index 79e10b2df01..33709ceac1c 100644 --- a/tests/wpt/metadata/dom/events/Event-dispatch-click.html.ini +++ b/tests/wpt/metadata/dom/events/Event-dispatch-click.html.ini @@ -1,20 +1,4 @@ [Event-dispatch-click.html] type: testharness - expected: TIMEOUT - [basic with dispatchEvent()] - expected: FAIL - - [look at parents when event bubbles] - expected: FAIL - - [pick the first with activation behavior <input type=checkbox>] - expected: FAIL - - [pick the first with activation behavior <a href>] - expected: TIMEOUT - [event state during post-click handling] - expected: TIMEOUT - - [redispatch during post-click handling] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/metadata/dom/events/Event-dispatch-handlers-changed.html.ini b/tests/wpt/metadata/dom/events/Event-dispatch-handlers-changed.html.ini deleted file mode 100644 index 6d6806cdcb0..00000000000 --- a/tests/wpt/metadata/dom/events/Event-dispatch-handlers-changed.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[Event-dispatch-handlers-changed.html] - [ Dispatch additional events inside an event listener ] - expected: FAIL - diff --git a/tests/wpt/metadata/dom/events/Event-dispatch-order-at-target.html.ini b/tests/wpt/metadata/dom/events/Event-dispatch-order-at-target.html.ini deleted file mode 100644 index 38fe82e7126..00000000000 --- a/tests/wpt/metadata/dom/events/Event-dispatch-order-at-target.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[Event-dispatch-order-at-target.html] - [Listeners are invoked in correct order (AT_TARGET phase)] - expected: FAIL - diff --git a/tests/wpt/metadata/dom/events/EventTarget-dispatchEvent.html.ini b/tests/wpt/metadata/dom/events/EventTarget-dispatchEvent.html.ini index d0949d1c28c..cf214675bf9 100644 --- a/tests/wpt/metadata/dom/events/EventTarget-dispatchEvent.html.ini +++ b/tests/wpt/metadata/dom/events/EventTarget-dispatchEvent.html.ini @@ -30,6 +30,4 @@ [If the event's initialized flag is not set, an InvalidStateError must be thrown (WheelEvent).] expected: FAIL - [Capturing event listeners should be called before non-capturing ones] - expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/forms/the-button-element/button-click-submits.html.ini b/tests/wpt/metadata/html/semantics/forms/the-button-element/button-click-submits.html.ini deleted file mode 100644 index 680eb46f5d3..00000000000 --- a/tests/wpt/metadata/html/semantics/forms/the-button-element/button-click-submits.html.ini +++ /dev/null @@ -1,12 +0,0 @@ -[button-click-submits.html] - type: testharness - expected: TIMEOUT - [clicking a button by dispatching an event should trigger a submit (form connected)] - expected: TIMEOUT - - [clicking the child of a button by dispatching a bubbling event should trigger a submit] - expected: TIMEOUT - - [clicking the child of a button with .click() should trigger a submit] - expected: TIMEOUT - diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/checkbox-click-events.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/checkbox-click-events.html.ini deleted file mode 100644 index 56ce2b7b199..00000000000 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/checkbox-click-events.html.ini +++ /dev/null @@ -1,11 +0,0 @@ -[checkbox-click-events.html] - type: testharness - [clicking and preventDefaulting a checkbox causes the checkbox to be checked during the click handler but reverted] - expected: FAIL - - [a checkbox input emits click, input, change events in order after dispatching click event] - expected: FAIL - - [checkbox input respects cancel behavior on synthetic clicks] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/checkbox-detached-change-event.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/checkbox-detached-change-event.html.ini deleted file mode 100644 index e599b1b9465..00000000000 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/checkbox-detached-change-event.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[checkbox-detached-change-event.html] - expected: TIMEOUT - [This test will pass if <input type=checkbox> emits change events while detached from document.body] - expected: TIMEOUT - diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/checkbox.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/checkbox.html.ini deleted file mode 100644 index afdd08c6014..00000000000 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/checkbox.html.ini +++ /dev/null @@ -1,8 +0,0 @@ -[checkbox.html] - type: testharness - [canceled activation steps on unchecked checkbox] - expected: FAIL - - [canceled activation steps on unchecked checkbox (indeterminate=true in onclick)] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/radio-detached-change-event.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/radio-detached-change-event.html.ini deleted file mode 100644 index 2c7029fa510..00000000000 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/radio-detached-change-event.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[radio-detached-change-event.html] - expected: TIMEOUT - [This test will pass if <input type=radio> emits change events while detached from document.body] - expected: TIMEOUT - diff --git a/tests/wpt/metadata/html/semantics/forms/the-input-element/radio-input-cancel.html.ini b/tests/wpt/metadata/html/semantics/forms/the-input-element/radio-input-cancel.html.ini deleted file mode 100644 index 4bd841d6206..00000000000 --- a/tests/wpt/metadata/html/semantics/forms/the-input-element/radio-input-cancel.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[radio-input-cancel.html] - type: testharness - [radio input cancel behavior reverts state] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/forms/the-label-element/proxy-click-to-associated-element.html.ini b/tests/wpt/metadata/html/semantics/forms/the-label-element/proxy-click-to-associated-element.html.ini deleted file mode 100644 index 48cb159cdbb..00000000000 --- a/tests/wpt/metadata/html/semantics/forms/the-label-element/proxy-click-to-associated-element.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[proxy-click-to-associated-element.html] - type: testharness - [clicking a label that prevents the event's default should not proxy click events] - expected: FAIL - diff --git a/tests/wpt/metadata/html/webappapis/scripting/event-loops/task_microtask_ordering.html.ini b/tests/wpt/metadata/html/webappapis/scripting/event-loops/task_microtask_ordering.html.ini deleted file mode 100644 index f464468cd8c..00000000000 --- a/tests/wpt/metadata/html/webappapis/scripting/event-loops/task_microtask_ordering.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[task_microtask_ordering.html] - expected: TIMEOUT - [Level 1 bossfight (synthetic click)] - expected: TIMEOUT - diff --git a/tests/wpt/metadata/uievents/legacy-domevents-tests/approved/dispatchEvent.click.checkbox.html.ini b/tests/wpt/metadata/uievents/legacy-domevents-tests/approved/dispatchEvent.click.checkbox.html.ini deleted file mode 100644 index 7319314e7f2..00000000000 --- a/tests/wpt/metadata/uievents/legacy-domevents-tests/approved/dispatchEvent.click.checkbox.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[dispatchEvent.click.checkbox.html] - type: testharness - [Test Description: MouseEvent: Default action is performed when a synthetic click event is dispatched on a checkbox element] - expected: FAIL - diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 4b79e1a49ac..dfd2b9f8a5d 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -18848,7 +18848,7 @@ "testharness" ], "mozilla/event_dispatch_order.html": [ - "48513cfff42b8635eb8822a903e7e85250a7ac51", + "172ae368c707e695fc334df491d62c44dfb81566", "testharness" ], "mozilla/event_handler_syntax_error.html": [ diff --git a/tests/wpt/mozilla/tests/mozilla/event_dispatch_order.html b/tests/wpt/mozilla/tests/mozilla/event_dispatch_order.html index 48513cfff42..172ae368c70 100644 --- a/tests/wpt/mozilla/tests/mozilla/event_dispatch_order.html +++ b/tests/wpt/mozilla/tests/mozilla/event_dispatch_order.html @@ -1,5 +1,6 @@ <html> <head> +<title>Even in the AT_TARGET phase, capture handlers fire before bubble handlers.</title> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> </head> @@ -7,25 +8,33 @@ <div id="foo"></div> <script> test(function() { + var sawBubble = false; var sawCapture = false; var sawBubbleTwice = false; function handler(ev) { + // Added first, but it's a bubble so it shouldn't fire until + // after the capture. assert_equals(ev.eventPhase, ev.AT_TARGET); + assert_equals(sawCapture, true); assert_equals(sawBubble, false); - assert_equals(sawCapture, false); + assert_equals(sawBubbleTwice, false); sawBubble = true; } function handler2(ev) { + // Capture: this should fire before both bubbles assert_equals(ev.eventPhase, ev.AT_TARGET); - assert_equals(sawBubble, true); assert_equals(sawCapture, false); + assert_equals(sawBubble, false); + assert_equals(sawBubbleTwice, false); sawCapture = true; } function handler3(ev) { + // And this one fires last. assert_equals(ev.eventPhase, ev.AT_TARGET); - assert_equals(sawBubble, true); assert_equals(sawCapture, true); + assert_equals(sawBubble, true); + assert_equals(sawBubbleTwice, false); sawBubbleTwice = true; } |