Auto merge of #25488 - pshaughn:clickactivate, r=jdm

Event dispatch rewritten to align to spec, activate on clicks better

I went over the changes to the event dispatch spec that had accumulated over the past few years, rewriting dispatch/invoke/inner-invoke almost completely and modifying other code where it was relevant. Most of the remaining obvious deviations from spec are things that will only come up when we start handling events in shadow DOM.

I am pushing now because I want to see CI test results, but please do not approve this PR just if automated test improvements look good. I may have broken some actual UI interactions in the course of fixing synthetic events, and some manual testing is needed, including checking that manual interactions with interactive content continue to fire the events they're supposed to.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #25384 and fix #22783 and fix #25199

<!-- Either: -->
- [ ] There are automated tests for the synthetic-click parts of these changes, BUT the effects on real UI events need some manual testing before merging

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
bors-servo 2020-02-13 17:37:12 -05:00 committed by GitHub
commit e697e6cca7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 466 additions and 556 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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,
);
}),

View file

@ -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>()

View file

@ -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.
// 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 14.
self.status()
// 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 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 {
@ -205,18 +360,6 @@ impl Event {
self.dispatching.get()
}
#[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.
// Steps 2-3.
let listeners = object.get_listeners_for(&event.type_(), specific_listener_phase);
// Step 2 TODO: relatedTarget only matters for shadow DOM
// 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.

View file

@ -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) {

View file

@ -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();

View file

@ -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);
}

View file

@ -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();

View file

@ -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

View file

@ -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,71 +2342,72 @@ 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();
let was_checked = self.Checked();
let was_indeterminate = self.Indeterminate();
self.SetIndeterminate(false);
self.SetChecked(!cache.checked);
self.SetChecked(!was_checked);
return Some(InputActivationState {
checked: was_checked,
indeterminate: was_indeterminate,
checked_radio: None,
old_type: InputType::Checkbox,
});
},
// 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();
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();
// 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);
}
},
// 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>()
@ -2449,8 +2427,6 @@ impl Activatable for HTMLInputElement {
} else {
self.SetChecked(false);
}
self.checked_changed.set(cache.checked_changed);
}
},
_ => (),
}
@ -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

View file

@ -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();
}
}
}

View file

@ -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 {

View file

@ -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

View file

@ -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

View file

@ -1,4 +0,0 @@
[Event-dispatch-handlers-changed.html]
[ Dispatch additional events inside an event listener ]
expected: FAIL

View file

@ -1,4 +0,0 @@
[Event-dispatch-order-at-target.html]
[Listeners are invoked in correct order (AT_TARGET phase)]
expected: FAIL

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,5 +0,0 @@
[radio-input-cancel.html]
type: testharness
[radio input cancel behavior reverts state]
expected: FAIL

View file

@ -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

View file

@ -1,5 +0,0 @@
[task_microtask_ordering.html]
expected: TIMEOUT
[Level 1 bossfight (synthetic click)]
expected: TIMEOUT

View file

@ -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

View file

@ -18848,7 +18848,7 @@
"testharness"
],
"mozilla/event_dispatch_order.html": [
"48513cfff42b8635eb8822a903e7e85250a7ac51",
"172ae368c707e695fc334df491d62c44dfb81566",
"testharness"
],
"mozilla/event_handler_syntax_error.html": [

View file

@ -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;
}