Files
servo/components/webdriver_server/script_argument_extraction.rs
Euclid Ye bf42488da8 webdriver: Report error instead of panic for invalid WebElement&ShadowRoot reference (#39976)
It is possible that the reference of `WebElement` and `ShadowRoot` in
the request is not String. Instead of panic, we should return "invalid
argument" same as the `WebWindow` and `WebFrame`.

Testing: Added 4 new subtests. There was only tests for `WebWindow` and
`WebFrame` somehow.

---------

Signed-off-by: Euclid <yezhizhenjiakang@gmail.com>
2025-10-18 10:44:05 +00:00

182 lines
8.3 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use base::id::BrowsingContextId;
use embedder_traits::WebDriverScriptCommand;
use ipc_channel::ipc;
use serde_json::Value;
use webdriver::command::JavascriptCommandParameters;
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
use crate::{Handler, VerifyBrowsingContextIsOpen, wait_for_ipc_response};
/// <https://w3c.github.io/webdriver/#dfn-web-element-identifier>
const ELEMENT_IDENTIFIER: &str = "element-6066-11e4-a52e-4f735466cecf";
/// <https://w3c.github.io/webdriver/#dfn-web-frame-identifier>
const FRAME_IDENTIFIER: &str = "frame-075b-4da1-b6ba-e579c2d3230a";
/// <https://w3c.github.io/webdriver/#dfn-web-window-identifier>
const WINDOW_IDENTIFIER: &str = "window-fcc6-11e5-b4f8-330a88ab9d7f";
/// <https://w3c.github.io/webdriver/#dfn-shadow-root-identifier>
const SHADOW_ROOT_IDENTIFIER: &str = "shadow-6066-11e4-a52e-4f735466cecf";
impl Handler {
/// <https://w3c.github.io/webdriver/#dfn-extract-the-script-arguments-from-a-request>
pub(crate) fn extract_script_arguments(
&self,
parameters: JavascriptCommandParameters,
) -> WebDriverResult<(String, Vec<String>)> {
// Step 1. Let script be the result of getting a property named "script" from parameters
// Step 2. (Done) If script is not a String, return error with error code invalid argument.
let script = parameters.script;
// Step 3. Let args be the result of getting a property named "args" from parameters.
// Step 4. (Done) If args is not an Array return error with error code invalid argument.
// Step 5. Let `arguments` be JSON deserialize with session and args.
let args: Vec<String> = parameters
.args
.as_deref()
.unwrap_or(&[])
.iter()
.map(|value| self.json_deserialize(value))
.collect::<WebDriverResult<Vec<_>>>()?;
Ok((script, args))
}
/// <https://w3c.github.io/webdriver/#dfn-deserialize-a-web-element>
fn deserialize_web_element(&self, element: &Value) -> WebDriverResult<String> {
// Step 2. Let reference be the result of getting the web element identifier property from object.
let element_ref = match element {
Value::String(string) => string.clone(),
_ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument, "")),
};
// Step 3. Let element be the result of trying to get a known element with session and reference.
let (sender, receiver) = ipc::channel().unwrap();
self.browsing_context_script_command(
WebDriverScriptCommand::GetKnownElement(element_ref.clone(), sender),
VerifyBrowsingContextIsOpen::No,
)?;
match wait_for_ipc_response(receiver)? {
// Step 4. Return success with data element.
Ok(_) => Ok(format!("window.webdriverElement(\"{}\")", element_ref)),
Err(err) => Err(WebDriverError::new(err, "No such element")),
}
}
/// <https://w3c.github.io/webdriver/#dfn-deserialize-a-shadow-root>
fn deserialize_shadow_root(&self, shadow_root: &Value) -> WebDriverResult<String> {
// Step 2. Let reference be the result of getting the shadow root identifier property from object.
let shadow_root_ref = match shadow_root {
Value::String(string) => string.clone(),
_ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument, "")),
};
// Step 3. Let element be the result of trying to get a known element with session and reference.
let (sender, receiver) = ipc::channel().unwrap();
self.browsing_context_script_command(
WebDriverScriptCommand::GetKnownShadowRoot(shadow_root_ref.clone(), sender),
VerifyBrowsingContextIsOpen::No,
)?;
match wait_for_ipc_response(receiver)? {
// Step 4. Return success with data element.
Ok(_) => Ok(format!(
"window.webdriverShadowRoot(\"{}\")",
shadow_root_ref
)),
Err(err) => Err(WebDriverError::new(err, "No such shadowroot")),
}
}
/// <https://w3c.github.io/webdriver/#dfn-deserialize-a-web-frame>
fn deserialize_web_frame(&self, frame: &Value) -> WebDriverResult<String> {
// Step 2. Let reference be the result of getting the web frame identifier property from object.
let frame_ref = match frame {
Value::String(string) => string.clone(),
_ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument, "")),
};
// Step 3. Let browsing context be the browsing context whose window handle is reference,
// or null if no such browsing context exists.
let Some(browsing_context_id) = BrowsingContextId::from_string(&frame_ref) else {
// Step 4. If browsing context is null or a top-level browsing context,
// return error with error code no such frame.
return Err(WebDriverError::new(ErrorStatus::NoSuchFrame, ""));
};
match self.verify_browsing_context_is_open(browsing_context_id) {
// Step 5. Return success with data browsing context's associated window.
Ok(_) => Ok(format!("window.webdriverFrame(\"{frame_ref}\")")),
// Part of Step 4.
Err(_) => Err(WebDriverError::new(ErrorStatus::NoSuchFrame, "")),
}
}
/// <https://w3c.github.io/webdriver/#dfn-deserialize-a-web-window>
fn deserialize_web_window(&self, window: &Value) -> WebDriverResult<String> {
// Step 2. Let reference be the result of getting the web window identifier property from object.
let window_ref = match window {
Value::String(string) => string.clone(),
_ => return Err(WebDriverError::new(ErrorStatus::InvalidArgument, "")),
};
// Step 3. Let browsing context be the browsing context whose window handle is reference,
// or null if no such browsing context exists.
let (sender, receiver) = ipc::channel().unwrap();
self.browsing_context_script_command(
WebDriverScriptCommand::GetKnownWindow(window_ref.clone(), sender),
VerifyBrowsingContextIsOpen::No,
)?;
match wait_for_ipc_response(receiver)? {
// Step 5. Return success with data browsing context's associated window.
Ok(_) => Ok(format!("window.webdriverWindow(\"{window_ref}\")")),
// Step 4. If browsing context is null or not a top-level browsing context,
// return error with error code no such window.
Err(err) => Err(WebDriverError::new(err, "No such window")),
}
}
/// <https://w3c.github.io/webdriver/#dfn-json-deserialize>
fn json_deserialize(&self, v: &Value) -> WebDriverResult<String> {
let res = match v {
Value::Array(list) => {
let elems = list
.iter()
.map(|v| self.json_deserialize(v))
.collect::<WebDriverResult<Vec<_>>>()?;
format!("[{}]", elems.join(", "))
},
Value::Object(map) => {
if let Some(id) = map.get(ELEMENT_IDENTIFIER) {
return self.deserialize_web_element(id);
}
if let Some(id) = map.get(SHADOW_ROOT_IDENTIFIER) {
return self.deserialize_shadow_root(id);
}
if let Some(id) = map.get(FRAME_IDENTIFIER) {
return self.deserialize_web_frame(id);
}
if let Some(id) = map.get(WINDOW_IDENTIFIER) {
return self.deserialize_web_window(id);
}
let elems = map
.iter()
.map(|(k, v)| {
let key = serde_json::to_string(k)?;
let arg = self.json_deserialize(v)?;
Ok(format!("{key}: {arg}"))
})
.collect::<WebDriverResult<Vec<String>>>()?;
format!("{{{}}}", elems.join(", "))
},
_ => serde_json::to_string(v)?,
};
Ok(res)
}
}