Consider shadow dom when dispatching events (#34788)

* Implement EventTarget::get_the_parent

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Add spec steps to Event::init_event

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Rewrite Event::composedPath to be spec compliant

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Retarget EventTargets instead of Nodes

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Rewrite event dispatch/invocation to better match the spec

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Add spec comments to Event struct

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Don't traverse shadow roots when calculating an events path

We can't do this correctly yet, and assuming that an events
composed flag is never set is correct 99% of the time.

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Fix typo in event dispatch

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* fix comment

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Update WPT expectations

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* allow crown error

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* fmt

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Reduce item visibility where possible

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Simplify code a bit

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Fix Step 5.10 of Event::invoke

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Fix renamed method calls

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:
Simon Wülker 2025-01-07 22:22:16 +01:00 committed by GitHub
parent cd39b1de64
commit 270df6e263
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 728 additions and 293 deletions

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,6 @@ use libc::c_char;
use servo_atoms::Atom; use servo_atoms::Atom;
use servo_url::ServoUrl; use servo_url::ServoUrl;
use super::bindings::trace::HashMapTracedValues;
use crate::dom::beforeunloadevent::BeforeUnloadEvent; use crate::dom::beforeunloadevent::BeforeUnloadEvent;
use crate::dom::bindings::callback::{CallbackContainer, CallbackFunction, ExceptionHandling}; use crate::dom::bindings::callback::{CallbackContainer, CallbackFunction, ExceptionHandling};
use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::cell::DomRefCell;
@ -38,6 +37,9 @@ use crate::dom::bindings::codegen::Bindings::EventListenerBinding::EventListener
use crate::dom::bindings::codegen::Bindings::EventTargetBinding::{ use crate::dom::bindings::codegen::Bindings::EventTargetBinding::{
AddEventListenerOptions, EventListenerOptions, EventTargetMethods, AddEventListenerOptions, EventListenerOptions, EventTargetMethods,
}; };
use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::UnionTypes::{ use crate::dom::bindings::codegen::UnionTypes::{
AddEventListenerOptionsOrBoolean, EventListenerOptionsOrBoolean, EventOrString, AddEventListenerOptionsOrBoolean, EventListenerOptionsOrBoolean, EventOrString,
@ -47,12 +49,15 @@ use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector}; use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector};
use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString; use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::trace::HashMapTracedValues;
use crate::dom::document::Document;
use crate::dom::element::Element; use crate::dom::element::Element;
use crate::dom::errorevent::ErrorEvent; use crate::dom::errorevent::ErrorEvent;
use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus};
use crate::dom::globalscope::GlobalScope; use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlformelement::FormControlElementHelpers; use crate::dom::htmlformelement::FormControlElementHelpers;
use crate::dom::node::NodeTraits; use crate::dom::node::{Node, NodeTraits};
use crate::dom::shadowroot::ShadowRoot;
use crate::dom::virtualmethods::VirtualMethods; use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::window::Window; use crate::dom::window::Window;
use crate::dom::workerglobalscope::WorkerGlobalScope; use crate::dom::workerglobalscope::WorkerGlobalScope;
@ -781,6 +786,67 @@ impl EventTarget {
} }
} }
} }
/// <https://dom.spec.whatwg.org/#get-the-parent>
pub(crate) fn get_the_parent(&self, event: &Event) -> Option<DomRoot<EventTarget>> {
if let Some(document) = self.downcast::<Document>() {
if event.type_() == atom!("load") || !document.has_browsing_context() {
return None;
} else {
return Some(DomRoot::from_ref(document.window().upcast::<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(node) = self.downcast::<Node>() {
// FIXME: Handle slottables here
let parent = node.GetParentNode()?;
return Some(DomRoot::from_ref(parent.upcast::<EventTarget>()));
}
None
}
// FIXME: This algorithm operates on "objects", which may not be event targets.
// All our current use-cases only work on event targets, but this might change in the future
/// <https://dom.spec.whatwg.org/#retarget>
pub(crate) fn retarget(&self, b: &Self) -> DomRoot<EventTarget> {
// To retarget an object A against an object B, repeat these steps until they return an object:
let mut a = DomRoot::from_ref(self);
loop {
// Step 1. If one of the following is true
// * A is not a node
// * As root is not a shadow root
// * B is a node and As root is a shadow-including inclusive ancestor of B
let Some(a_node) = a.downcast::<Node>() else {
return a;
};
let a_root = a_node.GetRootNode(&GetRootNodeOptions::empty());
if !a_root.is::<ShadowRoot>() {
return a;
}
if let Some(b_node) = b.downcast::<Node>() {
if a_root.is_shadow_including_inclusive_ancestor_of(b_node) {
return a;
}
}
// Step 2. Set A to As roots host.
a = DomRoot::from_ref(
a_root
.downcast::<ShadowRoot>()
.unwrap()
.Host()
.upcast::<EventTarget>(),
);
}
}
} }
impl EventTargetMethods<crate::DomTypeHolder> for EventTarget { impl EventTargetMethods<crate::DomTypeHolder> for EventTarget {

View file

@ -612,6 +612,7 @@ impl Node {
self.flags.get().contains(NodeFlags::IS_IN_A_DOCUMENT_TREE) self.flags.get().contains(NodeFlags::IS_IN_A_DOCUMENT_TREE)
} }
/// Return true iff node's root is a shadow-root.
pub fn is_in_a_shadow_tree(&self) -> bool { pub fn is_in_a_shadow_tree(&self) -> bool {
self.flags.get().contains(NodeFlags::IS_IN_SHADOW_TREE) self.flags.get().contains(NodeFlags::IS_IN_SHADOW_TREE)
} }
@ -1281,27 +1282,6 @@ impl Node {
} }
} }
/// <https://dom.spec.whatwg.org/#retarget>
pub fn retarget(&self, b: &Node) -> DomRoot<Node> {
let mut a = DomRoot::from_ref(self);
loop {
// Step 1.
let a_root = a.GetRootNode(&GetRootNodeOptions::empty());
if !a_root.is::<ShadowRoot>() || a_root.is_shadow_including_inclusive_ancestor_of(b) {
return DomRoot::from_ref(&a);
}
// Step 2.
a = DomRoot::from_ref(
a_root
.downcast::<ShadowRoot>()
.unwrap()
.Host()
.upcast::<Node>(),
);
}
}
pub fn is_styled(&self) -> bool { pub fn is_styled(&self) -> bool {
self.style_data.borrow().is_some() self.style_data.borrow().is_some()
} }

