Finish the JSON clone algorithm

This commit is contained in:
George Roman 2019-08-11 00:37:24 +03:00
parent 17f423723c
commit c665a65690
6 changed files with 137 additions and 56 deletions

View file

@ -18,7 +18,7 @@ use crate::dom::bindings::conversions::{
use crate::dom::bindings::conversions::{ use crate::dom::bindings::conversions::{
ConversionBehavior, ConversionResult, FromJSValConvertible, StringificationBehavior, 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::inheritance::Castable;
use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::root::DomRoot;
@ -39,9 +39,10 @@ use cookie::Cookie;
use euclid::default::{Point2D, Rect, Size2D}; use euclid::default::{Point2D, Rect, Size2D};
use hyper_serde::Serde; use hyper_serde::Serde;
use ipc_channel::ipc::{self, IpcSender}; 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::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::BrowsingContextId;
use msg::constellation_msg::PipelineId; use msg::constellation_msg::PipelineId;
use net_traits::CookieSource::{NonHTTP, HTTP}; use net_traits::CookieSource::{NonHTTP, HTTP};
@ -52,7 +53,9 @@ use script_traits::webdriver_msg::{
WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue, WebDriverFrameId, WebDriverJSError, WebDriverJSResult, WebDriverJSValue,
}; };
use servo_url::ServoUrl; 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; use webdriver::error::ErrorStatus;
fn find_node_by_unique_id( 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()) .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)] #[allow(unsafe_code)]
pub unsafe fn jsval_to_webdriver( pub unsafe fn jsval_to_webdriver(
cx: *mut JSContext, cx: *mut JSContext,
@ -157,20 +185,15 @@ pub unsafe fn jsval_to_webdriver(
}); });
let _ac = JSAutoRealm::new(cx, *object); let _ac = JSAutoRealm::new(cx, *object);
if let Ok(element) = root_from_object::<HTMLElement>(*object, cx) { if is_array_like(cx, val) {
return Ok(WebDriverJSValue::Element(WebElement( let mut result: Vec<WebDriverJSValue> = Vec::new();
element.upcast::<Node>().unique_id(),
)));
}
if !is_array_like(cx, val) { let length = match get_property::<u32>(
return Err(WebDriverJSError::UnknownType); cx,
} object.handle(),
"length",
let mut result: Vec<WebDriverJSValue> = Vec::new(); ConversionBehavior::Default,
) {
let length =
match get_property::<u32>(cx, object.handle(), "length", ConversionBehavior::Default) {
Ok(length) => match length { Ok(length) => match length {
Some(length) => length, Some(length) => length,
_ => return Err(WebDriverJSError::UnknownType), _ => return Err(WebDriverJSError::UnknownType),
@ -181,21 +204,76 @@ pub unsafe fn jsval_to_webdriver(
}, },
}; };
for i in 0..length { for i in 0..length {
rooted!(in(cx) let mut item = UndefinedValue()); rooted!(in(cx) let mut item = UndefinedValue());
match get_property_jsval(cx, object.handle(), &i.to_string(), item.handle_mut()) { match get_property_jsval(cx, object.handle(), &i.to_string(), item.handle_mut()) {
Ok(_) => match jsval_to_webdriver(cx, global_scope, item.handle()) { Ok(_) => match jsval_to_webdriver(cx, global_scope, item.handle()) {
Ok(converted_item) => result.push(converted_item), Ok(converted_item) => result.push(converted_item),
err @ Err(_) => return err, err @ Err(_) => return err,
}, },
Err(error) => { Err(error) => {
throw_dom_exception(SafeJSContext::from_ptr(cx), global_scope, error); throw_dom_exception(SafeJSContext::from_ptr(cx), global_scope, error);
return Err(WebDriverJSError::JSError); return Err(WebDriverJSError::JSError);
}, },
}
} }
}
Ok(WebDriverJSValue::ArrayLike(result)) Ok(WebDriverJSValue::ArrayLike(result))
} else if let Ok(element) = root_from_object::<Element>(*object, cx) {
Ok(WebDriverJSValue::Element(WebElement(
element.upcast::<Node>().unique_id(),
)))
} else if let Ok(window) = root_from_object::<Window>(*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::<Node>().unique_id(),
)))
} else {
Ok(WebDriverJSValue::Frame(WebFrame(
window.Document().upcast::<Node>().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::Object(result))
}
} else { } else {
Err(WebDriverJSError::UnknownType) Err(WebDriverJSError::UnknownType)
} }

View file

