diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index 564dc7ceb4dd..7293774b7a43 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -18,7 +18,7 @@ use crate::dom::bindings::conversions::{ use crate::dom::bindings::conversions::{ ConversionBehavior, ConversionResult, FromJSValConvertible, StringificationBehavior, }; -use crate::dom::bindings::error::throw_dom_exception; +use crate::dom::bindings::error::{throw_dom_exception, Error}; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::DomRoot; @@ -39,9 +39,10 @@ use cookie::Cookie; use euclid::default::{Point2D, Rect, Size2D}; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcSender}; -use js::jsapi::{JSAutoRealm, JSContext}; +use js::jsapi::{HandleValueArray, JSAutoRealm, JSContext, JSType, JS_IsExceptionPending}; use js::jsval::UndefinedValue; -use js::rust::HandleValue; +use js::rust::wrappers::{JS_CallFunctionName, JS_GetProperty, JS_HasOwnProperty, JS_TypeOfValue}; +use js::rust::{Handle, HandleObject, HandleValue}; use msg::constellation_msg::BrowsingContextId; use msg::constellation_msg::PipelineId; use net_traits::CookieSource::{NonHTTP, HTTP}; @@ -52,7 +53,9 @@ use script_traits::webdriver_msg::{ WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue, }; use servo_url::ServoUrl; -use webdriver::common::WebElement; +use std::collections::HashMap; +use std::ffi::CString; +use webdriver::common::{WebElement, WebFrame, WebWindow}; use webdriver::error::ErrorStatus; fn find_node_by_unique_id( @@ -121,6 +124,31 @@ fn first_matching_link( .map(|nodes| matching_links(&nodes, link_text, partial).take(1).next()) } +#[allow(unsafe_code)] +unsafe fn object_has_to_json_property( + cx: *mut JSContext, + global_scope: &GlobalScope, + object: HandleObject, +) -> bool { + let name = CString::new("toJSON").unwrap(); + let mut found = false; + if JS_HasOwnProperty(cx, object, name.as_ptr(), &mut found) && found { + rooted!(in(cx) let mut value = UndefinedValue()); + let result = JS_GetProperty(cx, object, name.as_ptr(), value.handle_mut()); + if !result { + throw_dom_exception(SafeJSContext::from_ptr(cx), global_scope, Error::JSFailed); + false + } else { + result && JS_TypeOfValue(cx, value.handle()) == JSType::JSTYPE_FUNCTION + } + } else if JS_IsExceptionPending(cx) { + throw_dom_exception(SafeJSContext::from_ptr(cx), global_scope, Error::JSFailed); + false + } else { + false + } +} + #[allow(unsafe_code)] pub unsafe fn jsval_to_webdriver( cx: *mut JSContext, @@ -157,20 +185,15 @@ pub unsafe fn jsval_to_webdriver( }); let _ac = JSAutoRealm::new(cx, *object); - if let Ok(element) = root_from_object::(*object, cx) { - return Ok(WebDriverJSValue::Element(WebElement( - element.upcast::().unique_id(), - ))); - } + if is_array_like(cx, val) { + let mut result: Vec = Vec::new(); - if !is_array_like(cx, val) { - return Err(WebDriverJSError::UnknownType); - } - - let mut result: Vec = Vec::new(); - - let length = - match get_property::(cx, object.handle(), "length", ConversionBehavior::Default) { + let length = match get_property::( + cx, + object.handle(), + "length", + ConversionBehavior::Default, + ) { Ok(length) => match length { Some(length) => length, _ => return Err(WebDriverJSError::UnknownType), @@ -181,21 +204,76 @@ pub unsafe fn jsval_to_webdriver( }, }; - for i in 0..length { - rooted!(in(cx) let mut item = UndefinedValue()); - match get_property_jsval(cx, object.handle(), &i.to_string(), item.handle_mut()) { - Ok(_) => match jsval_to_webdriver(cx, global_scope, item.handle()) { - Ok(converted_item) => result.push(converted_item), - err @ Err(_) => return err, - }, - Err(error) => { - throw_dom_exception(SafeJSContext::from_ptr(cx), global_scope, error); + for i in 0..length { + rooted!(in(cx) let mut item = UndefinedValue()); + match get_property_jsval(cx, object.handle(), &i.to_string(), item.handle_mut()) { + Ok(_) => match jsval_to_webdriver(cx, global_scope, item.handle()) { + Ok(converted_item) => result.push(converted_item), + err @ Err(_) => return err, + }, + Err(error) => { + throw_dom_exception(SafeJSContext::from_ptr(cx), global_scope, error); + return Err(WebDriverJSError::JSError); + }, + } + } + + Ok(WebDriverJSValue::ArrayLike(result)) + } else if let Ok(element) = root_from_object::(*object, cx) { + Ok(WebDriverJSValue::Element(WebElement( + element.upcast::().unique_id(), + ))) + } else if let Ok(window) = root_from_object::(*object, cx) { + let window_proxy = window.window_proxy(); + if window_proxy.is_browsing_context_discarded() { + Err(WebDriverJSError::StaleElementReference) + } else if window_proxy.browsing_context_id() == + window_proxy.top_level_browsing_context_id() + { + Ok(WebDriverJSValue::Window(WebWindow( + window.Document().upcast::().unique_id(), + ))) + } else { + Ok(WebDriverJSValue::Frame(WebFrame( + window.Document().upcast::().unique_id(), + ))) + } + } else if object_has_to_json_property(cx, global_scope, object.handle()) { + let name = CString::new("toJSON").unwrap(); + rooted!(in(cx) let mut value = UndefinedValue()); + if JS_CallFunctionName( + cx, + object.handle(), + name.as_ptr(), + &mut HandleValueArray::new(), + value.handle_mut(), + ) { + jsval_to_webdriver(cx, global_scope, Handle::new(&value)) + } else { + throw_dom_exception(SafeJSContext::from_ptr(cx), global_scope, Error::JSFailed); + Err(WebDriverJSError::JSError) + } + } else { + let mut result = HashMap::new(); + + let common_properties = vec!["x", "y", "width", "height", "key"]; + for property in common_properties.iter() { + rooted!(in(cx) let mut item = UndefinedValue()); + if let Ok(_) = get_property_jsval(cx, object.handle(), property, item.handle_mut()) + { + 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); return Err(WebDriverJSError::JSError); - }, + } } - } - Ok(WebDriverJSValue::ArrayLike(result)) + Ok(WebDriverJSValue::Object(result)) + } } else { Err(WebDriverJSError::UnknownType) } diff --git a/components/script_traits/webdriver_msg.rs b/components/script_traits/webdriver_msg.rs index 9716bd220d67..23fa60efddb8 100644 --- a/components/script_traits/webdriver_msg.rs +++ b/components/script_traits/webdriver_msg.rs @@ -10,7 +10,8 @@ use hyper_serde::Serde; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::BrowsingContextId; use servo_url::ServoUrl; -use webdriver::common::WebElement; +use std::collections::HashMap; +use webdriver::common::{WebElement, WebFrame, WebWindow}; use webdriver::error::ErrorStatus; #[derive(Debug, Deserialize, Serialize)] @@ -100,17 +101,21 @@ pub enum WebDriverJSValue { Number(f64), String(String), Element(WebElement), + Frame(WebFrame), + Window(WebWindow), ArrayLike(Vec), + Object(HashMap), } #[derive(Debug, Deserialize, Serialize)] pub enum WebDriverJSError { - Timeout, - UnknownType, - JSError, /// Occurs when handler received an event message for a layout channel that is not /// associated with the current script thread BrowsingContextNotFound, + JSError, + StaleElementReference, + Timeout, + UnknownType, } pub type WebDriverJSResult = Result; diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index 0bd7d140c325..3b5d223c3fbc 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -241,11 +241,18 @@ impl Serialize for SendableWebDriverJSValue { WebDriverJSValue::Number(x) => serializer.serialize_f64(x), WebDriverJSValue::String(ref x) => serializer.serialize_str(&x), WebDriverJSValue::Element(ref x) => x.serialize(serializer), + WebDriverJSValue::Frame(ref x) => x.serialize(serializer), + WebDriverJSValue::Window(ref x) => x.serialize(serializer), WebDriverJSValue::ArrayLike(ref x) => x .iter() .map(|element| SendableWebDriverJSValue(element.clone())) .collect::>() .serialize(serializer), + WebDriverJSValue::Object(ref x) => x + .iter() + .map(|(k, v)| (k.clone(), SendableWebDriverJSValue(v.clone()))) + .collect::>() + .serialize(serializer), } } } @@ -1396,18 +1403,22 @@ impl Handler { Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( serde_json::to_value(SendableWebDriverJSValue(value))?, ))), - Err(WebDriverJSError::Timeout) => Err(WebDriverError::new(ErrorStatus::Timeout, "")), - Err(WebDriverJSError::UnknownType) => Err(WebDriverError::new( - ErrorStatus::UnsupportedOperation, - "Unsupported return type", + Err(WebDriverJSError::BrowsingContextNotFound) => Err(WebDriverError::new( + ErrorStatus::JavascriptError, + "Pipeline id not found in browsing context", )), Err(WebDriverJSError::JSError) => Err(WebDriverError::new( ErrorStatus::JavascriptError, "JS evaluation raised an exception", )), - Err(WebDriverJSError::BrowsingContextNotFound) => Err(WebDriverError::new( - ErrorStatus::JavascriptError, - "Pipeline id not found in browsing context", + Err(WebDriverJSError::StaleElementReference) => Err(WebDriverError::new( + ErrorStatus::StaleElementReference, + "Stale element", + )), + Err(WebDriverJSError::Timeout) => Err(WebDriverError::new(ErrorStatus::Timeout, "")), + Err(WebDriverJSError::UnknownType) => Err(WebDriverError::new( + ErrorStatus::UnsupportedOperation, + "Unsupported return type", )), } } diff --git a/tests/wpt/metadata/webdriver/tests/execute_script/cyclic.py.ini b/tests/wpt/metadata/webdriver/tests/execute_script/cyclic.py.ini index 15a6f71fb269..81f7b00c745e 100644 --- a/tests/wpt/metadata/webdriver/tests/execute_script/cyclic.py.ini +++ b/tests/wpt/metadata/webdriver/tests/execute_script/cyclic.py.ini @@ -1,10 +1,4 @@ [cyclic.py] - [test_object] - expected: FAIL - - [test_array_in_object] - expected: FAIL - [test_element_in_object] expected: FAIL diff --git a/tests/wpt/metadata/webdriver/tests/get_element_property/get.py.ini b/tests/wpt/metadata/webdriver/tests/get_element_property/get.py.ini index d6b7c0acee80..8d73d60b8658 100644 --- a/tests/wpt/metadata/webdriver/tests/get_element_property/get.py.ini +++ b/tests/wpt/metadata/webdriver/tests/get_element_property/get.py.ini @@ -1,7 +1,4 @@ [get.py] - [test_primitives[js_primitive3-py_primitive3\]] - expected: FAIL - [test_idl_attribute] expected: FAIL @@ -17,9 +14,8 @@ [test_no_browsing_context] expected: ERROR - [test_primitives_set_by_execute_script[js_primitive3-py_primitive3\]] - expected: FAIL - [test_primitives_set_by_execute_script["foobar"-foobar\]] expected: FAIL + [test_primitives_set_by_execute_script[js_primitive3-py_primitive3\]] + expected: FAIL diff --git a/tests/wpt/metadata/webdriver/tests/get_window_rect/get.py.ini b/tests/wpt/metadata/webdriver/tests/get_window_rect/get.py.ini index 810217c99ac0..6cf1e1da10a2 100644 --- a/tests/wpt/metadata/webdriver/tests/get_window_rect/get.py.ini +++ b/tests/wpt/metadata/webdriver/tests/get_window_rect/get.py.ini @@ -2,6 +2,3 @@ [test_no_browsing_context] expected: ERROR - [test_payload] - expected: FAIL -