webdriver: Reuse JSValue as WebDriverJSValue (#38751)

After #38748, `WebDriverJSValue` is almost same as `JSValue`. Now we
turn "potentially merge into one in the future" into reality. The only
thing we should be cautious is to properly serialize `WebFrame`,
`WebWindow`, `WebElement` for WebDriver.

Testing: No regression. Some error is fixed previously by #38709 which
didn't update test :)
Binary size reduced by 134KB.

---------

Signed-off-by: Euclid Ye <euclid.ye@huawei.com>
This commit is contained in:
Euclid Ye 2025-08-19 01:38:48 +08:00 committed by GitHub
parent 7471ad7730
commit ec5872992b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 57 additions and 101 deletions

View file

@ -3979,16 +3979,14 @@ impl ScriptThread {
return; return;
}; };
let result = match jsval_to_webdriver( let result = jsval_to_webdriver(
context, context,
global_scope, global_scope,
return_value.handle(), return_value.handle(),
(&realm).into(), (&realm).into(),
can_gc, can_gc,
) { )
Ok(ref value) => Ok(value.into()), .map_err(|_| JavaScriptEvaluationError::SerializationError);
Err(_) => Err(JavaScriptEvaluationError::SerializationError),
};
let _ = self.senders.pipeline_to_constellation_sender.send(( let _ = self.senders.pipeline_to_constellation_sender.send((
pipeline_id, pipeline_id,

View file

@ -9,7 +9,7 @@ use std::ptr::NonNull;
use base::id::{BrowsingContextId, PipelineId}; use base::id::{BrowsingContextId, PipelineId};
use cookie::Cookie; use cookie::Cookie;
use embedder_traits::{ use embedder_traits::{
WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, JSValue, WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverLoadStatus,
}; };
use euclid::default::{Point2D, Rect, Size2D}; use euclid::default::{Point2D, Rect, Size2D};
use hyper_serde::Serde; use hyper_serde::Serde;
@ -31,7 +31,6 @@ use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMeth
use script_bindings::conversions::is_array_like; use script_bindings::conversions::is_array_like;
use script_bindings::num::Finite; use script_bindings::num::Finite;
use servo_url::ServoUrl; use servo_url::ServoUrl;
use webdriver::common::{WebElement, WebFrame, WebWindow};
use webdriver::error::ErrorStatus; use webdriver::error::ErrorStatus;
use crate::document_collection::DocumentCollection; use crate::document_collection::DocumentCollection;
@ -364,13 +363,13 @@ unsafe fn jsval_to_webdriver_inner(
) -> WebDriverJSResult { ) -> WebDriverJSResult {
let _ac = enter_realm(global_scope); let _ac = enter_realm(global_scope);
if val.get().is_undefined() { if val.get().is_undefined() {
Ok(WebDriverJSValue::Undefined) Ok(JSValue::Undefined)
} else if val.get().is_null() { } else if val.get().is_null() {
Ok(WebDriverJSValue::Null) Ok(JSValue::Null)
} else if val.get().is_boolean() { } else if val.get().is_boolean() {
Ok(WebDriverJSValue::Boolean(val.get().to_boolean())) Ok(JSValue::Boolean(val.get().to_boolean()))
} else if val.get().is_number() { } else if val.get().is_number() {
Ok(WebDriverJSValue::Number( Ok(JSValue::Number(
match FromJSValConvertible::from_jsval(cx, val, ()).unwrap() { match FromJSValConvertible::from_jsval(cx, val, ()).unwrap() {
ConversionResult::Success(c) => c, ConversionResult::Success(c) => c,
_ => unreachable!(), _ => unreachable!(),
@ -385,7 +384,7 @@ unsafe fn jsval_to_webdriver_inner(
ConversionResult::Success(c) => c, ConversionResult::Success(c) => c,
_ => unreachable!(), _ => unreachable!(),
}; };
Ok(WebDriverJSValue::String(String::from(string))) Ok(JSValue::String(String::from(string)))
} }
// https://w3c.github.io/webdriver/#dfn-clone-an-object // https://w3c.github.io/webdriver/#dfn-clone-an-object
else if val.get().is_object() { else if val.get().is_object() {
@ -406,7 +405,7 @@ unsafe fn jsval_to_webdriver_inner(
let return_val = if is_array_like::<crate::DomTypeHolder>(cx, val) || let return_val = if is_array_like::<crate::DomTypeHolder>(cx, val) ||
is_arguments_object(cx, val) is_arguments_object(cx, val)
{ {
let mut result: Vec<WebDriverJSValue> = Vec::new(); let mut result: Vec<JSValue> = Vec::new();
let length = match get_property::<u32>( let length = match get_property::<u32>(
cx, cx,
@ -449,12 +448,10 @@ unsafe fn jsval_to_webdriver_inner(
}, },
} }
} }
Ok(WebDriverJSValue::ArrayLike(result)) Ok(JSValue::Array(result))
} else if let Ok(element) = root_from_object::<Element>(*object, cx) { } else if let Ok(element) = root_from_object::<Element>(*object, cx) {
Ok(WebDriverJSValue::Element(WebElement( Ok(JSValue::Element(element.upcast::<Node>().unique_id(
element element.owner_document().window().pipeline_id(),
.upcast::<Node>()
.unique_id(element.owner_document().window().pipeline_id()),
))) )))
} else if let Ok(window) = root_from_object::<Window>(*object, cx) { } else if let Ok(window) = root_from_object::<Window>(*object, cx) {
let window_proxy = window.window_proxy(); let window_proxy = window.window_proxy();
@ -463,13 +460,13 @@ unsafe fn jsval_to_webdriver_inner(
} else { } else {
let pipeline = window.pipeline_id(); let pipeline = window.pipeline_id();
if window_proxy.browsing_context_id() == window_proxy.webview_id() { if window_proxy.browsing_context_id() == window_proxy.webview_id() {
Ok(WebDriverJSValue::Window(WebWindow( Ok(JSValue::Window(
window.Document().upcast::<Node>().unique_id(pipeline), window.Document().upcast::<Node>().unique_id(pipeline),
))) ))
} else { } else {
Ok(WebDriverJSValue::Frame(WebFrame( Ok(JSValue::Frame(
window.Document().upcast::<Node>().unique_id(pipeline), window.Document().upcast::<Node>().unique_id(pipeline),
))) ))
} }
} }
} else if object_has_to_json_property(cx, global_scope, object.handle()) { } else if object_has_to_json_property(cx, global_scope, object.handle()) {
@ -547,7 +544,7 @@ unsafe fn jsval_to_webdriver_inner(
} }
} }
} }
Ok(WebDriverJSValue::Object(result)) Ok(JSValue::Object(result))
}; };
// Step 5. Remove the last element of `seen`. // Step 5. Remove the last element of `seen`.
seen.remove(&hashable); seen.remove(&hashable);
@ -1648,7 +1645,7 @@ pub(crate) fn handle_get_property(
pipeline: PipelineId, pipeline: PipelineId,
node_id: String, node_id: String,
name: String, name: String,
reply: IpcSender<Result<WebDriverJSValue, ErrorStatus>>, reply: IpcSender<Result<JSValue, ErrorStatus>>,
can_gc: CanGc, can_gc: CanGc,
) { ) {
reply reply
@ -1676,12 +1673,12 @@ pub(crate) fn handle_get_property(
can_gc, can_gc,
) { ) {
Ok(property) => property, Ok(property) => property,
Err(_) => WebDriverJSValue::Undefined, Err(_) => JSValue::Undefined,
} }
}, },
Err(error) => { Err(error) => {
throw_dom_exception(cx, &element.global(), error, can_gc); throw_dom_exception(cx, &element.global(), error, can_gc);
WebDriverJSValue::Undefined JSValue::Undefined
}, },
} }
}), }),

