aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2020-02-13 17:37:12 -0500
committerGitHub <noreply@github.com>2020-02-13 17:37:12 -0500
commite697e6cca70bec57b5ed9512b95c2a72359d6ca1 (patch)
tree99fa07d2761b0ff18f7c166b8f0a98e1fc8b513e /components/script
parent9c135f0e9554bd78bb7a432a872257e2cae13a48 (diff)
parent01aba1fcc453192da64272dcc180135ce11e4ea7 (diff)
downloadservo-e697e6cca70bec57b5ed9512b95c2a72359d6ca1.tar.gz
servo-e697e6cca70bec57b5ed9512b95c2a72359d6ca1.zip
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. -->
Diffstat (limited to 'components/script')
-rw-r--r--components/script/dom/activation.rs94
-rw-r--r--components/script/dom/document.rs40
-rw-r--r--components/script/dom/element.rs48
-rw-r--r--components/script/dom/event.rs379
-rw-r--r--components/script/dom/eventtarget.rs16
-rw-r--r--components/script/dom/htmlanchorelement.rs7
-rw-r--r--components/script/dom/htmlareaelement.rs4
-rwxr-xr-xcomponents/script/dom/htmlbuttonelement.rs7
-rw-r--r--components/script/dom/htmlelement.rs21
-rwxr-xr-xcomponents/script/dom/htmlinputelement.rs209
-rw-r--r--components/script/dom/htmllabelelement.rs26
-rw-r--r--components/script/dom/node.rs42
-rw-r--r--components/script/dom/window.rs10
13 files changed, 442 insertions, 461 deletions
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