Bug 1107706: Part 8: Adapt emulator callbacks
☠☠ backed out by 4c2c50594967 ☠ ☠
authorAndreas Tolfsen <ato@mozilla.com>
Tue, 17 Mar 2015 16:10:58 +0000
changeset 264218 9972f443d70eb15554965978301d204003a5f431
parent 264217 20f9b7b24fc50c83afd07eced7a1678a53185c45
child 264219 0c6e1484ae7a6f082c2037f9c6e5e705874dd33b
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1107706
milestone39.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 1107706: Part 8: Adapt emulator callbacks Emulator callbacks are now created dynamically upon request, and uses a nicer data structure in chrome context. Each emulator callback is encapsulated in EmulatorCallback, and stored on Emulator. Emulator is stored on Dispatcher (as opposed to in marionette-server.js) which bypasses some of the problems with circumventing the Marionette protocol in GeckoDriver because of CommandProcessor. Emulator callbacks to the client should be considered transparent, hence they do not use the ListenerProxy. They are explicitly meant _not_ to be blocking.
testing/marionette/emulator.js
testing/marionette/marionette-simpletest.js
new file mode 100644
--- /dev/null
+++ b/testing/marionette/emulator.js
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci} = Components;
+const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+this.EXPORTED_SYMBOLS = ["emulator", "Emulator", "EmulatorCallback"];
+
+this.emulator = {};
+
+/**
+ * Determines if command ID is an emulator callback.
+ */
+this.emulator.isCallback = function(cmdId) {
+  return cmdId < 0;
+};
+
+/**
+ * Represents the connection between Marionette and the emulator it's
+ * running on.
+ *
+ * When injected scripts call the JS routines {@code runEmulatorCmd} or
+ * {@code runEmulatorShell}, the second argument to those is a callback
+ * which is stored in cbs.  They are later retreived by their unique ID
+ * using popCallback.
+ *
+ * @param {function(Object)} sendFn
+ *     Callback function that sends a message to the emulator.
+ */
+this.Emulator = function(sendFn) {
+  this.send = sendFn;
+  this.cbs = [];
+};
+
+/**
+ * Pops a callback off the stack if found.  Otherwise this is a no-op.
+ *
+ * @param {number} id
+ *     Unique ID associated with the callback.
+ *
+ * @return {?function(Object)}
+ *     Callback function that takes an emulator response message as
+ *     an argument.
+ */
+Emulator.prototype.popCallback = function(id) {
+  let f, fi;
+  for (let i = 0; i < this.cbs.length; ++i) {
+    if (this.cbs[i].id == id) {
+      f = this.cbs[i];
+      fi = i;
+    }
+  }
+
+  if (!f)
+    return null;
+
+  this.cbs.splice(fi, 1);
+  return f;
+};
+
+/**
+ * Pushes callback on to the stack.
+ *
+ * @param {function(Object)} cb
+ *     Callback function that takes an emulator response message as
+ *     an argument.
+ */
+Emulator.prototype.pushCallback = function(cb) {
+  cb.send_ = this.sendFn;
+  this.cbs.push(cb);
+};
+
+/**
+ * Encapsulates a callback to the emulator and provides an execution
+ * environment for them.
+ *
+ * Each callback is assigned a unique identifier, id, that can be used
+ * to retrieve them from Emulator's stack using popCallback.
+ *
+ * The onresult event listener is triggered when a result arrives on
+ * the callback.
+ *
+ * The onerror event listener is triggered when an error occurs during
+ * the execution of that callback.
+ */
+this.EmulatorCallback = function() {
+  this.id = uuidGen.generateUUID().toString();
+  this.onresult = null;
+  this.onerror = null;
+  this.send_ = null;
+};
+
+EmulatorCallback.prototype.command = function(cmd, cb) {
+  this.onresult = cb;
+  this.send_({emulator_cmd: cmd, id: this.id});
+};
+
+EmulatorCallback.prototype.shell = function(args, cb) {
+  this.onresult = cb;
+  this.send_({emulator_shell: args, id: this.id});
+};
+
+EmulatorCallback.prototype.result = function(msg) {
+  if (this.send_ === null)
+    throw new TypeError(
+      "EmulatorCallback must be registered with Emulator to fire");
+
+  try {
+    if (!this.onresult)
+      return;
+    this.onresult(msg.result);
+  } catch (e) {
+    if (this.onerror)
+      this.onerror(e);
+  }
+};
--- a/testing/marionette/marionette-simpletest.js
+++ b/testing/marionette/marionette-simpletest.js
@@ -1,12 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+let {utils: Cu} = Components;
+
+Cu.import("chrome://marionette/content/error.js");
+
 this.EXPORTED_SYMBOLS = ["Marionette"];
 
 /*
  * The Marionette object, passed to the script context.
  */
 this.Marionette = function(scope, window, context, logObj, timeout,
                            heartbeatCallback, testName) {
   this.scope = scope;
@@ -192,19 +196,9 @@ Marionette.prototype = {
       if (deadline <= now) {
         dump("waitFor timeout: " + test.toString() + "\n");
         // the script will timeout here, so no need to raise a separate
         // timeout exception
         return;
       }
       this.window.setTimeout(this.waitFor.bind(this), 100, callback, test, deadline);
   },
-
-  runEmulatorCmd: function runEmulatorCmd(cmd, callback) {
-    this.heartbeatCallback();
-    this.scope.runEmulatorCmd(cmd, callback);
-  },
-
-  runEmulatorShell: function runEmulatorShell(args, callback) {
-    this.heartbeatCallback();
-    this.scope.runEmulatorShell(args, callback);
-  },
 };