View file

@ -28,6 +28,7 @@ use crate::dom::node::{
BindContext, Node, NodeDamage, NodeFlags, NodeTraits, ShadowIncluding, UnbindContext, BindContext, Node, NodeDamage, NodeFlags, NodeTraits, ShadowIncluding, UnbindContext,
}; };
use crate::dom::stylesheetlist::{StyleSheetList, StyleSheetListOwner}; use crate::dom::stylesheetlist::{StyleSheetList, StyleSheetListOwner};
use crate::dom::types::EventTarget;
use crate::dom::virtualmethods::VirtualMethods; use crate::dom::virtualmethods::VirtualMethods;
use crate::dom::window::Window; use crate::dom::window::Window;
use crate::script_runtime::CanGc; use crate::script_runtime::CanGc;
@ -218,7 +219,9 @@ impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {
can_gc, can_gc,
) { ) {
Some(e) => { Some(e) => {
let retargeted_node = self.upcast::<Node>().retarget(e.upcast::<Node>()); let retargeted_node = self
.upcast::<EventTarget>()
.retarget(e.upcast::<EventTarget>());
retargeted_node.downcast::<Element>().map(DomRoot::from_ref) retargeted_node.downcast::<Element>().map(DomRoot::from_ref)
}, },
None => None, None => None,
@ -240,7 +243,9 @@ impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {
.elements_from_point(x, y, None, self.document.has_browsing_context(), can_gc) .elements_from_point(x, y, None, self.document.has_browsing_context(), can_gc)
.iter() .iter()
{ {
let retargeted_node = self.upcast::<Node>().retarget(e.upcast::<Node>()); let retargeted_node = self
.upcast::<EventTarget>()
.retarget(e.upcast::<EventTarget>());
if let Some(element) = retargeted_node.downcast::<Element>().map(DomRoot::from_ref) { if let Some(element) = retargeted_node.downcast::<Element>().map(DomRoot::from_ref) {
elements.push(element); elements.push(element);
} }

View file

@ -1601,12 +1601,15 @@ impl Window {
window_named_properties::create(cx, proto, object) window_named_properties::create(cx, proto, object)
} }
pub(crate) fn set_current_event(&self, event: Option<&Event>) -> Option<DomRoot<Event>> { pub(crate) fn current_event(&self) -> Option<DomRoot<Event>> {
let current = self self.current_event
.current_event
.borrow() .borrow()
.as_ref() .as_ref()
.map(|e| DomRoot::from_ref(&**e)); .map(|e| DomRoot::from_ref(&**e))
}
pub(crate) fn set_current_event(&self, event: Option<&Event>) -> Option<DomRoot<Event>> {
let current = self.current_event();
*self.current_event.borrow_mut() = event.map(Dom::from_ref); *self.current_event.borrow_mut() = event.map(Dom::from_ref);
current current
} }

View file

@ -1,6 +0,0 @@
[Event-dispatch-click.html]
[event state during post-click handling]
expected: FAIL
[submit button should not activate if the event listener disables it]
expected: FAIL

View file

@ -1,8 +0,0 @@
[EventTarget-constructible.any.worker.html]
[A constructed EventTarget implements dispatch correctly]
expected: FAIL
[EventTarget-constructible.any.html]
[A constructed EventTarget implements dispatch correctly]
expected: FAIL

View file

@ -1,6 +1,7 @@
[event-global.html] [event-global.html]
expected: TIMEOUT
[window.event is undefined if the target is in a shadow tree (event dispatched inside shadow tree)] [window.event is undefined if the target is in a shadow tree (event dispatched inside shadow tree)]
expected: FAIL expected: TIMEOUT
[window.event is undefined inside window.onerror if the target is in a shadow tree (ErrorEvent dispatched inside shadow tree)] [window.event is undefined inside window.onerror if the target is in a shadow tree (ErrorEvent dispatched inside shadow tree)]
expected: FAIL expected: FAIL

View file

@ -1,7 +1,4 @@
[Extensions-to-Event-Interface.html] [Extensions-to-Event-Interface.html]
[composedPath() must return an empty array when the event is no longer dispatched]
expected: FAIL
[composed on EventInit must default to false] [composed on EventInit must default to false]
expected: FAIL expected: FAIL

View file

@ -1,28 +1,13 @@
[event-post-dispatch.html] [event-post-dispatch.html]
[Event properties post dispatch without ShadowRoots (composed: true).]
expected: FAIL
[Event properties post dispatch without ShadowRoots (composed: false).]
expected: FAIL
[Event properties post dispatch with an open ShadowRoot (composed: true).] [Event properties post dispatch with an open ShadowRoot (composed: true).]
expected: FAIL expected: FAIL
[Event properties post dispatch with an open ShadowRoot (composed: false).]
expected: FAIL
[Event properties post dispatch with a closed ShadowRoot (composed: true).] [Event properties post dispatch with a closed ShadowRoot (composed: true).]
expected: FAIL expected: FAIL
[Event properties post dispatch with a closed ShadowRoot (composed: false).]
expected: FAIL
[Event properties post dispatch with nested ShadowRoots (composed: true).] [Event properties post dispatch with nested ShadowRoots (composed: true).]
expected: FAIL expected: FAIL
[Event properties post dispatch with nested ShadowRoots (composed: false).]
expected: FAIL
[Event properties post dispatch with relatedTarget in the same shadow tree. (composed: true)] [Event properties post dispatch with relatedTarget in the same shadow tree. (composed: true)]
expected: FAIL expected: FAIL
@ -43,6 +28,3 @@
[Event properties post dispatch when target get moved out of the shadow tree by event listener] [Event properties post dispatch when target get moved out of the shadow tree by event listener]
expected: FAIL expected: FAIL
[Event properties post dispatch when target get moved into the the shadow tree by event listener]
expected: FAIL