Various fixes for webdriver conformance tests (#35737)

* servodriver: Ensure capabilities is always a non-empty value.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* webdriver: Serialize arguments object like an array.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* webdriver: Ensure script body is always valid JS.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* webdriver: Use current browsing context when getting element center point.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* webdriver: Propagate errors received from getting element center point.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* webdriver: Ensure opening a new window records a unique window handle.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* webdriver: Don't panic if script execution fails.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* webdriver: Do not update the current browsing context after closing a window.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* Formatting.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* webdriver: Use more precise check for arguments exotic object.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* Formatting.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

---------

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews 2025-03-15 09:54:14 -04:00 committed by GitHub
parent bc4ee8b56a
commit 21ecfdd088
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 46 additions and 32 deletions

View file

@ -5,6 +5,7 @@
use std::cmp; use std::cmp;
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::CString; use std::ffi::CString;
use std::ptr::NonNull;
use base::id::{BrowsingContextId, PipelineId}; use base::id::{BrowsingContextId, PipelineId};
use cookie::Cookie; use cookie::Cookie;
@ -20,7 +21,7 @@ use js::jsapi::{
}; };
use js::jsval::UndefinedValue; use js::jsval::UndefinedValue;
use js::rust::wrappers::{JS_CallFunctionName, JS_GetProperty, JS_HasOwnProperty, JS_TypeOfValue}; use js::rust::wrappers::{JS_CallFunctionName, JS_GetProperty, JS_HasOwnProperty, JS_TypeOfValue};
use js::rust::{HandleObject, HandleValue, IdVector}; use js::rust::{HandleObject, HandleValue, IdVector, ToString};
use net_traits::CookieSource::{HTTP, NonHTTP}; use net_traits::CookieSource::{HTTP, NonHTTP};
use net_traits::CoreResourceMsg::{DeleteCookies, GetCookiesDataForUrl, SetCookieForUrl}; use net_traits::CoreResourceMsg::{DeleteCookies, GetCookiesDataForUrl, SetCookieForUrl};
use net_traits::IpcSend; use net_traits::IpcSend;
@ -42,7 +43,8 @@ use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::Bindings::XMLSerializerBinding::XMLSerializerMethods; use crate::dom::bindings::codegen::Bindings::XMLSerializerBinding::XMLSerializerMethods;
use crate::dom::bindings::conversions::{ use crate::dom::bindings::conversions::{
ConversionBehavior, ConversionResult, FromJSValConvertible, StringificationBehavior, ConversionBehavior, ConversionResult, FromJSValConvertible, StringificationBehavior,
get_property, get_property_jsval, is_array_like, jsid_to_string, root_from_object, get_property, get_property_jsval, is_array_like, jsid_to_string, jsstring_to_str,
root_from_object,
}; };
use crate::dom::bindings::error::{Error, throw_dom_exception}; use crate::dom::bindings::error::{Error, throw_dom_exception};
use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::inheritance::Castable;
@ -168,6 +170,16 @@ unsafe fn object_has_to_json_property(
} }
} }
#[allow(unsafe_code)]
/// <https://w3c.github.io/webdriver/#dfn-collection>
unsafe fn is_arguments_object(cx: *mut JSContext, value: HandleValue) -> bool {
rooted!(in(cx) let class_name = ToString(cx, value));
let Some(class_name) = NonNull::new(class_name.get()) else {
return false;
};
jsstring_to_str(cx, class_name) == "[object Arguments]"
}
#[allow(unsafe_code)] #[allow(unsafe_code)]
pub(crate) unsafe fn jsval_to_webdriver( pub(crate) unsafe fn jsval_to_webdriver(
cx: *mut JSContext, cx: *mut JSContext,
@ -212,7 +224,7 @@ pub(crate) unsafe fn jsval_to_webdriver(
}); });
let _ac = JSAutoRealm::new(cx, *object); let _ac = JSAutoRealm::new(cx, *object);
if is_array_like(cx, val) { if is_array_like(cx, val) || is_arguments_object(cx, val) {
let mut result: Vec<WebDriverJSValue> = Vec::new(); let mut result: Vec<WebDriverJSValue> = Vec::new();
let length = match get_property::<u32>( let length = match get_property::<u32>(

View file

@ -372,19 +372,15 @@ impl Handler {
PointerOrigin::Pointer => (start_x + x_offset, start_y + y_offset), PointerOrigin::Pointer => (start_x + x_offset, start_y + y_offset),
PointerOrigin::Element(ref x) => { PointerOrigin::Element(ref x) => {
let (sender, receiver) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap();
self.top_level_script_command(WebDriverScriptCommand::GetElementInViewCenterPoint( self.browsing_context_script_command(
x.to_string(), WebDriverScriptCommand::GetElementInViewCenterPoint(x.to_string(), sender),
sender, )
))
.unwrap(); .unwrap();
match receiver.recv().unwrap() { let Some(point) = receiver.recv().unwrap()? else {
Ok(point) => match point { return Err(ErrorStatus::UnknownError);
Some(point) => point, };
None => return Err(ErrorStatus::UnknownError), point
},
Err(_) => return Err(ErrorStatus::UnknownError),
}
}, },
}; };

View file

@ -897,14 +897,13 @@ impl Handler {
.unwrap(); .unwrap();
} }
let webview_id = self.focus_webview_id()?;
let browsing_context_id = BrowsingContextId::from(webview_id);
let session = self.session_mut().unwrap();
session.webview_id = webview_id;
session.browsing_context_id = browsing_context_id;
Ok(WebDriverResponse::CloseWindow(CloseWindowResponse( Ok(WebDriverResponse::CloseWindow(CloseWindowResponse(
session.window_handles.values().cloned().collect(), self.session()
.unwrap()
.window_handles
.values()
.cloned()
.collect(),
))) )))
} }
@ -924,17 +923,18 @@ impl Handler {
.send(ConstellationMsg::WebDriverCommand(cmd_msg)) .send(ConstellationMsg::WebDriverCommand(cmd_msg))
.unwrap(); .unwrap();
let mut handle = self.session.as_ref().unwrap().id.to_string();
if let Ok(new_webview_id) = receiver.recv() { if let Ok(new_webview_id) = receiver.recv() {
let session = self.session_mut().unwrap(); let session = self.session_mut().unwrap();
session.webview_id = new_webview_id; session.webview_id = new_webview_id;
session.browsing_context_id = BrowsingContextId::from(new_webview_id); session.browsing_context_id = BrowsingContextId::from(new_webview_id);
let new_handle = Uuid::new_v4().to_string(); let new_handle = Uuid::new_v4().to_string();
handle = new_handle.clone();
session.window_handles.insert(new_webview_id, new_handle); session.window_handles.insert(new_webview_id, new_handle);
} }
let _ = self.wait_for_load(); let _ = self.wait_for_load();
let handle = self.session.as_ref().unwrap().id.to_string();
Ok(WebDriverResponse::NewWindow(NewWindowResponse { Ok(WebDriverResponse::NewWindow(NewWindowResponse {
handle, handle,
typ: "tab".to_string(), typ: "tab".to_string(),
@ -1458,7 +1458,7 @@ impl Handler {
// new Function() and then takes the resulting function and executes // new Function() and then takes the resulting function and executes
// it with a vec of arguments. // it with a vec of arguments.
let script = format!( let script = format!(
"(function() {{ {} }})({})", "(function() {{ {}\n }})({})",
func_body, func_body,
args_string.join(", ") args_string.join(", ")
); );
@ -1467,7 +1467,9 @@ impl Handler {
let (sender, receiver) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap();
let command = WebDriverScriptCommand::ExecuteScript(script, sender); let command = WebDriverScriptCommand::ExecuteScript(script, sender);
self.browsing_context_script_command(command)?; self.browsing_context_script_command(command)?;
let result = receiver.recv().unwrap(); let result = receiver
.recv()
.unwrap_or(Err(WebDriverJSError::BrowsingContextNotFound));
self.postprocess_js_result(result) self.postprocess_js_result(result)
} }
@ -1491,7 +1493,7 @@ impl Handler {
"".into() "".into()
}; };
let script = format!( let script = format!(
"{} (function() {{ {} }})({})", "{} (function() {{ {}\n }})({})",
timeout_script, timeout_script,
func_body, func_body,
args_string.join(", "), args_string.join(", "),
@ -1501,7 +1503,9 @@ impl Handler {
let (sender, receiver) = ipc::channel().unwrap(); let (sender, receiver) = ipc::channel().unwrap();
let command = WebDriverScriptCommand::ExecuteAsyncScript(script, sender); let command = WebDriverScriptCommand::ExecuteAsyncScript(script, sender);
self.browsing_context_script_command(command)?; self.browsing_context_script_command(command)?;
let result = receiver.recv().unwrap(); let result = receiver
.recv()
.unwrap_or(Err(WebDriverJSError::BrowsingContextNotFound));
self.postprocess_js_result(result) self.postprocess_js_result(result)
} }
@ -1514,7 +1518,7 @@ impl Handler {
serde_json::to_value(SendableWebDriverJSValue(value))?, serde_json::to_value(SendableWebDriverJSValue(value))?,
))), ))),
Err(WebDriverJSError::BrowsingContextNotFound) => Err(WebDriverError::new( Err(WebDriverJSError::BrowsingContextNotFound) => Err(WebDriverError::new(
ErrorStatus::JavascriptError, ErrorStatus::NoSuchWindow,
"Pipeline id not found in browsing context", "Pipeline id not found in browsing context",
)), )),
Err(WebDriverJSError::JSError) => Err(WebDriverError::new( Err(WebDriverJSError::JSError) => Err(WebDriverError::new(

View file

@ -507579,7 +507579,7 @@
[] []
], ],
"servodriver.py": [ "servodriver.py": [
"18035f9331e0d5df1c3fa916ce33a8866bea091a", "bd16d7987e539a3325d88876982462ea1a3eb638",
[] []
], ],
"webkit.py": [ "webkit.py": [
@ -507645,7 +507645,7 @@
[] []
], ],
"executorservodriver.py": [ "executorservodriver.py": [
"7e4aee5e7af7b9719b4ccb1f5a0a7c7cb3aa1653", "e27b111ee8b7c4cdff4a0aee5c010d402efff263",
[] []
], ],
"executorwebdriver.py": [ "executorwebdriver.py": [

View file

@ -48,11 +48,13 @@ def browser_kwargs(logger, test_type, run_info_data, config, **kwargs):
"server_config": config, "server_config": config,
"user_stylesheets": kwargs.get("user_stylesheets"), "user_stylesheets": kwargs.get("user_stylesheets"),
"headless": kwargs.get("headless"), "headless": kwargs.get("headless"),
"capabilities": kwargs.get("capabilities"),
} }
def executor_kwargs(logger, test_type, test_environment, run_info_data, **kwargs): def executor_kwargs(logger, test_type, test_environment, run_info_data, **kwargs):
rv = base_executor_kwargs(test_type, test_environment, run_info_data, **kwargs) rv = base_executor_kwargs(test_type, test_environment, run_info_data, **kwargs)
rv['capabilities'] = {}
return rv return rv

View file

@ -78,7 +78,7 @@ class ServoWebDriverTestharnessExecutor(WebDriverTestharnessExecutor):
protocol_cls = ServoWebDriverProtocol protocol_cls = ServoWebDriverProtocol
def __init__(self, logger, browser, server_config, timeout_multiplier=1, def __init__(self, logger, browser, server_config, timeout_multiplier=1,
close_after_done=True, capabilities={}, debug_info=None, close_after_done=True, capabilities=None, debug_info=None,
**kwargs): **kwargs):
WebDriverTestharnessExecutor.__init__(self, logger, browser, server_config, WebDriverTestharnessExecutor.__init__(self, logger, browser, server_config,
timeout_multiplier, capabilities=capabilities, timeout_multiplier, capabilities=capabilities,
@ -96,7 +96,7 @@ class ServoWebDriverRefTestExecutor(WebDriverRefTestExecutor):
protocol_cls = ServoWebDriverProtocol protocol_cls = ServoWebDriverProtocol
def __init__(self, logger, browser, server_config, timeout_multiplier=1, def __init__(self, logger, browser, server_config, timeout_multiplier=1,
screenshot_cache=None, capabilities={}, debug_info=None, screenshot_cache=None, capabilities=None, debug_info=None,
**kwargs): **kwargs):
WebDriverRefTestExecutor.__init__(self, logger, browser, server_config, WebDriverRefTestExecutor.__init__(self, logger, browser, server_config,
timeout_multiplier, screenshot_cache, timeout_multiplier, screenshot_cache,
@ -114,7 +114,7 @@ class ServoWebDriverCrashtestExecutor(WebDriverCrashtestExecutor):
protocol_cls = ServoWebDriverProtocol protocol_cls = ServoWebDriverProtocol
def __init__(self, logger, browser, server_config, timeout_multiplier=1, def __init__(self, logger, browser, server_config, timeout_multiplier=1,
screenshot_cache=None, capabilities={}, debug_info=None, screenshot_cache=None, capabilities=None, debug_info=None,
**kwargs): **kwargs):
WebDriverCrashtestExecutor.__init__(self, logger, browser, server_config, WebDriverCrashtestExecutor.__init__(self, logger, browser, server_config,
timeout_multiplier, screenshot_cache, timeout_multiplier, screenshot_cache,