Bug 1274274 - Disassociate wrapValue from element store; r?automatedtester
Moves ElementManager#wrapValue to the testing/marionette/element.js
module level and renames it to toJson.
MozReview-Commit-ID: GJBl2L1GRxZ
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -884,17 +884,17 @@ GeckoDriver.prototype.execute_ = functio
sb = sandbox.augment(sb, {global: sb});
sb = sandbox.augment(sb, new emulator.Adapter(this.emulator));
}
opts.timeout = timeout;
script = this.importedScripts.for(Context.CHROME).concat(script);
let wargs = element.fromJson(args, this.curBrowser.elementManager, sb.window);
let evaluatePromise = evaluate.sandbox(sb, script, wargs, opts);
- return evaluatePromise.then(res => this.curBrowser.elementManager.wrapValue(res));
+ return evaluatePromise.then(res => element.toJson(res, this.curBrowser.elementManager));
}
};
/**
* Execute pure JavaScript. Used to execute simpletest harness tests,
* which are like mochitests only injected using Marionette.
*
* Scripts are expected to call the {@code finish} global when done.
@@ -921,17 +921,17 @@ GeckoDriver.prototype.executeJSScript =
function() {},
this.testName);
let sb = sandbox.createSimpleTest(win, harness);
// TODO(ato): Not sure this is needed:
sb = sandbox.augment(sb, new logging.Adapter(this.marionetteLog));
let res = yield evaluate.sandbox(sb, script, wargs, opts);
- resp.body.value = this.curBrowser.elementManager.wrapValue(res);
+ resp.body.value = element.toJson(res, this.curBrowser.elementManager);
break;
case Context.CONTENT:
resp.body.value = yield this.listener.executeSimpleTest(script, args, scriptTimeout, opts);
break;
}
};
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -183,78 +183,16 @@ element.Store = class {
element.isDisconnected(wrappedEl, wrappedFrame, wrappedShadowRoot)) {
throw new StaleElementReferenceError(
"The element reference is stale. Either the element " +
"is no longer attached to the DOM or the page has been refreshed.");
}
return el;
}
-
- /**
- * Convert values to primitives that can be transported over the
- * Marionette protocol.
- *
- * This function implements the marshaling algorithm defined in the
- * WebDriver specification:
- *
- * https://dvcs.w3.org/hg/webdriver/raw-file/tip/webdriver-spec.html#synchronous-javascript-execution
- *
- * @param object val
- * object to be marshaled
- *
- * @return object
- * Returns a JSON primitive or Object
- */
- wrapValue(val) {
- let result = null;
-
- switch (typeof(val)) {
- case "undefined":
- result = null;
- break;
-
- case "string":
- case "number":
- case "boolean":
- result = val;
- break;
-
- case "object":
- let type = Object.prototype.toString.call(val);
- if (type == "[object Array]" ||
- type == "[object NodeList]") {
- result = [];
- for (let i = 0; i < val.length; ++i) {
- result.push(this.wrapValue(val[i]));
-
- }
- }
- else if (val == null) {
- result = null;
- }
- else if (val.nodeType == 1) {
- let elementId = this.add(val);
- result = {[element.LegacyKey]: elementId, [element.Key]: elementId};
- }
- else {
- result = {};
- for (let prop in val) {
- try {
- result[prop] = this.wrapValue(val[prop]);
- } catch (e if (e.result == Cr.NS_ERROR_NOT_IMPLEMENTED)) {
- logger.debug(`Skipping ${prop} due to: ${e.message}`);
- }
- }
- }
- break;
- }
-
- return result;
- }
};
/**
* Find a single element or a collection of elements starting at the
* document root or a given node.
*
* If |timeout| is above 0, an implicit search technique is used.
* This will wait for the duration of |timeout| for the element
@@ -669,17 +607,17 @@ element.generateUUID = function() {
* Shadow root.
*
* @return {?}
* Same object as provided by |obj| with the web elements replaced
* by DOM elements.
*/
element.fromJson = function(
obj, seenEls, win, shadowRoot = undefined) {
- switch (typeof(obj)) {
+ switch (typeof obj) {
case "boolean":
case "number":
case "string":
return obj;
case "object":
if (obj === null) {
return obj;
@@ -708,16 +646,73 @@ element.fromJson = function(
rv[prop] = element.fromJson(obj[prop], seenEls, win, shadowRoot);
}
return rv;
}
}
};
/**
+ * Convert 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.
+ *
+ * @param {?} obj
+ * Object to be marshaled.
+ * @param {element.Store} seenEls
+ * Element store to use for lookup of web element references.
+ *
+ * @return {?}
+ * Same object as provided by |obj| with the elements replaced by
+ * web elements.
+ */
+element.toJson = function(obj, seenEls) {
+ switch (typeof obj) {
+ case "undefined":
+ return null;
+
+ case "boolean":
+ case "number":
+ case "string":
+ return obj;
+
+ case "object":
+ if (obj === null) {
+ return obj;
+ }
+
+ // NodeList, HTMLCollection
+ else if (element.isElementCollection(obj)) {
+ return [...obj].map(el => element.toJson(el, seenEls));
+ }
+
+ // DOM element
+ else if (obj.nodeType == 1) {
+ let uuid = seenEls.add(obj);
+ return {[element.Key]: uuid, [element.LegacyKey]: uuid};
+ }
+
+ // arbitrary objects
+ else {
+ let rv = {};
+ for (let prop in obj) {
+ try {
+ rv[prop] = element.toJson(obj[prop], seenEls);
+ } catch (e if (e.result == Cr.NS_ERROR_NOT_IMPLEMENTED)) {
+ logger.debug(`Skipping ${prop}: ${e.message}`);
+ }
+ }
+ return rv;
+ }
+ }
+};
+
+/**
* Check if the element is detached from the current frame as well as
* the optional shadow root (when inside a Shadow DOM context).
*
* @param {nsIDOMElement} el
* Element to be checked.
* @param nsIDOMWindow frame
* Window object that contains the element or the current host
* of the shadow root.
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -525,17 +525,17 @@ function* execute(script, args, timeout,
opts.timeout = timeout;
script = importedScripts.for("content").concat(script);
let sb = sandbox.createMutable(curContainer.frame);
let wargs = element.fromJson(
args, elementManager, curContainer.frame, curContainer.shadowRoot);
let res = yield evaluate.sandbox(sb, script, wargs, opts);
- return elementManager.wrapValue(res);
+ return element.toJson(res, elementManager);
}
function* executeInSandbox(script, args, timeout, opts) {
opts.timeout = timeout;
script = importedScripts.for("content").concat(script);
let sb = sandboxes.get(opts.sandboxName, opts.newSandbox);
if (opts.sandboxName) {
@@ -545,18 +545,20 @@ function* executeInSandbox(script, args,
sb = sandbox.augment(sb, new emulator.Adapter(emulatorClient));
}
let wargs = element.fromJson(
args, elementManager, curContainer.frame, curContainer.shadowRoot);
let evaluatePromise = evaluate.sandbox(sb, script, wargs, opts);
let res = yield evaluatePromise;
- sendSyncMessage("Marionette:shareData", {log: elementManager.wrapValue(contentLog.get())});
- return elementManager.wrapValue(res);
+ sendSyncMessage(
+ "Marionette:shareData",
+ {log: element.toJson(contentLog.get(), elementManager)});
+ return element.toJson(res, elementManager);
}
function* executeSimpleTest(script, args, timeout, opts) {
opts.timeout = timeout;
let win = curContainer.frame;
script = importedScripts.for("content").concat(script);
let harness = new simpletest.Harness(
@@ -569,18 +571,20 @@ function* executeSimpleTest(script, args
// TODO(ato): Not sure this is needed:
sb = sandbox.augment(sb, new logging.Adapter(contentLog));
let wargs = element.fromJson(
args, elementManager, curContainer.frame, curContainer.shadowRoot);
let evaluatePromise = evaluate.sandbox(sb, script, wargs, opts);
let res = yield evaluatePromise;
- sendSyncMessage("Marionette:shareData", {log: elementManager.wrapValue(contentLog.get())});
- return elementManager.wrapValue(res);
+ sendSyncMessage(
+ "Marionette:shareData",
+ {log: element.toJson(contentLog.get(), elementManager)});
+ return element.toJson(res, elementManager);
}
/**
* Sets the test name, used in logging messages.
*/
function setTestName(msg) {
marionetteTestName = msg.json.value;
sendOk(msg.json.command_id);
@@ -632,18 +636,19 @@ function emitTouchEvent(type, touch) {
rotation: touch.rotationAngle, force: touch.force });
return;
}
}
// we get here if we're not in asyncPacZoomEnabled land, or if we're the main process
/*
Disabled per bug 888303
contentLog.log(loggingInfo, "TRACE");
- sendSyncMessage("Marionette:shareData",
- {log: elementManager.wrapValue(contentLog.get())});
+ sendSyncMessage(
+ "Marionette:shareData",
+ {log: element.toJson(contentLog.get(), elementManager)});
contentLog.clear();
*/
let domWindowUtils = curContainer.frame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
}
}
/**
@@ -1430,17 +1435,18 @@ function switchToFrame(msg) {
if (foundFrame === null) {
sendError(new NoSuchFrameError("Unable to locate frame: " + (msg.json.id || msg.json.element)), command_id);
return true;
}
// send a synchronous message to let the server update the currently active
// frame element (for getActiveFrame)
- let frameValue = elementManager.wrapValue(curContainer.frame.wrappedJSObject).ELEMENT;
+ let frameValue = element.toJson(
+ curContainer.frame.wrappedJSObject, elementManager)[element.Key];
sendSyncMessage("Marionette:switchedToFrame", {frameValue: frameValue});
let rv = null;
if (curContainer.frame.contentWindow === null) {
// The frame we want to switch to is a remote/OOP frame;
// notify our parent to handle the switch
curContainer.frame = content;
rv = {win: parWindow, frame: foundFrame};