@ -10,7 +10,8 @@ use hyper_serde::Serde;
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::IpcSender;
use msg::constellation_msg::BrowsingContextId; use msg::constellation_msg::BrowsingContextId;
use servo_url::ServoUrl; use servo_url::ServoUrl;
use webdriver::common::WebElement; use std::collections::HashMap;
use webdriver::common::{WebElement, WebFrame, WebWindow};
use webdriver::error::ErrorStatus; use webdriver::error::ErrorStatus;
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
@ -100,17 +101,21 @@ pub enum WebDriverJSValue {
Number(f64), Number(f64),
String(String), String(String),
Element(WebElement), Element(WebElement),
Frame(WebFrame),
Window(WebWindow),
ArrayLike(Vec<WebDriverJSValue>), ArrayLike(Vec<WebDriverJSValue>),
Object(HashMap<String, WebDriverJSValue>),
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
pub enum WebDriverJSError { pub enum WebDriverJSError {
Timeout,
UnknownType,
JSError,
/// 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,
JSError,
StaleElementReference,
Timeout,
UnknownType,
} }
pub type WebDriverJSResult = Result<WebDriverJSValue, WebDriverJSError>; pub type WebDriverJSResult = Result<WebDriverJSValue, WebDriverJSError>;

View file

@ -241,11 +241,18 @@ impl Serialize for SendableWebDriverJSValue {
WebDriverJSValue::Number(x) => serializer.serialize_f64(x), WebDriverJSValue::Number(x) => serializer.serialize_f64(x),
WebDriverJSValue::String(ref x) => serializer.serialize_str(&x), WebDriverJSValue::String(ref x) => serializer.serialize_str(&x),
WebDriverJSValue::Element(ref x) => x.serialize(serializer), 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 WebDriverJSValue::ArrayLike(ref x) => x
.iter() .iter()
.map(|element| SendableWebDriverJSValue(element.clone())) .map(|element| SendableWebDriverJSValue(element.clone()))
.collect::<Vec<SendableWebDriverJSValue>>() .collect::<Vec<SendableWebDriverJSValue>>()
.serialize(serializer), .serialize(serializer),
WebDriverJSValue::Object(ref x) => x
.iter()
.map(|(k, v)| (k.clone(), SendableWebDriverJSValue(v.clone())))
.collect::<HashMap<String, SendableWebDriverJSValue>>()
.serialize(serializer),
} }
} }
} }
@ -1396,18 +1403,22 @@ impl Handler {
Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse( Ok(value) => Ok(WebDriverResponse::Generic(ValueResponse(
serde_json::to_value(SendableWebDriverJSValue(value))?, serde_json::to_value(SendableWebDriverJSValue(value))?,
))), ))),
Err(WebDriverJSError::Timeout) => Err(WebDriverError::new(ErrorStatus::Timeout, "")), Err(WebDriverJSError::BrowsingContextNotFound) => Err(WebDriverError::new(
Err(WebDriverJSError::UnknownType) => Err(WebDriverError::new( ErrorStatus::JavascriptError,
ErrorStatus::UnsupportedOperation, "Pipeline id not found in browsing context",
"Unsupported return type",
)), )),
Err(WebDriverJSError::JSError) => Err(WebDriverError::new( Err(WebDriverJSError::JSError) => Err(WebDriverError::new(
ErrorStatus::JavascriptError, ErrorStatus::JavascriptError,
"JS evaluation raised an exception", "JS evaluation raised an exception",
)), )),
Err(WebDriverJSError::BrowsingContextNotFound) => Err(WebDriverError::new( Err(WebDriverJSError::StaleElementReference) => Err(WebDriverError::new(
ErrorStatus::JavascriptError, ErrorStatus::StaleElementReference,
"Pipeline id not found in browsing context", "Stale element",
)),
Err(WebDriverJSError::Timeout) => Err(WebDriverError::new(ErrorStatus::Timeout, "")),
Err(WebDriverJSError::UnknownType) => Err(WebDriverError::new(
ErrorStatus::UnsupportedOperation,
"Unsupported return type",
)), )),
} }
} }

View file

@ -1,10 +1,4 @@
[cyclic.py] [cyclic.py]
[test_object]
expected: FAIL
[test_array_in_object]
expected: FAIL
[test_element_in_object] [test_element_in_object]
expected: FAIL expected: FAIL

View file

@ -1,7 +1,4 @@
[get.py] [get.py]
[test_primitives[js_primitive3-py_primitive3\]]
expected: FAIL
[test_idl_attribute] [test_idl_attribute]
expected: FAIL expected: FAIL
@ -17,9 +14,8 @@
[test_no_browsing_context] [test_no_browsing_context]
expected: ERROR expected: ERROR
[test_primitives_set_by_execute_script[js_primitive3-py_primitive3\]]
expected: FAIL
[test_primitives_set_by_execute_script["foobar"-foobar\]] [test_primitives_set_by_execute_script["foobar"-foobar\]]
expected: FAIL expected: FAIL
[test_primitives_set_by_execute_script[js_primitive3-py_primitive3\]]
expected: FAIL

View file

@ -2,6 +2,3 @@
[test_no_browsing_context] [test_no_browsing_context]
expected: ERROR expected: ERROR
[test_payload]
expected: FAIL