diff --git a/components/script/animations.rs b/components/script/animations.rs index ec430eb374a..f73400c343a 100644 --- a/components/script/animations.rs +++ b/components/script/animations.rs @@ -506,6 +506,7 @@ impl Animations { let parent = EventInit { bubbles: true, cancelable: false, + composed: false, }; let property_or_animation_name = diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index dbcb973da59..599847c23ae 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -73,6 +73,9 @@ pub(crate) struct Event { /// bubbles: Cell, + /// + composed: Cell, + /// is_trusted: Cell, @@ -129,6 +132,7 @@ impl Event { stop_immediate_propagation: Cell::new(false), cancelable: Cell::new(false), bubbles: Cell::new(false), + composed: Cell::new(false), is_trusted: Cell::new(false), dispatch: Cell::new(false), initialized: Cell::new(false), @@ -169,6 +173,8 @@ impl Event { can_gc: CanGc, ) -> DomRoot { let event = Event::new_uninitialized_with_proto(global, proto, can_gc); + + // NOTE: The spec doesn't tell us to call init event here, it just happens to do what we need. event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); event } @@ -622,10 +628,68 @@ impl Event { self.set_trusted(true); target.dispatch_event(self, can_gc) } + + /// + fn inner_creation_steps( + global: &GlobalScope, + proto: Option, + init: &EventBinding::EventInit, + can_gc: CanGc, + ) -> DomRoot { + // Step 1. Let event be the result of creating a new object using eventInterface. + // If realm is non-null, then use that realm; otherwise, use the default behavior defined in Web IDL. + let event = Event::new_uninitialized_with_proto(global, proto, can_gc); + + // Step 2. Set event’s initialized flag. + event.initialized.set(true); + + // Step 3. Initialize event’s timeStamp attribute to the relative high resolution + // coarse time given time and event’s relevant global object. + // NOTE: This is done inside Event::new_inherited + + // Step 3. For each member → value in dictionary, if event has an attribute whose + // identifier is member, then initialize that attribute to value.# + event.bubbles.set(init.bubbles); + event.cancelable.set(init.cancelable); + event.composed.set(init.composed); + + // Step 5. Run the event constructing steps with event and dictionary. + // NOTE: Event construction steps may be defined by subclasses + + // Step 6. Return event. + event + } + + /// Implements the logic behind the [get the parent](https://dom.spec.whatwg.org/#get-the-parent) + /// algorithm for shadow roots. + pub(crate) fn should_pass_shadow_boundary(&self, shadow_root: &ShadowRoot) -> bool { + debug_assert!(self.dispatching()); + + // > A shadow root’s get the parent algorithm, given an event, returns null if event’s composed flag + // > is unset and shadow root is the root of event’s path’s first struct’s invocation target; + // > otherwise shadow root’s host. + if self.Composed() { + return true; + } + + let path = self.path.borrow(); + let first_invocation_target = &path + .first() + .expect("Event path is empty despite event currently being dispatched") + .invocation_target + .as_rooted(); + + // The spec doesn't tell us what should happen if the invocation target is not a node + let Some(target_node) = first_invocation_target.downcast::() else { + return false; + }; + + &*target_node.GetRootNode(&GetRootNodeOptions::empty()) != shadow_root.upcast::() + } } impl EventMethods for Event { - /// + /// fn Constructor( global: &GlobalScope, proto: Option, @@ -633,16 +697,15 @@ impl EventMethods for Event { type_: DOMString, init: &EventBinding::EventInit, ) -> Fallible> { - let bubbles = EventBubbles::from(init.bubbles); - let cancelable = EventCancelable::from(init.cancelable); - Ok(Event::new_with_proto( - global, - proto, - Atom::from(type_), - bubbles, - cancelable, - can_gc, - )) + // Step 1. Let event be the result of running the inner event creation steps with + // this interface, null, now, and eventInitDict. + let event = Event::inner_creation_steps(global, proto, init, can_gc); + + // Step 2. Initialize event’s type attribute to type. + *event.type_.borrow_mut() = Atom::from(type_); + + // Step 3. Return event. + Ok(event) } /// @@ -805,6 +868,11 @@ impl EventMethods for Event { self.canceled.get() == EventDefault::Prevented } + /// + fn Composed(&self) -> bool { + self.composed.get() + } + /// fn PreventDefault(&self) { if self.cancelable.get() { diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs index b5404890160..bdd5a4b4f45 100644 --- a/components/script/dom/eventtarget.rs +++ b/components/script/dom/eventtarget.rs @@ -805,11 +805,13 @@ impl EventTarget { } } - if self.downcast::().is_some() { - // FIXME: Handle event composed flag here - // We currently assume that events are never composed (so events may never - // cross a shadow boundary) - return None; + if let Some(shadow_root) = self.downcast::() { + if event.should_pass_shadow_boundary(shadow_root) { + let host = shadow_root.Host(); + return Some(DomRoot::from_ref(host.upcast::())); + } else { + return None; + } } if let Some(node) = self.downcast::() { diff --git a/components/script/dom/webidls/Event.webidl b/components/script/dom/webidls/Event.webidl index 0fd7d2ba881..bc5c3d2abdd 100644 --- a/components/script/dom/webidls/Event.webidl +++ b/components/script/dom/webidls/Event.webidl @@ -34,6 +34,7 @@ interface Event { undefined preventDefault(); [Pure] readonly attribute boolean defaultPrevented; + readonly attribute boolean composed; [LegacyUnforgeable] readonly attribute boolean isTrusted; @@ -46,4 +47,5 @@ interface Event { dictionary EventInit { boolean bubbles = false; boolean cancelable = false; + boolean composed = false; }; diff --git a/tests/wpt/meta/dom/events/event-global-extra.window.js.ini b/tests/wpt/meta/dom/events/event-global-extra.window.js.ini deleted file mode 100644 index 629e31384ff..00000000000 --- a/tests/wpt/meta/dom/events/event-global-extra.window.js.ini +++ /dev/null @@ -1,3 +0,0 @@ -[event-global-extra.window.html] - [window.event should not be affected by nodes moving post-dispatch] - expected: FAIL diff --git a/tests/wpt/meta/dom/events/event-global.html.ini b/tests/wpt/meta/dom/events/event-global.html.ini index 953ec3d4727..3b073f1a9d6 100644 --- a/tests/wpt/meta/dom/events/event-global.html.ini +++ b/tests/wpt/meta/dom/events/event-global.html.ini @@ -1,7 +1,3 @@ [event-global.html] - expected: TIMEOUT - [window.event is undefined if the target is in a shadow tree (event dispatched inside shadow tree)] - expected: TIMEOUT - [window.event is undefined inside window.onerror if the target is in a shadow tree (ErrorEvent dispatched inside shadow tree)] expected: FAIL diff --git a/tests/wpt/meta/dom/idlharness.any.js.ini b/tests/wpt/meta/dom/idlharness.any.js.ini index fd0ba94f88d..c75d2d801bf 100644 --- a/tests/wpt/meta/dom/idlharness.any.js.ini +++ b/tests/wpt/meta/dom/idlharness.any.js.ini @@ -5,15 +5,6 @@ expected: ERROR [idlharness.any.worker.html] - [Event interface: attribute composed] - expected: FAIL - - [Event interface: new Event("foo") must inherit property "composed" with the proper type] - expected: FAIL - - [Event interface: new CustomEvent("foo") must inherit property "composed" with the proper type] - expected: FAIL - [AbortController interface: attribute signal] expected: FAIL diff --git a/tests/wpt/meta/dom/idlharness.window.js.ini b/tests/wpt/meta/dom/idlharness.window.js.ini index 011ac74fab1..fc0798643ed 100644 --- a/tests/wpt/meta/dom/idlharness.window.js.ini +++ b/tests/wpt/meta/dom/idlharness.window.js.ini @@ -8,9 +8,6 @@ [AbortSignal must be primary interface of new AbortController().signal] expected: FAIL - [Event interface: attribute composed] - expected: FAIL - [AbortSignal interface: existence and properties of interface prototype object's @@unscopables property] expected: FAIL @@ -32,9 +29,6 @@ [AbortSignal interface object name] expected: FAIL - [Event interface: new CustomEvent("foo") must inherit property "composed" with the proper type] - expected: FAIL - [EventTarget interface: new AbortController().signal must inherit property "removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))" with the proper type] expected: FAIL @@ -116,9 +110,6 @@ [AbortSignal interface: existence and properties of interface prototype object] expected: FAIL - [Event interface: new Event("foo") must inherit property "composed" with the proper type] - expected: FAIL - [Element interface: operation remove()] expected: FAIL @@ -146,9 +137,6 @@ [Document interface: operation append((Node or DOMString)...)] expected: FAIL - [Event interface: document.createEvent("Event") must inherit property "composed" with the proper type] - expected: FAIL - [DocumentType interface: operation remove()] expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/Extensions-to-Event-Interface.html.ini b/tests/wpt/meta/shadow-dom/Extensions-to-Event-Interface.html.ini index e20872ee901..011b5d6a886 100644 --- a/tests/wpt/meta/shadow-dom/Extensions-to-Event-Interface.html.ini +++ b/tests/wpt/meta/shadow-dom/Extensions-to-Event-Interface.html.ini @@ -1,16 +1,4 @@ [Extensions-to-Event-Interface.html] - [composed on EventInit must default to false] - expected: FAIL - - [composed on EventInit must set the composed flag] - expected: FAIL - - [The event must propagate out of open mode shadow boundaries when the composed flag is set] - expected: FAIL - - [The event must propagate out of closed mode shadow boundaries when the composed flag is set] - expected: FAIL - [The event must not propagate out of open mode shadow tree of the target but must propagate out of inner shadow trees when the scoped flag is set] expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html.ini b/tests/wpt/meta/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html.ini index 19b257357ac..157ea88abf1 100644 --- a/tests/wpt/meta/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html.ini +++ b/tests/wpt/meta/shadow-dom/capturing-and-bubbling-event-listeners-across-shadow-trees.html.ini @@ -1,10 +1,4 @@ [capturing-and-bubbling-event-listeners-across-shadow-trees.html] - [Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a shadow tree] - expected: FAIL - - [Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a doubly nested shadow tree] - expected: FAIL - [Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched via a slot] expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/event-composed-path-after-dom-mutation.html.ini b/tests/wpt/meta/shadow-dom/event-composed-path-after-dom-mutation.html.ini deleted file mode 100644 index c86edd6f045..00000000000 --- a/tests/wpt/meta/shadow-dom/event-composed-path-after-dom-mutation.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[event-composed-path-after-dom-mutation.html] - expected: TIMEOUT - [Event.composedPath() should return the same result even if DOM is mutated (1/2)] - expected: TIMEOUT - - [Event.composedPath() should return the same result even if DOM is mutated (2/2)] - expected: TIMEOUT diff --git a/tests/wpt/meta/shadow-dom/event-composed-path.html.ini b/tests/wpt/meta/shadow-dom/event-composed-path.html.ini index f55f38269be..13babc698bc 100644 --- a/tests/wpt/meta/shadow-dom/event-composed-path.html.ini +++ b/tests/wpt/meta/shadow-dom/event-composed-path.html.ini @@ -1,22 +1,4 @@ [event-composed-path.html] - [Event Path with an open ShadowRoot.] - expected: FAIL - - [Event Path with a closed ShadowRoot.] - expected: FAIL - - [Event Path with nested ShadowRoots: open > open.] - expected: FAIL - - [Event Path with nested ShadowRoots: open > closed.] - expected: FAIL - - [Event Path with nested ShadowRoots: closed > open.] - expected: FAIL - - [Event Path with nested ShadowRoots: closed > closed.] - expected: FAIL - [Event Path with a slot in an open Shadow Root.] expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/event-composed.html.ini b/tests/wpt/meta/shadow-dom/event-composed.html.ini index 1e389d5e84a..73650923859 100644 --- a/tests/wpt/meta/shadow-dom/event-composed.html.ini +++ b/tests/wpt/meta/shadow-dom/event-composed.html.ini @@ -1,13 +1,4 @@ [event-composed.html] - [A new events composed value should be set to false by default.] - expected: FAIL - - [Users should be able to set a composed value.] - expected: FAIL - - [An event should not be scoped if composed is specified] - expected: FAIL - [A synthetic MouseEvent with composed=true should not be scoped] expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/event-dispatch-order.tentative.html.ini b/tests/wpt/meta/shadow-dom/event-dispatch-order.tentative.html.ini deleted file mode 100644 index 576acf2527b..00000000000 --- a/tests/wpt/meta/shadow-dom/event-dispatch-order.tentative.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[event-dispatch-order.tentative.html] - [Event dispatch order: capture listerns should be called in capturing phase at a shadow host] - expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/event-inside-shadow-tree.html.ini b/tests/wpt/meta/shadow-dom/event-inside-shadow-tree.html.ini deleted file mode 100644 index ded94bb1e10..00000000000 --- a/tests/wpt/meta/shadow-dom/event-inside-shadow-tree.html.ini +++ /dev/null @@ -1,36 +0,0 @@ -[event-inside-shadow-tree.html] - [Firing an event inside a grand child of a detached open mode shadow tree] - expected: FAIL - - [Firing an event inside a grand child of a detached closed mode shadow tree] - expected: FAIL - - [Firing an event inside a grand child of an in-document open mode shadow tree] - expected: FAIL - - [Firing an event inside a grand child of an in-document closed mode shadow tree] - expected: FAIL - - [Firing an event inside a detached open mode shadow tree inside open mode shadow tree] - expected: FAIL - - [Firing an event inside a detached open mode shadow tree inside closed mode shadow tree] - expected: FAIL - - [Firing an event inside a detached closed mode shadow tree inside open mode shadow tree] - expected: FAIL - - [Firing an event inside a detached closed mode shadow tree inside closed mode shadow tree] - expected: FAIL - - [Firing an event inside an in-document open mode shadow tree inside open mode shadow tree] - expected: FAIL - - [Firing an event inside an in-document open mode shadow tree inside closed mode shadow tree] - expected: FAIL - - [Firing an event inside an in-document closed mode shadow tree inside open mode shadow tree] - expected: FAIL - - [Firing an event inside an in-document closed mode shadow tree inside closed mode shadow tree] - expected: FAIL diff --git a/tests/wpt/meta/shadow-dom/event-post-dispatch.html.ini b/tests/wpt/meta/shadow-dom/event-post-dispatch.html.ini index 5cba2f9849e..b7ea654323c 100644 --- a/tests/wpt/meta/shadow-dom/event-post-dispatch.html.ini +++ b/tests/wpt/meta/shadow-dom/event-post-dispatch.html.ini @@ -1,13 +1,4 @@ [event-post-dispatch.html] - [Event properties post dispatch with an open ShadowRoot (composed: true).] - expected: FAIL - - [Event properties post dispatch with a closed ShadowRoot (composed: true).] - expected: FAIL - - [Event properties post dispatch with nested ShadowRoots (composed: true).] - expected: FAIL - [Event properties post dispatch with relatedTarget in the same shadow tree. (composed: true)] expected: FAIL