mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
Allow running testharness/testdriver/reftests in servodriver (#34550)
* Make servodriver a thin wrapper over the base webdriver browser/executor classes. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Make ServoWebDriverRefTestExecutor a thin shell over the webdriver reftest executor. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Wait for the initial load to complete when opening a new tab via webdriver. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Remove assumption of a single tab from the webdriver server. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Serialize all keys of JS objects when converting to webdriver values. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Formatting. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Cleanup, docs, etc. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Use webview terminology more consistently. Signed-off-by: Josh Matthews <josh@joshmatthews.net> * Fix flake8 errors. Signed-off-by: Josh Matthews <josh@joshmatthews.net> --------- Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
parent
25f242b652
commit
7b160700d0
9 changed files with 336 additions and 407 deletions
|
@ -1461,7 +1461,11 @@ where
|
|||
// Create a new top level browsing context. Will use response_chan to return
|
||||
// the browsing context id.
|
||||
FromCompositorMsg::NewWebView(url, top_level_browsing_context_id) => {
|
||||
self.handle_new_top_level_browsing_context(url, top_level_browsing_context_id);
|
||||
self.handle_new_top_level_browsing_context(
|
||||
url,
|
||||
top_level_browsing_context_id,
|
||||
None,
|
||||
);
|
||||
},
|
||||
// A top level browsing context is created and opened in both constellation and
|
||||
// compositor.
|
||||
|
@ -1485,14 +1489,7 @@ where
|
|||
self.handle_panic(top_level_browsing_context_id, error, None);
|
||||
},
|
||||
FromCompositorMsg::FocusWebView(top_level_browsing_context_id) => {
|
||||
if self.webviews.get(top_level_browsing_context_id).is_none() {
|
||||
return warn!("{top_level_browsing_context_id}: FocusWebView on unknown top-level browsing context");
|
||||
}
|
||||
self.webviews.focus(top_level_browsing_context_id);
|
||||
self.embedder_proxy.send((
|
||||
Some(top_level_browsing_context_id),
|
||||
EmbedderMsg::WebViewFocused(top_level_browsing_context_id),
|
||||
));
|
||||
self.handle_focus_web_view(top_level_browsing_context_id);
|
||||
},
|
||||
FromCompositorMsg::BlurWebView => {
|
||||
self.webviews.unfocus();
|
||||
|
@ -2957,6 +2954,21 @@ where
|
|||
feature = "tracing",
|
||||
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
|
||||
)]
|
||||
fn handle_focus_web_view(&mut self, top_level_browsing_context_id: TopLevelBrowsingContextId) {
|
||||
if self.webviews.get(top_level_browsing_context_id).is_none() {
|
||||
return warn!("{top_level_browsing_context_id}: FocusWebView on unknown top-level browsing context");
|
||||
}
|
||||
self.webviews.focus(top_level_browsing_context_id);
|
||||
self.embedder_proxy.send((
|
||||
Some(top_level_browsing_context_id),
|
||||
EmbedderMsg::WebViewFocused(top_level_browsing_context_id),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(skip_all, fields(servo_profiling = true))
|
||||
)]
|
||||
fn handle_log_entry(
|
||||
&mut self,
|
||||
top_level_browsing_context_id: Option<TopLevelBrowsingContextId>,
|
||||
|
@ -3044,6 +3056,7 @@ where
|
|||
&mut self,
|
||||
url: ServoUrl,
|
||||
top_level_browsing_context_id: TopLevelBrowsingContextId,
|
||||
response_sender: Option<IpcSender<webdriver_msg::LoadStatus>>,
|
||||
) {
|
||||
let window_size = self.window_size.initial_viewport;
|
||||
let pipeline_id = PipelineId::new();
|
||||
|
@ -3104,6 +3117,10 @@ where
|
|||
}),
|
||||
window_size,
|
||||
});
|
||||
|
||||
if let Some(response_sender) = response_sender {
|
||||
self.webdriver.load_channel = Some((pipeline_id, response_sender));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
|
@ -3759,7 +3776,7 @@ where
|
|||
) {
|
||||
let mut webdriver_reset = false;
|
||||
if let Some((expected_pipeline_id, ref reply_chan)) = self.webdriver.load_channel {
|
||||
debug!("Sending load to WebDriver");
|
||||
debug!("Sending load for {:?} to WebDriver", expected_pipeline_id);
|
||||
if expected_pipeline_id == pipeline_id {
|
||||
let _ = reply_chan.send(webdriver_msg::LoadStatus::LoadComplete);
|
||||
webdriver_reset = true;
|
||||
|
@ -4606,6 +4623,21 @@ where
|
|||
// Find the script channel for the given parent pipeline,
|
||||
// and pass the event to that script thread.
|
||||
match msg {
|
||||
WebDriverCommandMsg::CloseWebView(top_level_browsing_context_id) => {
|
||||
self.handle_close_top_level_browsing_context(top_level_browsing_context_id);
|
||||
},
|
||||
WebDriverCommandMsg::NewWebView(sender, load_sender) => {
|
||||
let top_level_browsing_context_id = TopLevelBrowsingContextId::new();
|
||||
self.handle_new_top_level_browsing_context(
|
||||
ServoUrl::parse_with_base(None, "about:blank").expect("Infallible parse"),
|
||||
top_level_browsing_context_id,
|
||||
Some(load_sender),
|
||||
);
|
||||
let _ = sender.send(top_level_browsing_context_id);
|
||||
},
|
||||
WebDriverCommandMsg::FocusWebView(top_level_browsing_context_id) => {
|
||||
self.handle_focus_web_view(top_level_browsing_context_id);
|
||||
},
|
||||
WebDriverCommandMsg::GetWindowSize(_, response_sender) => {
|
||||
let _ = response_sender.send(self.window_size);
|
||||
},
|
||||
|
@ -4888,13 +4920,20 @@ where
|
|||
);
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(new_pipeline_id) = self.load_url(
|
||||
top_level_browsing_context_id,
|
||||
pipeline_id,
|
||||
load_data,
|
||||
replace,
|
||||
) {
|
||||
debug!(
|
||||
"Setting up webdriver load notification for {:?}",
|
||||
new_pipeline_id
|
||||
);
|
||||
self.webdriver.load_channel = Some((new_pipeline_id, response_sender));
|
||||
} else {
|
||||
let _ = response_sender.send(webdriver_msg::LoadStatus::LoadCanceled);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,10 +11,13 @@ use cookie::Cookie;
|
|||
use euclid::default::{Point2D, Rect, Size2D};
|
||||
use hyper_serde::Serde;
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
use js::jsapi::{HandleValueArray, JSAutoRealm, JSContext, JSType, JS_IsExceptionPending};
|
||||
use js::jsapi::{
|
||||
self, GetPropertyKeys, HandleValueArray, JSAutoRealm, JSContext, JSType,
|
||||
JS_GetOwnPropertyDescriptorById, JS_GetPropertyById, JS_IsExceptionPending, PropertyDescriptor,
|
||||
};
|
||||
use js::jsval::UndefinedValue;
|
||||
use js::rust::wrappers::{JS_CallFunctionName, JS_GetProperty, JS_HasOwnProperty, JS_TypeOfValue};
|
||||
use js::rust::{HandleObject, HandleValue};
|
||||
use js::rust::{HandleObject, HandleValue, IdVector};
|
||||
use net_traits::CookieSource::{NonHTTP, HTTP};
|
||||
use net_traits::CoreResourceMsg::{DeleteCookies, GetCookiesDataForUrl, SetCookieForUrl};
|
||||
use net_traits::IpcSend;
|
||||
|
@ -38,8 +41,8 @@ use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, N
|
|||
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::XMLSerializerBinding::XMLSerializerMethods;
|
||||
use crate::dom::bindings::conversions::{
|
||||
get_property, get_property_jsval, is_array_like, root_from_object, ConversionBehavior,
|
||||
ConversionResult, FromJSValConvertible, StringificationBehavior,
|
||||
get_property, get_property_jsval, is_array_like, jsid_to_string, root_from_object,
|
||||
ConversionBehavior, ConversionResult, FromJSValConvertible, StringificationBehavior,
|
||||
};
|
||||
use crate::dom::bindings::error::{throw_dom_exception, Error};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
|
@ -263,19 +266,50 @@ pub unsafe fn jsval_to_webdriver(
|
|||
} else {
|
||||
let mut result = HashMap::new();
|
||||
|
||||
let common_properties = ["x", "y", "width", "height", "key"];
|
||||
for property in common_properties.iter() {
|
||||
rooted!(in(cx) let mut item = UndefinedValue());
|
||||
if get_property_jsval(cx, object.handle(), property, item.handle_mut()).is_ok() {
|
||||
if !item.is_undefined() {
|
||||
if let Ok(value) = jsval_to_webdriver(cx, global_scope, item.handle()) {
|
||||
result.insert(property.to_string(), value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw_dom_exception(SafeJSContext::from_ptr(cx), global_scope, Error::JSFailed);
|
||||
let mut ids = IdVector::new(cx);
|
||||
if !GetPropertyKeys(
|
||||
cx,
|
||||
object.handle().into(),
|
||||
jsapi::JSITER_OWNONLY,
|
||||
ids.handle_mut(),
|
||||
) {
|
||||
return Err(WebDriverJSError::JSError);
|
||||
}
|
||||
for id in ids.iter() {
|
||||
rooted!(in(cx) let id = *id);
|
||||
rooted!(in(cx) let mut desc = PropertyDescriptor::default());
|
||||
|
||||
let mut is_none = false;
|
||||
if !JS_GetOwnPropertyDescriptorById(
|
||||
cx,
|
||||
object.handle().into(),
|
||||
id.handle().into(),
|
||||
desc.handle_mut().into(),
|
||||
&mut is_none,
|
||||
) {
|
||||
return Err(WebDriverJSError::JSError);
|
||||
}
|
||||
|
||||
rooted!(in(cx) let mut property = UndefinedValue());
|
||||
if !JS_GetPropertyById(
|
||||
cx,
|
||||
object.handle().into(),
|
||||
id.handle().into(),
|
||||
property.handle_mut().into(),
|
||||
) {
|
||||
return Err(WebDriverJSError::JSError);
|
||||
}
|
||||
if !property.is_undefined() {
|
||||
let Some(name) = jsid_to_string(cx, id.handle()) else {
|
||||
return Err(WebDriverJSError::JSError);
|
||||
};
|
||||
|
||||
if let Ok(value) = jsval_to_webdriver(cx, global_scope, property.handle()) {
|
||||
result.insert(name.into(), value);
|
||||
} else {
|
||||
return Err(WebDriverJSError::JSError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(WebDriverJSValue::Object(result))
|
||||
|
|
|
@ -808,6 +808,15 @@ pub enum WebDriverCommandMsg {
|
|||
Option<Rect<f32, CSSPixel>>,
|
||||
IpcSender<Option<Image>>,
|
||||
),
|
||||
/// Create a new webview that loads about:blank. The constellation will use
|
||||
/// the provided channels to return the top level browsing context id
|
||||
/// associated with the new webview, and a notification when the initial
|
||||
/// load is complete.
|
||||
NewWebView(IpcSender<TopLevelBrowsingContextId>, IpcSender<LoadStatus>),
|
||||
/// Close the webview associated with the provided id.
|
||||
CloseWebView(TopLevelBrowsingContextId),
|
||||
/// Focus the webview associated with the provided id.
|
||||
FocusWebView(TopLevelBrowsingContextId),
|
||||
}
|
||||
|
||||
/// Resources required by workerglobalscopes
|
||||
|
|
|
@ -135,4 +135,5 @@ pub enum WebDriverFrameId {
|
|||
pub enum LoadStatus {
|
||||
LoadComplete,
|
||||
LoadTimeout,
|
||||
LoadCanceled,
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ use cookie::{CookieBuilder, Expiration};
|
|||
use crossbeam_channel::{after, select, unbounded, Receiver, Sender};
|
||||
use euclid::{Rect, Size2D};
|
||||
use http::method::Method;
|
||||
use image::{DynamicImage, ImageFormat, RgbImage};
|
||||
use image::{DynamicImage, ImageFormat, RgbaImage};
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
use ipc_channel::router::ROUTER;
|
||||
use keyboard_types::webdriver::send_keys;
|
||||
|
@ -53,16 +53,16 @@ use webdriver::actions::{
|
|||
use webdriver::capabilities::{Capabilities, CapabilitiesMatching};
|
||||
use webdriver::command::{
|
||||
ActionsParameters, AddCookieParameters, GetParameters, JavascriptCommandParameters,
|
||||
LocatorParameters, NewSessionParameters, SendKeysParameters, SwitchToFrameParameters,
|
||||
SwitchToWindowParameters, TimeoutsParameters, WebDriverCommand, WebDriverExtensionCommand,
|
||||
WebDriverMessage, WindowRectParameters,
|
||||
LocatorParameters, NewSessionParameters, NewWindowParameters, SendKeysParameters,
|
||||
SwitchToFrameParameters, SwitchToWindowParameters, TimeoutsParameters, WebDriverCommand,
|
||||
WebDriverExtensionCommand, WebDriverMessage, WindowRectParameters,
|
||||
};
|
||||
use webdriver::common::{Cookie, Date, LocatorStrategy, Parameters, WebElement};
|
||||
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
|
||||
use webdriver::httpapi::WebDriverExtensionRoute;
|
||||
use webdriver::response::{
|
||||
CookieResponse, CookiesResponse, ElementRectResponse, NewSessionResponse, TimeoutsResponse,
|
||||
ValueResponse, WebDriverResponse, WindowRectResponse,
|
||||
CloseWindowResponse, CookieResponse, CookiesResponse, ElementRectResponse, NewSessionResponse,
|
||||
NewWindowResponse, TimeoutsResponse, ValueResponse, WebDriverResponse, WindowRectResponse,
|
||||
};
|
||||
use webdriver::server::{self, Session, SessionTeardownKind, WebDriverHandler};
|
||||
|
||||
|
@ -130,6 +130,8 @@ pub struct WebDriverSession {
|
|||
browsing_context_id: BrowsingContextId,
|
||||
top_level_browsing_context_id: TopLevelBrowsingContextId,
|
||||
|
||||
window_handles: HashMap<TopLevelBrowsingContextId, String>,
|
||||
|
||||
/// Time to wait for injected scripts to run before interrupting them. A [`None`] value
|
||||
/// specifies that the script should run indefinitely.
|
||||
script_timeout: Option<u64>,
|
||||
|
@ -158,11 +160,17 @@ impl WebDriverSession {
|
|||
browsing_context_id: BrowsingContextId,
|
||||
top_level_browsing_context_id: TopLevelBrowsingContextId,
|
||||
) -> WebDriverSession {
|
||||
let mut window_handles = HashMap::new();
|
||||
let handle = Uuid::new_v4().to_string();
|
||||
window_handles.insert(top_level_browsing_context_id, handle);
|
||||
|
||||
WebDriverSession {
|
||||
id: Uuid::new_v4(),
|
||||
browsing_context_id,
|
||||
top_level_browsing_context_id,
|
||||
|
||||
window_handles,
|
||||
|
||||
script_timeout: Some(30_000),
|
||||
load_timeout: 300_000,
|
||||
implicit_wait_timeout: 0,
|
||||
|
@ -418,6 +426,9 @@ impl Handler {
|
|||
}
|
||||
|
||||
fn focus_top_level_browsing_context_id(&self) -> WebDriverResult<TopLevelBrowsingContextId> {
|
||||
// FIXME(#34550): This is a hack for unexpected behaviour in the constellation.
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
|
||||
debug!("Getting focused context.");
|
||||
let interval = 20;
|
||||
let iterations = 30_000 / interval;
|
||||
|
@ -673,13 +684,16 @@ impl Handler {
|
|||
}
|
||||
|
||||
fn wait_for_load(&self) -> WebDriverResult<WebDriverResponse> {
|
||||
debug!("waiting for load");
|
||||
let timeout = self.session()?.load_timeout;
|
||||
select! {
|
||||
let result = select! {
|
||||
recv(self.load_status_receiver) -> _ => Ok(WebDriverResponse::Void),
|
||||
recv(after(Duration::from_millis(timeout))) -> _ => Err(
|
||||
WebDriverError::new(ErrorStatus::Timeout, "Load timed out")
|
||||
),
|
||||
}
|
||||
};
|
||||
debug!("finished waiting for load with {:?}", result);
|
||||
result
|
||||
}
|
||||
|
||||
fn handle_current_url(&self) -> WebDriverResult<WebDriverResponse> {
|
||||
|
@ -718,6 +732,13 @@ impl Handler {
|
|||
params: &WindowRectParameters,
|
||||
) -> WebDriverResult<WebDriverResponse> {
|
||||
let (sender, receiver) = ipc::channel().unwrap();
|
||||
|
||||
// We don't current allow modifying the window x/y positions, so we can just
|
||||
// return the current window rectangle.
|
||||
if params.width.is_none() || params.height.is_none() {
|
||||
return self.handle_window_size();
|
||||
}
|
||||
|
||||
let width = params.width.unwrap_or(0);
|
||||
let height = params.height.unwrap_or(0);
|
||||
let size = Size2D::new(width as u32, height as u32);
|
||||
|
@ -829,20 +850,27 @@ impl Handler {
|
|||
}
|
||||
|
||||
fn handle_window_handle(&self) -> WebDriverResult<WebDriverResponse> {
|
||||
// For now we assume there's only one window so just use the session
|
||||
// id as the window id
|
||||
let handle = self.session.as_ref().unwrap().id.to_string();
|
||||
Ok(WebDriverResponse::Generic(ValueResponse(
|
||||
serde_json::to_value(handle)?,
|
||||
)))
|
||||
let session = self.session.as_ref().unwrap();
|
||||
match session
|
||||
.window_handles
|
||||
.get(&session.top_level_browsing_context_id)
|
||||
{
|
||||
Some(handle) => Ok(WebDriverResponse::Generic(ValueResponse(
|
||||
serde_json::to_value(handle)?,
|
||||
))),
|
||||
None => Ok(WebDriverResponse::Void),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_window_handles(&self) -> WebDriverResult<WebDriverResponse> {
|
||||
// For now we assume there's only one window so just use the session
|
||||
// id as the window id
|
||||
let handles = vec![serde_json::to_value(
|
||||
self.session.as_ref().unwrap().id.to_string(),
|
||||
)?];
|
||||
let handles = self
|
||||
.session
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.window_handles
|
||||
.values()
|
||||
.map(serde_json::to_value)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(WebDriverResponse::Generic(ValueResponse(
|
||||
serde_json::to_value(handles)?,
|
||||
)))
|
||||
|
@ -891,6 +919,60 @@ impl Handler {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_close_window(&mut self) -> WebDriverResult<WebDriverResponse> {
|
||||
{
|
||||
let session = self.session_mut().unwrap();
|
||||
session
|
||||
.window_handles
|
||||
.remove(&session.top_level_browsing_context_id);
|
||||
let cmd_msg = WebDriverCommandMsg::CloseWebView(session.top_level_browsing_context_id);
|
||||
self.constellation_chan
|
||||
.send(ConstellationMsg::WebDriverCommand(cmd_msg))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let top_level_browsing_context_id = self.focus_top_level_browsing_context_id()?;
|
||||
let browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
|
||||
let session = self.session_mut().unwrap();
|
||||
session.top_level_browsing_context_id = top_level_browsing_context_id;
|
||||
session.browsing_context_id = browsing_context_id;
|
||||
|
||||
Ok(WebDriverResponse::CloseWindow(CloseWindowResponse(
|
||||
session.window_handles.values().cloned().collect(),
|
||||
)))
|
||||
}
|
||||
|
||||
fn handle_new_window(
|
||||
&mut self,
|
||||
_parameters: &NewWindowParameters,
|
||||
) -> WebDriverResult<WebDriverResponse> {
|
||||
let (sender, receiver) = ipc::channel().unwrap();
|
||||
|
||||
let cmd_msg = WebDriverCommandMsg::NewWebView(sender, self.load_status_sender.clone());
|
||||
self.constellation_chan
|
||||
.send(ConstellationMsg::WebDriverCommand(cmd_msg))
|
||||
.unwrap();
|
||||
|
||||
if let Ok(new_top_level_browsing_context_id) = receiver.recv() {
|
||||
let session = self.session_mut().unwrap();
|
||||
session.top_level_browsing_context_id = new_top_level_browsing_context_id;
|
||||
session.browsing_context_id =
|
||||
BrowsingContextId::from(new_top_level_browsing_context_id);
|
||||
let new_handle = Uuid::new_v4().to_string();
|
||||
session
|
||||
.window_handles
|
||||
.insert(new_top_level_browsing_context_id, new_handle);
|
||||
}
|
||||
|
||||
let _ = self.wait_for_load();
|
||||
|
||||
let handle = self.session.as_ref().unwrap().id.to_string();
|
||||
Ok(WebDriverResponse::NewWindow(NewWindowResponse {
|
||||
handle,
|
||||
typ: "tab".to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn handle_switch_to_frame(
|
||||
&mut self,
|
||||
parameters: &SwitchToFrameParameters,
|
||||
|
@ -919,9 +1001,21 @@ impl Handler {
|
|||
&mut self,
|
||||
parameters: &SwitchToWindowParameters,
|
||||
) -> WebDriverResult<WebDriverResponse> {
|
||||
// For now we assume there is only one window which has the current
|
||||
// session's id as window id
|
||||
if parameters.handle == self.session.as_ref().unwrap().id.to_string() {
|
||||
let session = self.session_mut().unwrap();
|
||||
if session.id.to_string() == parameters.handle {
|
||||
// There's only one main window, so there's nothing to do here.
|
||||
Ok(WebDriverResponse::Void)
|
||||
} else if let Some((top_level_browsing_context_id, _)) = session
|
||||
.window_handles
|
||||
.iter()
|
||||
.find(|(_k, v)| **v == parameters.handle)
|
||||
{
|
||||
let top_level_browsing_context_id = *top_level_browsing_context_id;
|
||||
session.top_level_browsing_context_id = top_level_browsing_context_id;
|
||||
session.browsing_context_id = BrowsingContextId::from(top_level_browsing_context_id);
|
||||
|
||||
let msg = ConstellationMsg::FocusWebView(top_level_browsing_context_id);
|
||||
self.constellation_chan.send(msg).unwrap();
|
||||
Ok(WebDriverResponse::Void)
|
||||
} else {
|
||||
Err(WebDriverError::new(
|
||||
|
@ -1385,12 +1479,23 @@ impl Handler {
|
|||
parameters: &JavascriptCommandParameters,
|
||||
) -> WebDriverResult<WebDriverResponse> {
|
||||
let func_body = ¶meters.script;
|
||||
let args_string = "";
|
||||
let args_string: Vec<_> = parameters
|
||||
.args
|
||||
.as_deref()
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.map(webdriver_value_to_js_argument)
|
||||
.collect();
|
||||
|
||||
// This is pretty ugly; we really want something that acts like
|
||||
// new Function() and then takes the resulting function and executes
|
||||
// it with a vec of arguments.
|
||||
let script = format!("(function() {{ {} }})({})", func_body, args_string);
|
||||
let script = format!(
|
||||
"(function() {{ {} }})({})",
|
||||
func_body,
|
||||
args_string.join(", ")
|
||||
);
|
||||
debug!("{}", script);
|
||||
|
||||
let (sender, receiver) = ipc::channel().unwrap();
|
||||
let command = WebDriverScriptCommand::ExecuteScript(script, sender);
|
||||
|
@ -1404,7 +1509,14 @@ impl Handler {
|
|||
parameters: &JavascriptCommandParameters,
|
||||
) -> WebDriverResult<WebDriverResponse> {
|
||||
let func_body = ¶meters.script;
|
||||
let args_string = "window.webdriverCallback";
|
||||
let mut args_string: Vec<_> = parameters
|
||||
.args
|
||||
.as_deref()
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.map(webdriver_value_to_js_argument)
|
||||
.collect();
|
||||
args_string.push("window.webdriverCallback".to_string());
|
||||
|
||||
let timeout_script = if let Some(script_timeout) = self.session()?.script_timeout {
|
||||
format!("setTimeout(webdriverTimeout, {});", script_timeout)
|
||||
|
@ -1412,9 +1524,12 @@ impl Handler {
|
|||
"".into()
|
||||
};
|
||||
let script = format!(
|
||||
"{} (function(callback) {{ {} }})({})",
|
||||
timeout_script, func_body, args_string
|
||||
"{} (function() {{ {} }})({})",
|
||||
timeout_script,
|
||||
func_body,
|
||||
args_string.join(", "),
|
||||
);
|
||||
debug!("{}", script);
|
||||
|
||||
let (sender, receiver) = ipc::channel().unwrap();
|
||||
let command = WebDriverScriptCommand::ExecuteAsyncScript(script, sender);
|
||||
|
@ -1587,16 +1702,16 @@ impl Handler {
|
|||
},
|
||||
};
|
||||
|
||||
// The compositor always sends RGB pixels.
|
||||
// The compositor always sends RGBA pixels.
|
||||
assert_eq!(
|
||||
img.format,
|
||||
PixelFormat::RGB8,
|
||||
PixelFormat::RGBA8,
|
||||
"Unexpected screenshot pixel format"
|
||||
);
|
||||
|
||||
let rgb = RgbImage::from_raw(img.width, img.height, img.bytes.to_vec()).unwrap();
|
||||
let rgb = RgbaImage::from_raw(img.width, img.height, img.bytes.to_vec()).unwrap();
|
||||
let mut png_data = Cursor::new(Vec::new());
|
||||
DynamicImage::ImageRgb8(rgb)
|
||||
DynamicImage::ImageRgba8(rgb)
|
||||
.write_to(&mut png_data, ImageFormat::Png)
|
||||
.unwrap();
|
||||
|
||||
|
@ -1729,6 +1844,8 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler {
|
|||
WebDriverCommand::GetTitle => self.handle_title(),
|
||||
WebDriverCommand::GetWindowHandle => self.handle_window_handle(),
|
||||
WebDriverCommand::GetWindowHandles => self.handle_window_handles(),
|
||||
WebDriverCommand::NewWindow(ref parameters) => self.handle_new_window(parameters),
|
||||
WebDriverCommand::CloseWindow => self.handle_close_window(),
|
||||
WebDriverCommand::SwitchToFrame(ref parameters) => {
|
||||
self.handle_switch_to_frame(parameters)
|
||||
},
|
||||
|
@ -1794,3 +1911,26 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler {
|
|||
self.session = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn webdriver_value_to_js_argument(v: &Value) -> String {
|
||||
match v {
|
||||
Value::String(ref s) => format!("\"{}\"", s),
|
||||
Value::Null => "null".to_string(),
|
||||
Value::Bool(b) => b.to_string(),
|
||||
Value::Number(n) => n.to_string(),
|
||||
Value::Array(list) => {
|
||||
let elems = list
|
||||
.iter()
|
||||
.map(|v| webdriver_value_to_js_argument(v).to_string())
|
||||
.collect::<Vec<_>>();
|
||||
format!("[{}]", elems.join(", "))
|
||||
},
|
||||
Value::Object(map) => {
|
||||
let elems = map
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}: {}", k, webdriver_value_to_js_argument(v)))
|
||||
.collect::<Vec<_>>();
|
||||
format!("{{{}}}", elems.join(", "))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue