Bug 1274274 - Disassociate wrapValue from element store; r=automatedtester
authorAndreas Tolfsen <ato@mozilla.com>
Fri, 20 May 2016 16:32:31 +0100
changeset 337936 dd338221d47080318113d6a908ac7651d11b5ce3
parent 337935 e848faf4bb29bc0195b11ba682cfe9322d841b53
child 337937 dc85479726f9a1ad5cfbbc873b0168bedbd839f6
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersautomatedtester
bugs1274274
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
@@ -876,17 +876,17 @@ GeckoDriver.prototype.execute_ = functio
         sb = sandbox.augment(sb, new logging.Adapter(this.marionetteLog));
         sb = sandbox.augment(sb, {global: sb});
       }
 
       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.
@@ -913,17 +913,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
@@ -524,17 +524,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) {
@@ -542,18 +542,20 @@ function* executeInSandbox(script, args,
     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);
 }
 
 function* executeSimpleTest(script, args, timeout, opts) {
   opts.timeout = timeout;
   let win = curContainer.frame;
   script = importedScripts.for("content").concat(script);
 
   let harness = new simpletest.Harness(
@@ -566,18 +568,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);
@@ -629,18 +633,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);
   }
 }
 
 /**
@@ -1427,17 +1432,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};