Bug 1107706: Part 8: Adapt emulator callbacks
authorAndreas Tolfsen <ato@mozilla.com>
Tue, 17 Mar 2015 16:10:58 +0000
changeset 264851 4ef84ad178b563d452e5561b91e119705d987163
parent 264850 853e7da581097725fa815a95bb92172ef6f5dcce
child 264852 6af978a7ee3238b1794fe15969ab7f97287dfb81
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);
-  },
 };