script: Support decomposing ShadowRoot from mozjs HandleValue (#38984)

- Add `ShadowRoot` to `JSValue` to avoid
`WebDriverJSError::UnknownType`, and
`JavaScriptEvaluationError::SerializationError` when execute JS from
embedder.
- Add unit test.
- Move [is_detached](https://w3c.github.io/webdriver/#dfn-is-detached)
to `fn is_detached` to be reused.
- Other random simplification.

Testing: WebDriver conformance tests.

---------

Signed-off-by: Euclid Ye <euclid.ye@huawei.com>
This commit is contained in:
Euclid Ye 2025-08-28 12:31:06 +08:00 committed by GitHub
parent 5308228436
commit 3ac226e841
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 43 additions and 39 deletions

View file

@ -617,7 +617,7 @@ impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement {
// Note: the element can pass this check without yet being a custom
// element, as long as there is a registered definition
// that could upgrade it to one later.
let registry = self.owner_document().window().CustomElements();
let registry = self.owner_window().CustomElements();
let definition = registry.lookup_definition(self.as_element().local_name(), None);
// Step 3: If definition is null, then throw an "NotSupportedError" DOMException

View file

@ -1282,7 +1282,7 @@ impl Node {
pub(crate) fn summarize(&self, can_gc: CanGc) -> NodeInfo {
let USVString(base_uri) = self.BaseURI();
let node_type = self.NodeType();
let pipeline = self.owner_document().window().pipeline_id();
let pipeline = self.owner_window().pipeline_id();
let maybe_shadow_root = self.downcast::<ShadowRoot>();
let shadow_root_mode = maybe_shadow_root

View file

@ -95,6 +95,13 @@ fn is_stale(element: &Element) -> bool {
!element.owner_document().is_active() || !element.is_connected()
}
/// <https://w3c.github.io/webdriver/#dfn-is-detached>
fn is_detached(shadow_root: &ShadowRoot) -> bool {
// A shadow root is detached if its node document is not the active document
// or if the element node referred to as its host is stale.
!shadow_root.owner_document().is_active() || is_stale(&shadow_root.Host())
}
/// <https://w3c.github.io/webdriver/#dfn-disabled>
fn is_disabled(element: &Element) -> bool {
// Step 1. If element is an option element or element is an optgroup element
@ -173,12 +180,7 @@ fn get_known_shadow_root(
// A shadow root is detached if its node document is not the active document
// or if the element node referred to as its host is stale.
let shadow_root = DomRoot::downcast::<ShadowRoot>(node).unwrap();
if !shadow_root.owner_document().is_active() {
return Err(ErrorStatus::DetachedShadowRoot);
}
let host = shadow_root.Host();
if is_stale(&host) {
if is_detached(&shadow_root) {
return Err(ErrorStatus::DetachedShadowRoot);
}
// Step 5. Return success with data node.
@ -396,15 +398,27 @@ unsafe fn jsval_to_webdriver_inner(
});
let _ac = JSAutoRealm::new(cx, *object);
// TODO: special handling for ShadowRoot
if let Ok(element) = root_from_object::<Element>(*object, cx) {
// If the element is stale, return error with error code stale element reference.
if is_stale(&element) {
Err(WebDriverJSError::StaleElementReference)
} else {
Ok(JSValue::Element(element.upcast::<Node>().unique_id(
element.owner_document().window().pipeline_id(),
)))
Ok(JSValue::Element(
element
.upcast::<Node>()
.unique_id(element.owner_window().pipeline_id()),
))
}
} else if let Ok(shadow_root) = root_from_object::<ShadowRoot>(*object, cx) {
// If the shadow root is detached, return error with error code detached shadow root.
if is_detached(&shadow_root) {
Err(WebDriverJSError::DetachedShadowRoot)
} else {
Ok(JSValue::ShadowRoot(
shadow_root
.upcast::<Node>()
.unique_id(shadow_root.owner_window().pipeline_id()),
))
}
} else if let Ok(window) = root_from_object::<Window>(*object, cx) {
let window_proxy = window.window_proxy();

View file

@ -80,6 +80,16 @@ fn test_evaluate_javascript_basic(servo_test: &ServoTest) -> Result<(), anyhow::
let result = evaluate_javascript(servo_test, webview.clone(), "document.body");
ensure!(matches!(result, Ok(JSValue::Element(..))));
let result = evaluate_javascript(
servo_test,
webview.clone(),
"document.body.attachShadow({mode: 'open'})",
);
ensure!(matches!(result, Ok(JSValue::ShadowRoot(..))));
let result = evaluate_javascript(servo_test, webview.clone(), "document.body.shadowRoot");
ensure!(matches!(result, Ok(JSValue::ShadowRoot(..))));
let result = evaluate_javascript(
servo_test,
webview.clone(),

View file

@ -1047,6 +1047,7 @@ pub enum JSValue {
Number(f64),
String(String),
Element(String),
ShadowRoot(String),
Frame(String),
Window(String),
Array(Vec<JSValue>),

View file

@ -256,6 +256,7 @@ pub enum WebDriverJSError {
/// Occurs when handler received an event message for a layout channel that is not
/// associated with the current script thread
BrowsingContextNotFound,
DetachedShadowRoot,
JSException(JSValue),
JSError,
StaleElementReference,

View file

@ -260,6 +260,7 @@ impl Serialize for SendableJSValue {
JSValue::Number(x) => serializer.serialize_f64(x),
JSValue::String(ref x) => serializer.serialize_str(x),
JSValue::Element(ref x) => WebElement(x.clone()).serialize(serializer),
JSValue::ShadowRoot(ref x) => ShadowRoot(x.clone()).serialize(serializer),
JSValue::Frame(ref x) => WebFrame(x.clone()).serialize(serializer),
JSValue::Window(ref x) => WebWindow(x.clone()).serialize(serializer),
JSValue::Array(ref x) => x
@ -2066,6 +2067,10 @@ impl Handler {
ErrorStatus::StaleElementReference,
"Stale element",
)),
Err(WebDriverJSError::DetachedShadowRoot) => Err(WebDriverError::new(
ErrorStatus::DetachedShadowRoot,
"Detached shadow root",
)),
Err(WebDriverJSError::Timeout) => {
Err(WebDriverError::new(ErrorStatus::ScriptTimeout, ""))
},

View file

@ -35,8 +35,5 @@
[test_element_reference[frame\]]
expected: FAIL
[test_element_reference[shadow-root\]]
expected: FAIL
[test_element_reference[window\]]
expected: FAIL

View file

@ -1,13 +1,4 @@
[node.py]
[test_detached_shadow_root[top_context\]]
expected: FAIL
[test_detached_shadow_root[child_context\]]
expected: FAIL
[test_element_reference[shadow-root\]]
expected: FAIL
[test_not_supported_nodes[attribute\]]
expected: FAIL

View file

@ -35,8 +35,5 @@
[test_element_reference[frame\]]
expected: FAIL
[test_element_reference[shadow-root\]]
expected: FAIL
[test_element_reference[window\]]
expected: FAIL

View file

@ -1,13 +1,4 @@
[node.py]
[test_detached_shadow_root[top_context\]]
expected: FAIL
[test_detached_shadow_root[child_context\]]
expected: FAIL
[test_web_reference[shadow-root\]]
expected: FAIL
[test_not_supported_nodes[attribute\]]
expected: FAIL

View file

@ -1,6 +1,3 @@
[get.py]
[test_no_browsing_context]
expected: FAIL
[test_web_reference[shadowRoot-ShadowRoot\]]
expected: FAIL