View file

@ -1003,29 +1003,6 @@ pub enum JSValue {
Object(HashMap<String, JSValue>), Object(HashMap<String, JSValue>),
} }
impl From<&WebDriverJSValue> for JSValue {
fn from(value: &WebDriverJSValue) -> Self {
match value {
WebDriverJSValue::Undefined => Self::Undefined,
WebDriverJSValue::Null => Self::Null,
WebDriverJSValue::Boolean(value) => Self::Boolean(*value),
WebDriverJSValue::Number(value) => Self::Number(*value),
WebDriverJSValue::String(value) => Self::String(value.clone()),
WebDriverJSValue::Element(web_element) => Self::Element(web_element.0.clone()),
WebDriverJSValue::Frame(web_frame) => Self::Frame(web_frame.0.clone()),
WebDriverJSValue::Window(web_window) => Self::Window(web_window.0.clone()),
WebDriverJSValue::ArrayLike(vector) => {
Self::Array(vector.iter().map(Into::into).collect())
},
WebDriverJSValue::Object(map) => Self::Object(
map.iter()
.map(|(key, value)| (key.clone(), value.into()))
.collect(),
),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum JavaScriptEvaluationError { pub enum JavaScriptEvaluationError {
/// The script could not be compiled /// The script could not be compiled

View file

@ -19,11 +19,10 @@ use serde::{Deserialize, Serialize};
use servo_geometry::DeviceIndependentIntRect; use servo_geometry::DeviceIndependentIntRect;
use servo_url::ServoUrl; use servo_url::ServoUrl;
use style_traits::CSSPixel; use style_traits::CSSPixel;
use webdriver::common::{WebElement, WebFrame, WebWindow};
use webdriver::error::ErrorStatus; use webdriver::error::ErrorStatus;
use webrender_api::units::DevicePixel; use webrender_api::units::DevicePixel;
use crate::{FocusId, MouseButton, MouseButtonAction, TraversalId}; use crate::{FocusId, JSValue, MouseButton, MouseButtonAction, TraversalId};
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct WebDriverMessageId(pub usize); pub struct WebDriverMessageId(pub usize);
@ -226,11 +225,7 @@ pub enum WebDriverScriptCommand {
String, String,
IpcSender<Result<Option<String>, ErrorStatus>>, IpcSender<Result<Option<String>, ErrorStatus>>,
), ),
GetElementProperty( GetElementProperty(String, String, IpcSender<Result<JSValue, ErrorStatus>>),
String,
String,
IpcSender<Result<WebDriverJSValue, ErrorStatus>>,
),
GetElementCSS(String, String, IpcSender<Result<String, ErrorStatus>>), GetElementCSS(String, String, IpcSender<Result<String, ErrorStatus>>),
GetElementRect(String, IpcSender<Result<UntypedRect<f64>, ErrorStatus>>), GetElementRect(String, IpcSender<Result<UntypedRect<f64>, ErrorStatus>>),
GetElementTagName(String, IpcSender<Result<String, ErrorStatus>>), GetElementTagName(String, IpcSender<Result<String, ErrorStatus>>),
@ -254,33 +249,19 @@ pub enum WebDriverScriptCommand {
GetWindowHandle(IpcSender<Result<String, ErrorStatus>>), GetWindowHandle(IpcSender<Result<String, ErrorStatus>>),
} }
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum WebDriverJSValue {
Undefined,
Null,
Boolean(bool),
Number(f64),
String(String),
Element(WebElement),
Frame(WebFrame),
Window(WebWindow),
ArrayLike(Vec<WebDriverJSValue>),
Object(HashMap<String, WebDriverJSValue>),
}
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub enum WebDriverJSError { pub enum WebDriverJSError {
/// Occurs when handler received an event message for a layout channel that is not /// Occurs when handler received an event message for a layout channel that is not
/// associated with the current script thread /// associated with the current script thread
BrowsingContextNotFound, BrowsingContextNotFound,
JSException(WebDriverJSValue), JSException(JSValue),
JSError, JSError,
StaleElementReference, StaleElementReference,
Timeout, Timeout,
UnknownType, UnknownType,
} }
pub type WebDriverJSResult = Result<WebDriverJSValue, WebDriverJSError>; pub type WebDriverJSResult = Result<JSValue, WebDriverJSError>;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub enum WebDriverFrameId { pub enum WebDriverFrameId {

View file

@ -27,8 +27,8 @@ use capabilities::ServoCapabilities;
use cookie::{CookieBuilder, Expiration, SameSite}; use cookie::{CookieBuilder, Expiration, SameSite};
use crossbeam_channel::{Receiver, Sender, after, select, unbounded}; use crossbeam_channel::{Receiver, Sender, after, select, unbounded};
use embedder_traits::{ use embedder_traits::{
EventLoopWaker, MouseButton, WebDriverCommandMsg, WebDriverCommandResponse, WebDriverFrameId, EventLoopWaker, JSValue, MouseButton, WebDriverCommandMsg, WebDriverCommandResponse,
WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverLoadStatus, WebDriverMessageId, WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverLoadStatus, WebDriverMessageId,
WebDriverScriptCommand, WebDriverScriptCommand,
}; };
use euclid::{Point2D, Rect, Size2D}; use euclid::{Point2D, Rect, Size2D};
@ -60,7 +60,9 @@ use webdriver::command::{
SwitchToFrameParameters, SwitchToWindowParameters, TimeoutsParameters, WebDriverCommand, SwitchToFrameParameters, SwitchToWindowParameters, TimeoutsParameters, WebDriverCommand,
WebDriverExtensionCommand, WebDriverMessage, WindowRectParameters, WebDriverExtensionCommand, WebDriverMessage, WindowRectParameters,
}; };
use webdriver::common::{Cookie, Date, LocatorStrategy, Parameters, ShadowRoot, WebElement}; use webdriver::common::{
Cookie, Date, LocatorStrategy, Parameters, ShadowRoot, WebElement, WebFrame, WebWindow,
};
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult}; use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
use webdriver::httpapi::WebDriverExtensionRoute; use webdriver::httpapi::WebDriverExtensionRoute;
use webdriver::response::{ use webdriver::response::{
@ -294,31 +296,31 @@ impl WebDriverExtensionCommand for ServoExtensionCommand {
} }
#[derive(Clone)] #[derive(Clone)]
struct SendableWebDriverJSValue(pub WebDriverJSValue); struct SendableJSValue(pub JSValue);
impl Serialize for SendableWebDriverJSValue { impl Serialize for SendableJSValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
match self.0 { match self.0 {
WebDriverJSValue::Undefined => serializer.serialize_unit(), JSValue::Undefined => serializer.serialize_unit(),
WebDriverJSValue::Null => serializer.serialize_unit(), JSValue::Null => serializer.serialize_unit(),
WebDriverJSValue::Boolean(x) => serializer.serialize_bool(x), JSValue::Boolean(x) => serializer.serialize_bool(x),
WebDriverJSValue::Number(x) => serializer.serialize_f64(x), JSValue::Number(x) => serializer.serialize_f64(x),
WebDriverJSValue::String(ref x) => serializer.serialize_str(x), JSValue::String(ref x) => serializer.serialize_str(x),
WebDriverJSValue::Element(ref x) => x.serialize(serializer), JSValue::Element(ref x) => WebElement(x.clone()).serialize(serializer),
WebDriverJSValue::Frame(ref x) => x.serialize(serializer), JSValue::Frame(ref x) => WebFrame(x.clone()).serialize(serializer),
WebDriverJSValue::Window(ref x) => x.serialize(serializer), JSValue::Window(ref x) => WebWindow(x.clone()).serialize(serializer),
WebDriverJSValue::ArrayLike(ref x) => x JSValue::Array(ref x) => x
.iter() .iter()
.map(|element| SendableWebDriverJSValue(element.clone())) .map(|element| SendableJSValue(element.clone()))
.collect::<Vec<SendableWebDriverJSValue>>() .collect::<Vec<SendableJSValue>>()
.serialize(serializer), .serialize(serializer),
WebDriverJSValue::Object(ref x) => x JSValue::Object(ref x) => x
.iter() .iter()
.map(|(k, v)| (k.clone(), SendableWebDriverJSValue(v.clone()))) .map(|(k, v)| (k.clone(), SendableJSValue(v.clone())))
.collect::<HashMap<String, SendableWebDriverJSValue>>() .collect::<HashMap<String, SendableJSValue>>()
.serialize(serializer), .serialize(serializer),
} }
} }
@ -1748,7 +1750,7 @@ impl Handler {
match wait_for_ipc_response(receiver)? { match wait_for_ipc_response(receiver)? {
Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(SendableWebDriverJSValue(value))?, serde_json::to_value(SendableJSValue(value))?,
))), ))),
Err(error) => Err(WebDriverError::new(error, "")), Err(error) => Err(WebDriverError::new(error, "")),
} }
@ -2100,7 +2102,7 @@ impl Handler {
) -> WebDriverResult<WebDriverResponse> { ) -> WebDriverResult<WebDriverResponse> {
match result { match result {
Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(SendableWebDriverJSValue(value))?, serde_json::to_value(SendableJSValue(value))?,
))), ))),
Err(WebDriverJSError::BrowsingContextNotFound) => Err(WebDriverError::new( Err(WebDriverJSError::BrowsingContextNotFound) => Err(WebDriverError::new(
ErrorStatus::NoSuchWindow, ErrorStatus::NoSuchWindow,

View file

@ -20,10 +20,9 @@ use servo::webrender_api::ScrollLocation;
use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize}; use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize};
use servo::{ use servo::{
AllowOrDenyRequest, AuthenticationRequest, FilterPattern, FocusId, FormControl, AllowOrDenyRequest, AuthenticationRequest, FilterPattern, FocusId, FormControl,
GamepadHapticEffectType, KeyboardEvent, LoadStatus, PermissionRequest, Servo, ServoDelegate, GamepadHapticEffectType, JSValue, KeyboardEvent, LoadStatus, PermissionRequest, Servo,
ServoError, SimpleDialog, TraversalId, WebDriverCommandMsg, WebDriverJSResult, ServoDelegate, ServoError, SimpleDialog, TraversalId, WebDriverCommandMsg, WebDriverJSResult,
WebDriverJSValue, WebDriverLoadStatus, WebDriverUserPrompt, WebView, WebViewBuilder, WebDriverLoadStatus, WebDriverUserPrompt, WebView, WebViewBuilder, WebViewDelegate,
WebViewDelegate,
}; };
use url::Url; use url::Url;
@ -500,8 +499,10 @@ impl RunningAppState {
.borrow() .borrow()
.script_evaluation_interrupt_sender .script_evaluation_interrupt_sender
{ {
sender.send(Ok(WebDriverJSValue::Null)).unwrap_or_else(|err| { sender.send(Ok(JSValue::Null)).unwrap_or_else(|err| {
info!("Notify dialog appear failed. Maybe the channel to webdriver is closed: {err}"); info!(
"Notify dialog appear failed. Maybe the channel to webdriver is closed: {err}"
);
}); });
} }
} }

View file

@ -27,7 +27,7 @@
expected: FAIL expected: FAIL
[test_not_supported_nodes[document\]] [test_not_supported_nodes[document\]]
expected: ERROR expected: FAIL
[test_not_supported_nodes[doctype\]] [test_not_supported_nodes[doctype\]]
expected: ERROR expected: FAIL