From 3ac226e841406d6d6f8316e5271a25367c2f2ee8 Mon Sep 17 00:00:00 2001 From: Euclid Ye Date: Thu, 28 Aug 2025 12:31:06 +0800 Subject: [PATCH] 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 --- components/script/dom/htmlelement.rs | 2 +- components/script/dom/node.rs | 2 +- components/script/webdriver_handlers.rs | 34 +++++++++++++------ components/servo/tests/webview.rs | 10 ++++++ components/shared/embedder/lib.rs | 1 + components/shared/embedder/webdriver.rs | 1 + components/webdriver_server/lib.rs | 5 +++ .../execute_async_script/arguments.py.ini | 3 -- .../classic/execute_async_script/node.py.ini | 9 ----- .../classic/execute_script/arguments.py.ini | 3 -- .../tests/classic/execute_script/node.py.ini | 9 ----- .../classic/get_element_property/get.py.ini | 3 -- 12 files changed, 43 insertions(+), 39 deletions(-) diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index d0683607609..14629f60d7a 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -617,7 +617,7 @@ impl HTMLElementMethods 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 diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index e4d36023d9b..a0cbe563780 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -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::(); let shadow_root_mode = maybe_shadow_root diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index 49f6a66b61b..b97fcba4700 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -95,6 +95,13 @@ fn is_stale(element: &Element) -> bool { !element.owner_document().is_active() || !element.is_connected() } +/// +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()) +} + /// 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::(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::(*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::().unique_id( - element.owner_document().window().pipeline_id(), - ))) + Ok(JSValue::Element( + element + .upcast::() + .unique_id(element.owner_window().pipeline_id()), + )) + } + } else if let Ok(shadow_root) = root_from_object::(*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::() + .unique_id(shadow_root.owner_window().pipeline_id()), + )) } } else if let Ok(window) = root_from_object::(*object, cx) { let window_proxy = window.window_proxy(); diff --git a/components/servo/tests/webview.rs b/components/servo/tests/webview.rs index 5457946f7cf..84f44c34ad8 100644 --- a/components/servo/tests/webview.rs +++ b/components/servo/tests/webview.rs @@ -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(), diff --git a/components/shared/embedder/lib.rs b/components/shared/embedder/lib.rs index ac500519768..2042f8d4eaa 100644 --- a/components/shared/embedder/lib.rs +++ b/components/shared/embedder/lib.rs @@ -1047,6 +1047,7 @@ pub enum JSValue { Number(f64), String(String), Element(String), + ShadowRoot(String), Frame(String), Window(String), Array(Vec), diff --git a/components/shared/embedder/webdriver.rs b/components/shared/embedder/webdriver.rs index f40af46d039..da0f73f2e7b 100644 --- a/components/shared/embedder/webdriver.rs +++ b/components/shared/embedder/webdriver.rs @@ -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, diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index 1090fcf982b..b99c7c1b878 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -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, "")) }, diff --git a/tests/wpt/meta/webdriver/tests/classic/execute_async_script/arguments.py.ini b/tests/wpt/meta/webdriver/tests/classic/execute_async_script/arguments.py.ini index aac667cc691..5f1d34e6018 100644 --- a/tests/wpt/meta/webdriver/tests/classic/execute_async_script/arguments.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/execute_async_script/arguments.py.ini @@ -35,8 +35,5 @@ [test_element_reference[frame\]] expected: FAIL - [test_element_reference[shadow-root\]] - expected: FAIL - [test_element_reference[window\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/execute_async_script/node.py.ini b/tests/wpt/meta/webdriver/tests/classic/execute_async_script/node.py.ini index 0d4bc418417..847fea98bbf 100644 --- a/tests/wpt/meta/webdriver/tests/classic/execute_async_script/node.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/execute_async_script/node.py.ini @@ -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 diff --git a/tests/wpt/meta/webdriver/tests/classic/execute_script/arguments.py.ini b/tests/wpt/meta/webdriver/tests/classic/execute_script/arguments.py.ini index aac667cc691..5f1d34e6018 100644 --- a/tests/wpt/meta/webdriver/tests/classic/execute_script/arguments.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/execute_script/arguments.py.ini @@ -35,8 +35,5 @@ [test_element_reference[frame\]] expected: FAIL - [test_element_reference[shadow-root\]] - expected: FAIL - [test_element_reference[window\]] expected: FAIL diff --git a/tests/wpt/meta/webdriver/tests/classic/execute_script/node.py.ini b/tests/wpt/meta/webdriver/tests/classic/execute_script/node.py.ini index 67480399962..847fea98bbf 100644 --- a/tests/wpt/meta/webdriver/tests/classic/execute_script/node.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/execute_script/node.py.ini @@ -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 diff --git a/tests/wpt/meta/webdriver/tests/classic/get_element_property/get.py.ini b/tests/wpt/meta/webdriver/tests/classic/get_element_property/get.py.ini index 4844164d09c..bb9de0071f4 100644 --- a/tests/wpt/meta/webdriver/tests/classic/get_element_property/get.py.ini +++ b/tests/wpt/meta/webdriver/tests/classic/get_element_property/get.py.ini @@ -1,6 +1,3 @@ [get.py] [test_no_browsing_context] expected: FAIL - - [test_web_reference[shadowRoot-ShadowRoot\]] - expected: FAIL