Bug 1106913 - Detect cyclic objects when marshaling objects. r=whimboo

Marionette does currently not test for cyclic object references as
it marshals return values for transport across the wire.

Example of cyclic object:

	let obj = {};
	obj.cyclic = obj;

Passing this through evalaute.toJSON currently causes an infinite
recursion due to obj being referenced inside itself.  We can use
JSON.stringify to test if obj contains such cyclic values.  It is
assumed that the input to assert.acyclic is already JSON safe, so it can
be parsed by JSON.stringify, because of the previous checks it has made.

MozReview-Commit-ID: 4CnY2dcW5IF

--HG--
extra : rebase_source : e1a5fb595ad487fa47566bad5c2129a79c1d7b34
This commit is contained in:
Andreas Tolfsen 2017-11-24 18:21:17 +00:00
Родитель 17e02f9dbb
Коммит 7fe6af9075
5 изменённых файлов: 98 добавлений и 9 удалений

Просмотреть файл

@ -11,6 +11,7 @@ Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("chrome://marionette/content/assert.js");
const {
element,
WebElement,
@ -243,11 +244,37 @@ evaluate.fromJSON = function(obj, seenEls = undefined, window = undefined) {
};
/**
* Convert arbitrary objects to JSON-safe primitives that can be
* Marshal arbitrary objects to JSON-safe primitives that can be
* transported over the Marionette protocol.
*
* Any DOM elements are converted to web elements by looking them up
* and/or adding them to the element store provided.
* The marshaling rules are as follows:
*
* <ul>
*
* <li>
* Primitives are returned as is.
*
* <li>
* Collections, such as <code>Array</code>, <code>NodeList</code>,
* <code>HTMLCollection</code> et al. are expanded to arrays and
* then recursed.
*
* <li>
* Elements that are not known web elements are added to the
* <var>seenEls</var> element store. Once known, the elements'
* associated web element representation is returned.
*
* <li>
* Objects with custom JSON representations, i.e. if they have a
* callable <code>toJSON</code> function, are returned verbatim.
* This means their internal integrity <em>are not</em> checked.
* Be careful.
*
* <li>
* Other arbitrary objects are first tested for cyclic references
* and then recursed into.
*
* </ul>
*
* @param {Object} obj
* Object to be marshaled.
@ -257,6 +284,9 @@ evaluate.fromJSON = function(obj, seenEls = undefined, window = undefined) {
* @return {Object}
* Same object as provided by <var>obj</var> with the elements
* replaced by web elements.
*
* @throws {JavaScriptError}
* If an object contains cyclic references.
*/
evaluate.toJSON = function(obj, seenEls) {
const t = Object.prototype.toString.call(obj);
@ -273,13 +303,14 @@ evaluate.toJSON = function(obj, seenEls) {
// Array, NodeList, HTMLCollection, et al.
} else if (element.isCollection(obj)) {
assert.acyclic(obj);
return [...obj].map(el => evaluate.toJSON(el, seenEls));
// WebElement
} else if (WebElement.isReference(obj)) {
return obj;
// Element (HTMLElement, SVGElement, XULElement, &c.)
// Element (HTMLElement, SVGElement, XULElement, et al.)
} else if (element.isElement(obj)) {
let webEl = seenEls.add(obj);
return webEl.toJSON();
@ -293,6 +324,8 @@ evaluate.toJSON = function(obj, seenEls) {
// arbitrary objects + files
let rv = {};
for (let prop in obj) {
assert.acyclic(obj[prop]);
try {
rv[prop] = evaluate.toJSON(obj[prop], seenEls);
} catch (e) {

Просмотреть файл

@ -373774,6 +373774,12 @@
{}
]
],
"webdriver/tests/execute_script/cyclic.py": [
[
"/webdriver/tests/execute_script/cyclic.py",
{}
]
],
"webdriver/tests/execute_script/user_prompts.py": [
[
"/webdriver/tests/execute_script/user_prompts.py",
@ -575624,7 +575630,7 @@
"support"
],
"wasm/wasm_local_iframe_test.html": [
"dd715a4da792b9d8d634536d938b278230c66df5",
"e7b86a731b7cf7c122a1e37118cebce7342291fc",
"testharness"
],
"wasm/wasm_serialization_tests.html": [
@ -576432,7 +576438,7 @@
"wdspec"
],
"webdriver/tests/actions/modifier_click.py": [
"be31579cae0cb3dd26a913ce0d966be72fd79495",
"a41f28b359c950af698be51ef35e4d78dca53e2c",
"wdspec"
],
"webdriver/tests/actions/mouse.py": [
@ -576547,6 +576553,10 @@
"e31edd4537f9b7479a348465154381f5b18f938c",
"wdspec"
],
"webdriver/tests/execute_script/cyclic.py": [
"cbebfbd2413ea0b10f547ab66fcc7159898e684a",
"wdspec"
],
"webdriver/tests/execute_script/user_prompts.py": [
"901487f8270dcce693867ca090393e093d26f22b",
"wdspec"

Просмотреть файл

@ -1,5 +1,5 @@
[json_serialize_windowproxy.py]
expected: TIMEOUT
[json_serialize_windowproxy.py::test_initial_window]
expected: FAIL

Просмотреть файл

@ -1,6 +1,4 @@
[user_prompts.py]
expected: ERROR
[user_prompts.py::test_handle_prompt_accept]
expected: FAIL

Просмотреть файл

@ -0,0 +1,48 @@
from tests.support.asserts import assert_error
def execute_script(session, script, args=None):
if args is None:
args = []
body = {"script": script, "args": args}
return session.transport.send(
"POST",
"/session/{session_id}/execute/sync".format(
session_id=session.session_id),
body)
def test_array(session):
response = execute_script(session, """
let arr = [];
arr.push(arr);
return arr;
""")
assert_error(response, "javascript error")
def test_object(session):
response = execute_script(session, """
let obj = {};
obj.reference = obj;
return obj;
""")
assert_error(response, "javascript error")
def test_array_in_object(session):
response = execute_script(session, """
let arr = [];
arr.push(arr);
return {arr};
""")
assert_error(response, "javascript error")
def test_object_in_array(session):
response = execute_script(session, """
let obj = {};
obj.reference = obj;
return [obj];
""")
assert_error(response, "javascript error")