Bug 1274274 - Disassociate wrapValue from element store; r?automatedtester draft
authorAndreas Tolfsen <ato@mozilla.com>
Fri, 20 May 2016 16:32:31 +0100
changeset 370721 5ae2bb04a6293f9afb2de4e5874b8b30c0838423
parent 370720 1e4a81749eebfc0aa2d0090621adb3a6eaba7423
child 370722 322e00b1f661a56c800ce120831818d335888d4b
push id19137
push userbmo:ato@mozilla.com
push dateWed, 25 May 2016 09:03:54 +0000
reviewersautomatedtester
bugs1274274
milestone49.0a1
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
testing/marionette/driver.js
testing/marionette/element.js
testing/marionette/listener.js
--- 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};