mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Implement Event propagation across shadow roots (#34884)
* Implement Event.composed flag Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Allow composed events to pass shadow boundaries Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> * Update WPT expectations Signed-off-by: Simon Wülker <simon.wuelker@arcor.de> --------- Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
parent
aed7e8cefd
commit
1b882f2729
16 changed files with 89 additions and 144 deletions
|
@ -506,6 +506,7 @@ impl Animations {
|
|||
let parent = EventInit {
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
composed: false,
|
||||
};
|
||||
|
||||
let property_or_animation_name =
|
||||
|
|
|
@ -73,6 +73,9 @@ pub(crate) struct Event {
|
|||
/// <https://dom.spec.whatwg.org/#dom-event-bubbles>
|
||||
bubbles: Cell<bool>,
|
||||
|
||||
/// <https://dom.spec.whatwg.org/#dom-event-composed>
|
||||
composed: Cell<bool>,
|
||||
|
||||
/// <https://dom.spec.whatwg.org/#dom-event-istrusted>
|
||||
is_trusted: Cell<bool>,
|
||||
|
||||
|
@ -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<Event> {
|
||||
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)
|
||||
}
|
||||
|
||||
/// <https://dom.spec.whatwg.org/#inner-event-creation-steps>
|
||||
fn inner_creation_steps(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
init: &EventBinding::EventInit,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<Event> {
|
||||
// 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::<Node>() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
&*target_node.GetRootNode(&GetRootNodeOptions::empty()) != shadow_root.upcast::<Node>()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventMethods<crate::DomTypeHolder> for Event {
|
||||
/// <https://dom.spec.whatwg.org/#dom-event-event>
|
||||
/// <https://dom.spec.whatwg.org/#concept-event-constructor>
|
||||
fn Constructor(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
|
@ -633,16 +697,15 @@ impl EventMethods<crate::DomTypeHolder> for Event {
|
|||
type_: DOMString,
|
||||
init: &EventBinding::EventInit,
|
||||
) -> Fallible<DomRoot<Event>> {
|
||||
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)
|
||||
}
|
||||
|
||||
/// <https://dom.spec.whatwg.org/#dom-event-eventphase>
|
||||
|
@ -805,6 +868,11 @@ impl EventMethods<crate::DomTypeHolder> for Event {
|
|||
self.canceled.get() == EventDefault::Prevented
|
||||
}
|
||||
|
||||
/// <https://dom.spec.whatwg.org/#dom-event-composed>
|
||||
fn Composed(&self) -> bool {
|
||||
self.composed.get()
|
||||
}
|
||||
|
||||
/// <https://dom.spec.whatwg.org/#dom-event-preventdefault>
|
||||
fn PreventDefault(&self) {
|
||||
if self.cancelable.get() {
|
||||
|
|
|
@ -805,11 +805,13 @@ impl EventTarget {
|
|||
}
|
||||
}
|
||||
|
||||
if self.downcast::<ShadowRoot>().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::<ShadowRoot>() {
|
||||
if event.should_pass_shadow_boundary(shadow_root) {
|
||||
let host = shadow_root.Host();
|
||||
return Some(DomRoot::from_ref(host.upcast::<EventTarget>()));
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(node) = self.downcast::<Node>() {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[event-global-extra.window.html]
|
||||
[window.event should not be affected by nodes moving post-dispatch]
|
||||
expected: FAIL
|
|
@ -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
|
||||
|
|
9
tests/wpt/meta/dom/idlharness.any.js.ini
vendored
9
tests/wpt/meta/dom/idlharness.any.js.ini
vendored
|
@ -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
|
||||
|
||||
|
|
12
tests/wpt/meta/dom/idlharness.window.js.ini
vendored
12
tests/wpt/meta/dom/idlharness.window.js.ini
vendored
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue