Bug 712643 - land Marionette on m-c. r=mossop,robcee
authorJoel Maher <jmaher@mozilla.com>
Thu, 22 Mar 2012 11:19:57 -0400
changeset 93358 e5fc0c1f7adfdbd73d46507ca74f628aad8d20c9
parent 93357 54136d44055bf6d43ec995639c9c0fa341e603ca
child 93359 beb523be92e1b541b766d507d14114938ff7fd20
push idunknown
push userunknown
push dateunknown
reviewersmossop, robcee
bugs712643
milestone14.0a1
Bug 712643 - land Marionette on m-c. r=mossop,robcee
allmakefiles.sh
b2g/app/b2g.js
b2g/confvars.sh
b2g/installer/package-manifest.in
config/autoconf.mk.in
configure.in
testing/marionette/Makefile.in
testing/marionette/components/Makefile.in
testing/marionette/components/MarionetteComponents.manifest
testing/marionette/components/marionettecomponent.js
testing/marionette/jar.mn
testing/marionette/marionette-actors.js
testing/marionette/marionette-elements.js
testing/marionette/marionette-listener.js
testing/marionette/marionette-log-obj.js
testing/marionette/marionette-simpletest.js
testing/marionette/tests/Makefile.in
testing/marionette/tests/unit/head_mar.js
testing/marionette/tests/unit/test_marionette_err.js
testing/marionette/tests/unit/test_marionette_exec.js
testing/marionette/tests/unit/test_marionette_execAsync.js
testing/marionette/tests/unit/test_marionette_execjs.js
testing/marionette/tests/unit/xpcshell.ini
testing/xpcshell/xpcshell.ini
toolkit/toolkit-tiers.mk
--- a/allmakefiles.sh
+++ b/allmakefiles.sh
@@ -117,16 +117,24 @@ if [ "$OS_ARCH" != "WINNT" -a "$OS_ARCH"
 fi
 
 if [ "$COMPILER_DEPEND" = "" -a "$MOZ_NATIVE_MAKEDEPEND" = "" ]; then
   add_makefiles "
     config/mkdepend/Makefile
   "
 fi
 
+if [ "$ENABLE_MARIONETTE" ]; then
+  add_makefiles "
+    testing/marionette/Makefile
+    testing/marionette/components/Makefile
+    testing/marionette/tests/Makefile
+  "
+fi
+
 if [ "$ENABLE_TESTS" ]; then
   add_makefiles "
     build/autoconf/test/Makefile
   "
   if [ ! "$LIBXUL_SDK" ]; then 
     add_makefiles "
       mozglue/tests/Makefile
     "
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -450,16 +450,20 @@ pref("media.volume.steps", 10);
 // navigator.settings API, or even in a database where we can look
 // it up automatically (bug 729440), but for this will have to do.
 pref("ril.data.enabled", false);
 pref("ril.data.roaming.enabled", false);
 pref("ril.data.apn", "");
 pref("ril.data.user", "");
 pref("ril.data.passwd", "");
 
+//Enable/disable marionette server, set listening port
+pref("marionette.defaultPrefs.enabled", true);
+pref("marionette.defaultPrefs.port", 2828);
+
 #ifdef MOZ_UPDATER
 pref("app.update.enabled", true);
 pref("app.update.auto", true);
 pref("app.update.silent", true);
 pref("app.update.mode", 0);
 pref("app.update.incompatible.mode", 0);
 pref("app.update.service.enabled", true);
 
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -41,17 +41,17 @@ MOZ_APP_VENDOR=Mozilla
 MOZ_APP_VERSION=14.0a1
 MOZ_APP_UA_NAME=Firefox
 
 MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
 MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
 # MOZ_APP_DISPLAYNAME is set by branding/configure.sh
 
 MOZ_SAFE_BROWSING=
-MOZ_SERVICES_SYNC=
+MOZ_SERVICES_SYNC=1
 
 MOZ_WEBSMS_BACKEND=1
 MOZ_DISABLE_DOMCRYPTO=1
 MOZ_APP_STATIC_INI=1
 
 if test "$OS_TARGET" = "Android"; then
 MOZ_CAPTURE=1
 MOZ_RAW=1
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -612,14 +612,18 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DL
 
 [b2g]
 @BINPATH@/chrome/icons/
 @BINPATH@/chrome/chrome@JAREXT@
 @BINPATH@/chrome/chrome.manifest
 @BINPATH@/components/B2GComponents.manifest
 @BINPATH@/components/B2GComponents.xpt
 @BINPATH@/components/CameraContent.js
+@BINPATH@/chrome/marionette@JAREXT@
+@BINPATH@/chrome/marionette.manifest
+@BINPATH@/components/MarionetteComponents.manifest
+@BINPATH@/components/marionettecomponent.js
 @BINPATH@/components/AlertsService.js
 @BINPATH@/components/ContentPermissionPrompt.js
 #ifdef MOZ_UPDATER
 @BINPATH@/components/UpdatePrompt.js
 #endif
 @BINPATH@/components/MozKeyboard.js
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -130,16 +130,17 @@ NS_TRACE_MALLOC = @NS_TRACE_MALLOC@
 USE_ELF_DYNSTR_GC = @USE_ELF_DYNSTR_GC@
 USE_ELF_HACK = @USE_ELF_HACK@
 STDCXX_COMPAT = @STDCXX_COMPAT@
 MOZ_LIBSTDCXX_TARGET_VERSION=@MOZ_LIBSTDCXX_TARGET_VERSION@
 MOZ_LIBSTDCXX_HOST_VERSION=@MOZ_LIBSTDCXX_HOST_VERSION@
 INCREMENTAL_LINKER = @INCREMENTAL_LINKER@
 MACOSX_DEPLOYMENT_TARGET = @MACOSX_DEPLOYMENT_TARGET@
 ENABLE_TESTS	= @ENABLE_TESTS@
+ENABLE_MARIONETTE = @ENABLE_MARIONETTE@
 IBMBIDI = @IBMBIDI@
 MOZ_UNIVERSALCHARDET = @MOZ_UNIVERSALCHARDET@
 ACCESSIBILITY = @ACCESSIBILITY@
 MOZ_BRANDING_DIRECTORY = @MOZ_BRANDING_DIRECTORY@
 XPCOM_USE_LEA = @XPCOM_USE_LEA@
 MOZ_INSTALLER	= @MOZ_INSTALLER@
 MOZ_MAINTENANCE_SERVICE	= @MOZ_MAINTENANCE_SERVICE@
 MOZ_VERIFY_MAR_SIGNATURE	= @MOZ_VERIFY_MAR_SIGNATURE@
--- a/configure.in
+++ b/configure.in
@@ -6433,16 +6433,24 @@ dnl ====================================
 dnl build the tests by default
 dnl ========================================================
 MOZ_ARG_DISABLE_BOOL(tests,
 [  --disable-tests         Do not build test libraries & programs],
     ENABLE_TESTS=,
     ENABLE_TESTS=1 )
 
 dnl ========================================================
+dnl Marionette
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(marionette,
+[  --enable-marionette     Enable Marionette for remote testing and control],
+    ENABLE_MARIONETTE=1,
+    ENABLE_MARIONETTE)
+
+dnl ========================================================
 dnl parental controls (for Windows Vista)
 dnl ========================================================
 MOZ_ARG_DISABLE_BOOL(parental-controls,
 [  --disable-parental-controls
                           Do not build parental controls],
    MOZ_DISABLE_PARENTAL_CONTROLS=1,
    MOZ_DISABLE_PARENTAL_CONTROLS=)
 if test -n "$MOZ_DISABLE_PARENTAL_CONTROLS"; then
@@ -8376,16 +8384,17 @@ AC_SUBST(MOZ_HELP_VIEWER)
 
 AC_SUBST(JAVA)
 AC_SUBST(JAVAC)
 AC_SUBST(JAR)
 
 AC_SUBST(MOZ_PROFILELOCKING)
 
 AC_SUBST(ENABLE_TESTS)
+AC_SUBST(ENABLE_MARIONETTE)
 AC_SUBST(IBMBIDI)
 AC_SUBST(MOZ_UNIVERSALCHARDET)
 AC_SUBST(ACCESSIBILITY)
 AC_SUBST(MOZ_SPELLCHECK)
 AC_SUBST(MOZ_JAVA_COMPOSITOR)
 AC_SUBST(MOZ_ONLY_TOUCH_EVENTS)
 AC_SUBST(MOZ_USER_DIR)
 AC_SUBST(MOZ_CRASHREPORTER)
new file mode 100644
--- /dev/null
+++ b/testing/marionette/Makefile.in
@@ -0,0 +1,19 @@
+# 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/.
+
+DEPTH = ../..
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+ifdef ENABLE_MARIONETTE
+    DIRS += components
+  ifdef ENABLE_TESTS
+      DIRS += tests
+  endif
+endif
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/testing/marionette/components/Makefile.in
@@ -0,0 +1,17 @@
+# 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/.
+
+DEPTH = ../../..
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+EXTRA_PP_COMPONENTS = \
+        MarionetteComponents.manifest \
+        marionettecomponent.js \
+        $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/testing/marionette/components/MarionetteComponents.manifest
@@ -0,0 +1,4 @@
+# Marionette
+component {786a1369-dca5-4adc-8486-33d23c88010a} marionettecomponent.js
+contract @mozilla.org/marionette;1 {786a1369-dca5-4adc-8486-33d23c88010a}
+category profile-after-change MarionetteComponent @mozilla.org/marionette;1
new file mode 100644
--- /dev/null
+++ b/testing/marionette/components/marionettecomponent.js
@@ -0,0 +1,89 @@
+/* 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/. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+const MARIONETTE_CONTRACTID = "@mozilla.org/marionette;1";
+const MARIONETTE_CID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}");
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function MarionetteComponent() {
+  this._loaded = false;
+}
+
+MarionetteComponent.prototype = {
+  classDescription: "Marionette component",
+  classID: MARIONETTE_CID,
+  contractID: MARIONETTE_CONTRACTID,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+  _xpcom_categories: [{category: "profile-after-change", service: true}],
+
+  observe: function mc_observe(aSubject, aTopic, aData) {
+    let observerService = Services.obs;
+    switch (aTopic) {
+      case "profile-after-change":
+        if (Services.prefs.getBoolPref('marionette.defaultPrefs.enabled')) {
+          // set up the logger
+          Cu.import("resource://gre/modules/FileUtils.jsm");
+          Cu.import("resource://gre/modules/services-sync/log4moz.js");
+
+          let logger = Log4Moz.repository.getLogger("Marionette");
+          logger.level = Log4Moz.Level["All"];
+          let logf = FileUtils.getFile('ProfD', ['marionette.log']);
+          
+          let formatter = new Log4Moz.BasicFormatter();
+          logger.addAppender(new Log4Moz.FileAppender(logf, formatter));
+          logger.info("MarionetteComponent loaded");
+
+          //add observers
+          observerService.addObserver(this, "final-ui-startup", false);
+          observerService.addObserver(this, "xpcom-shutdown", false);
+        }
+        else {
+          logger.info("marionette not enabled");
+        }
+        break;
+      case "final-ui-startup":
+        observerService.removeObserver(this, "final-ui-startup");
+        this.init();
+        break;
+      case "xpcom-shutdown":
+        observerService.removeObserver(this, "xpcom-shutdown");
+        this.uninit();
+        break;
+    }
+  },
+
+  init: function mc_init() {
+    if (!this._loaded) {
+      this._loaded = true;
+      let port;
+      try {
+        port = Services.prefs.getIntPref('marionette.defaultPrefs.port');
+      }
+      catch(e) {
+        port = 2828;
+      }
+      try {
+        Cu.import('resource:///modules/devtools/dbg-server.jsm');
+        DebuggerServer.addActors('chrome://marionette/content/marionette-actors.js');
+        DebuggerServer.initTransport();
+        DebuggerServer.openListener(port, true);
+      }
+      catch(e) {
+        logger.error('exception: ' + e.name + ', ' + e.message);
+      }
+    }
+  },
+
+  uninit: function mc_uninit() {
+    DebuggerServer.closeListener();
+    this._loaded = false;
+  },
+
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([MarionetteComponent]);
new file mode 100644
--- /dev/null
+++ b/testing/marionette/jar.mn
@@ -0,0 +1,7 @@
+marionette.jar:
+% content marionette %content/
+  content/marionette-actors.js      (marionette-actors.js)
+  content/marionette-listener.js    (marionette-listener.js)
+  content/marionette-elements.js    (marionette-elements.js)
+  content/marionette-log-obj.js     (marionette-log-obj.js)
+  content/marionette-simpletest.js  (marionette-simpletest.js)
new file mode 100644
--- /dev/null
+++ b/testing/marionette/marionette-actors.js
@@ -0,0 +1,1050 @@
+/* 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";
+/**
+ * Gecko-specific actors.
+ */
+
+let Ci = Components.interfaces;
+let Cc = Components.classes;
+let Cu = Components.utils;
+
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+             .getService(Ci.mozIJSSubScriptLoader);
+loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
+loader.loadSubScript("chrome://marionette/content/marionette-log-obj.js");
+Cu.import("chrome://marionette/content/marionette-elements.js");
+
+let prefs = Cc["@mozilla.org/preferences-service;1"]
+            .getService(Ci.nsIPrefBranch);
+prefs.setBoolPref("marionette.contentListener", false);
+
+let xulAppInfo = Cc["@mozilla.org/xre/app-info;1"]
+                 .getService(Ci.nsIXULAppInfo);
+let appName = xulAppInfo.name;
+
+// import logger
+Cu.import("resource://gre/modules/services-sync/log4moz.js");
+let logger = Log4Moz.repository.getLogger("Marionette");
+logger.info('marionette-actors.js loaded');
+
+/**
+ * Creates the root actor once a connection is established
+ */
+
+function createRootActor(aConnection)
+{
+  return new MarionetteRootActor(aConnection);
+}
+
+/**
+ * Root actor for Marionette. Add any future actors to its actor pool.
+ * Implements methods needed by resource:///modules/devtools/dbg-server.jsm
+ */
+
+function MarionetteRootActor(aConnection)
+{
+  this.conn = aConnection;
+  this._marionetteActor = new MarionetteDriverActor(this.conn);
+  this._marionetteActorPool = null; //hold future actors
+
+  this._marionetteActorPool = new ActorPool(this.conn);
+  this._marionetteActorPool.addActor(this._marionetteActor);
+  this.conn.addActorPool(this._marionetteActorPool);
+}
+
+MarionetteRootActor.prototype = {
+  /**
+   * Called when a client first makes a connection
+   *
+   * @return object
+   *         returns the name of the actor, the application type, and any traits
+   */
+  sayHello: function MRA_sayHello() {
+    return { from: "root",
+             applicationType: "gecko",
+             traits: [] };
+  },
+
+  /**
+   * Called when client disconnects. Cleans up marionette driver actions.
+   */
+  disconnect: function MRA_disconnect() {
+    this._marionetteActor.deleteSession();
+  },
+
+  /**
+   * Used to get the running marionette actor, so we can issue commands
+   *
+   * @return object
+   *         Returns the ID the client can use to communicate with the
+   *         MarionetteDriverActor
+   */
+  getMarionetteID: function MRA_getMarionette() {
+    return { "from": "root",
+             "id": this._marionetteActor.actorID } ;
+  },
+}
+
+// register the calls
+MarionetteRootActor.prototype.requestTypes = {
+  "getMarionetteID": MarionetteRootActor.prototype.getMarionetteID,
+  "sayHello": MarionetteRootActor.prototype.sayHello
+};
+
+/**
+ * This actor is responsible for all marionette API calls. It gets created
+ * for each connection and manages all chrome and browser based calls. It 
+ * mediates content calls by issuing appropriate messages to the content process.
+ */
+function MarionetteDriverActor(aConnection)
+{
+  this.uuidGen = Cc["@mozilla.org/uuid-generator;1"]
+                 .getService(Ci.nsIUUIDGenerator);
+
+  this.conn = aConnection;
+  this.messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
+                             getService(Ci.nsIChromeFrameMessageManager);
+  this.windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator);
+  this.browsers = {}; //holds list of BrowserObjs
+  this.curBrowser = null; // points to current browser
+  this.context = "content";
+  this.scriptTimeout = null;
+  this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]);
+  this.timer = null;
+  this.marionetteLog = new MarionetteLogObj();
+  this.command_id = null;
+
+  //register all message listeners
+  this.messageManager.addMessageListener("Marionette:ok", this);
+  this.messageManager.addMessageListener("Marionette:done", this);
+  this.messageManager.addMessageListener("Marionette:error", this);
+  this.messageManager.addMessageListener("Marionette:log", this);
+  this.messageManager.addMessageListener("Marionette:testLog", this);
+  this.messageManager.addMessageListener("Marionette:register", this);
+  this.messageManager.addMessageListener("Marionette:goUrl", this);
+}
+
+MarionetteDriverActor.prototype = {
+
+  //name of the actor
+  actorPrefix: "marionette",
+
+  /**
+   * Helper method to send async messages to the content listener
+   *
+   * @param string name
+   *        Suffix of the targetted message listener (Marionette:<suffix>)
+   * @param object values
+   *        Object to send to the listener
+   */
+  sendAsync: function MDA_sendAsync(name, values) {
+    this.messageManager.sendAsyncMessage("Marionette:" + name + this.browsers[this.curBrowser].curFrameId, values);
+  },
+
+  /**
+   * Helper methods:
+   */
+
+  /**
+   * Generic method to pass a response to the client
+   * 
+   * @param object msg
+   *        Response to send back to client
+   * @param string command_id
+   *        Unique identifier assigned to the client's request.
+   *        Used to distinguish the asynchronous responses.
+   */
+  sendToClient: function MDA_sendToClient(msg, command_id) {
+    logger.info("sendToClient: " + JSON.stringify(msg) + ", " + command_id + ", " + this.command_id);
+    if (command_id == undefined || command_id == this.command_id) {
+      this.conn.send(msg);
+      this.command_id = null;
+    }
+  },
+
+  /**
+   * Send a value to client
+   *
+   * @param object value
+   *        Value to send back to client
+   * @param string command_id
+   *        Unique identifier assigned to the client's request.
+   *        Used to distinguish the asynchronous responses.
+   */
+  sendResponse: function MDA_sendResponse(value, command_id) {
+    if (typeof(value) == 'undefined')
+        value = null;
+    this.sendToClient({from:this.actorID, value: value}, command_id);
+  },
+
+  /**
+   * Send ack to client
+   * 
+   * @param string command_id
+   *        Unique identifier assigned to the client's request.
+   *        Used to distinguish the asynchronous responses.
+   */
+  sendOk: function MDA_sendOk(command_id) {
+    this.sendToClient({from:this.actorID, ok: true}, command_id);
+  },
+
+  /**
+   * Send error message to client
+   *
+   * @param string message
+   *        Error message
+   * @param number status
+   *        Status number
+   * @param string trace
+   *        Stack trace
+   * @param string command_id
+   *        Unique identifier assigned to the client's request.
+   *        Used to distinguish the asynchronous responses.
+   */
+  sendError: function MDA_sendError(message, status, trace, command_id) {
+    let error_msg = {message: message, status: status, stacktrace: trace};
+    this.sendToClient({from:this.actorID, error: error_msg}, command_id);
+  },
+
+  /**
+   * Gets the current active window
+   * 
+   * @return nsIDOMWindow
+   */
+  getCurrentWindow: function MDA_getCurrentWindow() {
+    let type = null;
+    if (appName != "B2G") {
+      type = 'navigator:browser';
+    }
+    return this.windowMediator.getMostRecentWindow(type);
+  },
+
+  /**
+   * Gets the the window enumerator
+   *
+   * @return nsISimpleEnumerator
+   */
+  getWinEnumerator: function MDA_getWinEnumerator() {
+    let type = null;
+    if (appName != "B2G") {
+      type = 'navigator:browser';
+    }
+    return this.windowMediator.getEnumerator(type);
+  },
+
+  /**
+   * Create a new BrowserObj for window and add to known browsers
+   * 
+   * @param nsIDOMWindow win
+   *        Window for which we will create a BrowserObj
+   *
+   * @return string
+   *        Returns the unique server-assigned ID of the window
+   */
+  addBrowser: function MDA_addBrowser(win) {
+    let browser = new BrowserObj(win);
+    let winId = win.QueryInterface(Ci.nsIInterfaceRequestor).
+                    getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
+    winId = winId + ((appName == "B2G") ? '-b2g' : '');
+    if (this.elementManager.seenItems[winId] == undefined) {
+      //add this to seenItems so we can guarantee the user will get winId as this window's id
+      this.elementManager.seenItems[winId] = win;
+    }
+    this.browsers[winId] = browser;
+    return winId;
+  },
+
+  /**
+   * Start a new session in a new browser. 
+   *
+   * If newSession is true, we will switch focus to the start frame 
+   * when it registers. Also, if it is in desktop, then a new tab 
+   * with the start page uri (about:blank) will be opened.
+   *
+   * @param nsIDOMWindow win
+   *        Window whose browser we need to access
+   * @param boolean newSession
+   *        True if this is the first time we're talking to this browser
+   */
+  startBrowser: function MDA_startBrowser(win, newSession) {
+    let winId = this.addBrowser(win);
+    this.curBrowser = winId;
+    this.browsers[this.curBrowser].newSession = newSession;
+    this.browsers[this.curBrowser].startSession(newSession);
+    this.browsers[this.curBrowser].loadFrameScript("chrome://marionette/content/marionette-listener.js", win);
+  },
+
+  /**
+   * Marionette API:
+   */
+
+  /**
+   * Create a new session. This creates a BrowserObj.
+   *
+   * In a desktop environment, this opens a new 'about:blank' tab for 
+   * the client to test in.
+   *
+   */
+  newSession: function MDA_newSession() {
+    if (!prefs.getBoolPref("marionette.contentListener")) {
+      this.startBrowser(this.getCurrentWindow(), true);
+    }
+    else if ((appName == "B2G")&& (this.curBrowser == null)) {
+      //if there is a content listener, then we just wake it up
+      let winId = this.addBrowser(this.getCurrentWindow());
+      this.curBrowser = winId;
+      this.browsers[this.curBrowser].startSession(false);
+      this.messageManager.sendAsyncMessage("Marionette:restart", {});
+    }
+    else {
+      this.sendError("Session already running", 500, null);
+    }
+  },
+
+  /**
+   * Log message. Accepts user defined log-level.
+   *
+   * @param object aRequest
+   *        'value' member holds log message
+   *        'level' member hold log level
+   */
+  log: function MDA_log(aRequest) {
+    this.marionetteLog.log(aRequest.value, aRequest.level);
+    this.sendOk();
+  },
+
+  /**
+   * Return all logged messages.
+   */
+  getLogs: function MDA_getLogs() {
+    this.sendResponse(this.marionetteLog.getLogs());
+  },
+
+  /**
+   * Sets the context of the subsequent commands to be either 'chrome' or 'content'
+   *
+   * @param object aRequest
+   *        'value' member holds the name of the context to be switched to
+   */
+  setContext: function MDA_setContext(aRequest) {
+    let context = aRequest.value;
+    if (context != "content" && context != "chrome") {
+      this.sendError("invalid context", 500, null);
+    }
+    else {
+      this.context = context;
+      this.sendOk();
+    }
+  },
+
+  /**
+   * Returns a chrome sandbox that can be used by the execute_foo functions.
+   *
+   * @param nsIDOMWindow aWindow
+   *        Window in which we will execute code
+   * @param Marionette marionette
+   *        Marionette test instance
+   * @param object args
+   *        Client given args
+   * @return Sandbox
+   *        Returns the sandbox
+   */
+  createExecuteSandbox: function MDA_createExecuteSandbox(aWindow, marionette, args) {
+    try {
+      args = this.elementManager.convertWrappedArguments(args, aWindow);
+    }
+    catch(e) {
+      this.sendError(e.message, e.num, e.stack);
+      return;
+    }
+
+    let _chromeSandbox = new Cu.Sandbox(aWindow,
+       { sandboxPrototype: aWindow, wantXrays: false, sandboxName: ''});
+    _chromeSandbox.__namedArgs = this.elementManager.applyNamedArgs(args);
+    _chromeSandbox.__marionetteParams = args;
+
+    marionette.exports.forEach(function(fn) {
+      _chromeSandbox[fn] = marionette[fn].bind(marionette);
+    });
+
+    return _chromeSandbox;
+  },
+
+  /**
+   * Executes a script in the given sandbox.
+   *
+   * @param Sandbox sandbox
+   *        Sandbox in which the script will run
+   * @param string script
+   *        The script to run
+   * @param boolean directInject
+   *        If true, then the script will be run as is,
+   *        and not as a function body (as you would
+   *        do using the WebDriver spec)
+   * @param boolean async
+   *        True if the script is asynchronous
+   */
+  executeScriptInSandbox: function MDA_executeScriptInSandbox(sandbox, script,
+     directInject, async) {
+    try {
+      if (directInject && async &&
+          (this.scriptTimeout == null || this.scriptTimeout == 0)) {
+        this.sendError("Please set a timeout", 21, null);
+        return;
+      }
+
+      let res = Cu.evalInSandbox(script, sandbox, "1.8");
+
+      if (directInject && !async &&
+          (res == undefined || res.passed == undefined)) {
+        this.sendError("finish() not called", 500, null);
+        return;
+      }
+
+      if (!async) {
+        this.sendResponse(this.elementManager.wrapValue(res));
+      }
+    }
+    catch (e) {
+      this.sendError(e.name + ': ' + e.message, 17, e.stack);
+    }
+  },
+
+  /**
+   * Execute the given script either as a function body (executeScript)
+   * or directly (for 'mochitest' like JS Marionette tests)
+   *
+   * @param object aRequest
+   *        'value' member is the script to run
+   *        'args' member holds the arguments to the script
+   * @param boolean directInject
+   *        if true, it will be run directly and not as a 
+   *        function body
+   */
+  execute: function MDA_execute(aRequest, directInject) {
+    if (this.context == "content") {
+      this.sendAsync("executeScript", {value: aRequest.value, args: aRequest.args});
+      return;
+    }
+
+    let curWindow = this.getCurrentWindow();
+    let marionette = new Marionette(false, curWindow, "chrome", this.marionetteLog);
+    let _chromeSandbox = this.createExecuteSandbox(curWindow, marionette, aRequest.args);
+    if (!_chromeSandbox)
+      return;
+
+    try {
+      _chromeSandbox.finish = function chromeSandbox_finish() {
+        return marionette.generate_results();
+      };
+
+      let script;
+      if (directInject) {
+        script = aRequest.value;
+      }
+      else {
+        script = "let func = function() {" +
+                       aRequest.value + 
+                     "};" +
+                     "func.apply(null, __marionetteParams);";
+      }
+      this.executeScriptInSandbox(_chromeSandbox, script, directInject, false);
+    }
+    catch (e) {
+      this.sendError(e.name + ': ' + e.message, 17, e.stack);
+    }
+  },
+
+  /**
+   * Set the timeout for asynchronous script execution
+   *
+   * @param object aRequest
+   *        'value' member is time in milliseconds to set timeout
+   */
+  setScriptTimeout: function MDA_setScriptTimeout(aRequest) {
+    let timeout = parseInt(aRequest.value);
+    if(isNaN(timeout)){
+      this.sendError("Not a Number", 500, null);
+    }
+    else {
+      this.scriptTimeout = timeout;
+      this.sendAsync("setScriptTimeout", {value: timeout});
+      this.sendOk();
+    }
+  },
+
+  /**
+   * execute pure JS script. Used to execute 'mochitest'-style Marionette tests.
+   *
+   * @param object aRequest
+   *        'value' member holds the script to execute
+   *        'args' member holds the arguments to the script
+   *        'timeout' member will be used as the script timeout if it is given
+   */
+  executeJSScript: function MDA_executeJSScript(aRequest) {
+    //all pure JS scripts will need to call Marionette.finish() to complete the test.
+    if (this.context == "chrome") {
+      if (aRequest.timeout) {
+        this.executeWithCallback(aRequest, aRequest.timeout);
+      }
+      else {
+        this.execute(aRequest, true);
+      }
+    }
+    else {
+      this.sendAsync("executeJSScript", {value:aRequest.value, args:aRequest.args, timeout:aRequest.timeout});
+   }
+  },
+
+  /**
+   * This function is used by executeAsync and executeJSScript to execute a script
+   * in a sandbox. 
+   * 
+   * For executeJSScript, it will return a message only when the finish() method is called.
+   * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1] 
+   * method is called, or if it times out.
+   *
+   * @param object aRequest
+   *        'value' member holds the script to execute
+   *        'args' member holds the arguments for the script
+   * @param boolean directInject
+   *        if true, it will be run directly and not as a 
+   *        function body
+   */
+  executeWithCallback: function MDA_executeWithCallback(aRequest, directInject) {
+    this.command_id = this.uuidGen.generateUUID().toString();
+
+    if (this.context == "content") {
+      this.sendAsync("executeAsyncScript", {value: aRequest.value,
+                                            args: aRequest.args,
+                                            id: this.command_id});
+      return;
+    }
+
+    let curWindow = this.getCurrentWindow();
+    let original_onerror = curWindow.onerror;
+    let that = this;
+    let marionette = new Marionette(true, curWindow, "chrome", this.marionetteLog);
+    marionette.command_id = this.command_id;
+
+    function chromeAsyncReturnFunc(value, status) {
+      if (value == undefined)
+        value = null;
+      if (that.command_id == marionette.command_id) {
+        if (that.timer != null) {
+          that.timer.cancel();
+          that.timer = null;
+        }
+
+        curWindow.onerror = original_onerror;
+
+        if (status == 0 || status == undefined) {
+          that.sendToClient({from: that.actorID, value: that.elementManager.wrapValue(value), status: status},
+                            marionette.command_id);
+        }
+        else {
+          let error_msg = {message: value, status: status, stacktrace: null};
+          that.sendToClient({from: that.actorID, error: error_msg},
+                            marionette.command_id);
+        }
+      }
+    }
+
+    curWindow.onerror = function (errorMsg, url, lineNumber) {
+      chromeAsyncReturnFunc(errorMsg + " at: " + url + " line: " + lineNumber, 17);
+      return true;
+    };
+
+    function chromeAsyncFinish() {
+      chromeAsyncReturnFunc(marionette.generate_results(), 0);
+    }
+
+    let _chromeSandbox = this.createExecuteSandbox(curWindow, marionette, aRequest.args);
+    if (!_chromeSandbox)
+      return;
+
+    try {
+
+      this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+      if (this.timer != null) {
+        this.timer.initWithCallback(function() {
+          chromeAsyncReturnFunc("timed out", 28);
+        }, that.scriptTimeout, Ci.nsITimer.TYPE_ONESHOT);
+      }
+
+      _chromeSandbox.returnFunc = chromeAsyncReturnFunc;
+      _chromeSandbox.finish = chromeAsyncFinish;
+
+      let script;
+      if (directInject) {
+        script = aRequest.value;
+      }
+      else {
+        script =  '__marionetteParams.push(returnFunc);'
+                + 'let marionetteScriptFinished = returnFunc;'
+                + 'let __marionetteFunc = function() {' + aRequest.value + '};'
+                + '__marionetteFunc.apply(null, __marionetteParams);';
+      }
+
+      this.executeScriptInSandbox(_chromeSandbox, script, directInject, true);
+    } catch (e) {
+      this.sendError(e.name + ": " + e.message, 17, e.stack, marionette.command_id);
+    }
+  },
+
+  /**
+   * Navigates to given url
+   *
+   * @param object aRequest
+   *        'value' member holds the url to navigate to
+   */
+  goUrl: function MDA_goUrl(aRequest) {
+    this.sendAsync("goUrl", aRequest);
+  },
+
+  /**
+   * Gets current url
+   */
+  getUrl: function MDA_getUrl() {
+    if (this.context == "chrome") {
+      this.sendResponse(this.getCurrentWindow().location.href);
+    }
+    else {
+      this.sendAsync("getUrl", {});
+    }
+  },
+
+  /**
+   * Go back in history
+   */
+  goBack: function MDA_goBack() {
+    this.sendAsync("goBack", {});
+  },
+
+  /**
+   * Go forward in history
+   */
+  goForward: function MDA_goForward() {
+    this.sendAsync("goForward", {});
+  },
+
+  /**
+   * Refresh the page
+   */
+  refresh: function MDA_refresh() {
+    this.sendAsync("refresh", {});
+  },
+
+  /**
+   * Get the current window's server-assigned ID
+   */
+  getWindow: function MDA_getWindow() {
+    this.sendResponse(this.curBrowser);
+  },
+
+  /**
+   * Get the server-assigned IDs of all available windows
+   */
+  getWindows: function MDA_getWindows() {
+    let res = [];
+    let winEn = this.getWinEnumerator(); 
+    while(winEn.hasMoreElements()) {
+      let foundWin = winEn.getNext();
+      let winId = foundWin.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils).outerWindowID;
+      winId = winId + ((appName == "B2G") ? '-b2g' : '');
+      res.push(winId)
+    }
+    this.sendResponse(res);
+  },
+
+  /**
+   * Switch to a window based on name or server-assigned id.
+   * Searches based on name, then id.
+   *
+   * @param object aRequest
+   *        'value' member holds the id of the window to switch to
+   */
+  switchToWindow: function MDA_switchToWindow(aRequest) {
+    let winEn = this.getWinEnumerator(); 
+    while(winEn.hasMoreElements()) {
+      let foundWin = winEn.getNext();
+      let winId = foundWin.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils).outerWindowID;
+      winId = winId + ((appName == "B2G") ? '-b2g' : '');
+      if (aRequest.value == foundWin.name || aRequest.value == winId) {
+        if (this.browsers[winId] == undefined) {
+          //enable Marionette in that browser window
+          this.startBrowser(foundWin, false);
+        }
+        foundWin.focus();
+        this.curBrowser = winId;
+        this.sendOk();
+        return;
+      }
+    }
+    this.sendError("Unable to locate window " + aRequest.value, 23, null);
+  },
+
+  /**
+   * Switch to a given frame within the current window
+   *
+   * @param object aRequest
+   *        'value' holds the id of the frame to switch to
+   */
+  switchToFrame: function MDA_switchToFrame(aRequest) {
+    this.sendAsync("switchToFrame", aRequest);
+  },
+
+  /**
+   * Set timeout for searching for elements
+   *
+   * @param object aRequest
+   *        'value' holds the search timeout in milliseconds
+   */
+  setSearchTimeout: function MDA_setSearchTimeout(aRequest) {
+    if (this.context == "chrome") {
+      try {
+        this.elementManager.setSearchTimeout(aRequest.value);
+        this.sendOk();
+      }
+      catch (e) {
+        this.sendError(e.message, e.num, e.stack);
+      }
+    }
+    else {
+      this.sendAsync("setSearchTimeout", {value: aRequest.value});
+    }
+  },
+
+  /**
+   * Find an element using the indicated search strategy.
+   *
+   * @param object aRequest
+   *        'using' member indicates which search method to use
+   *        'value' member is the value the client is looking for
+   */
+  findElement: function MDA_findElement(aRequest) {
+    if (this.context == "chrome") {
+      let id;
+      try {
+        let notify = this.sendResponse.bind(this);
+        id = this.elementManager.find(aRequest, this.getCurrentWindow().document, notify, false);
+      }
+      catch (e) {
+        this.sendError(e.message, e.num, e.stack);
+        return;
+      }
+    }
+    else {
+      this.sendAsync("findElementContent", {value: aRequest.value, using: aRequest.using, element: aRequest.element});
+    }
+  },
+
+  /**
+   * Find elements using the indicated search strategy.
+   *
+   * @param object aRequest
+   *        'using' member indicates which search method to use
+   *        'value' member is the value the client is looking for
+   */
+  findElements: function MDA_findElements(aRequest) {
+    if (this.context == "chrome") {
+      let id;
+      try {
+        let notify = this.sendResponse.bind(this);
+        id = this.elementManager.find(aRequest, this.getCurrentWindow().document, notify, true);
+      }
+      catch (e) {
+        this.sendError(e.message, e.num, e.stack);
+        return;
+      }
+    }
+    else {
+      this.sendAsync("findElementsContent", {value: aRequest.value, using: aRequest.using, element: aRequest.element});
+    }
+  },
+
+  /**
+   * Send click event to element
+   * 
+   * @param object aRequest
+   *        'element' member holds the reference id to
+   *        the element that will be clicked
+   */
+  clickElement: function MDA_clickElement(aRequest) {
+    this.sendAsync("clickElement", {element: aRequest.element});
+  },
+
+  /**
+   * Deletes the session.
+   * 
+   * If it is a desktop environment, it will close the session's tab and close all listeners
+   *
+   * If it is a B2G environment, it will make the main content listener sleep, and close
+   * all other listeners. The main content listener persists after disconnect (it's the homescreen),
+   * and can safely be reused.
+   */
+  deleteSession: function MDA_deleteSession() {
+    if (this.browsers[this.curBrowser] != null) {
+      if (appName == "B2G") {
+        this.messageManager.sendAsyncMessage("Marionette:sleepSession" + this.browsers[this.curBrowser].mainContentId, {});
+        this.browsers[this.curBrowser].knownFrames.splice(this.browsers[this.curBrowser].knownFrames.indexOf(this.browsers[this.curBrowser].mainContentId), 1);
+      }
+      else {
+        //don't set this pref for B2G since the framescript can be safely reused
+        prefs.setBoolPref("marionette.contentListener", false);
+      }
+      this.browsers[this.curBrowser].closeTab();
+      //delete session in each frame in each browser
+      for (let win in this.browsers) {
+        for (let i in this.browsers[win].knownFrames) {
+          this.messageManager.sendAsyncMessage("Marionette:deleteSession" + this.browsers[win].knownFrames[i], {});
+        }
+      }
+      let winEnum = this.getWinEnumerator();
+      while (winEnum.hasMoreElements()) {
+        winEnum.getNext().messageManager.removeDelayedFrameScript("chrome://marionette/content/marionette-listener.js"); 
+      }
+    }
+    this.sendOk();
+    this.messageManager.removeMessageListener("Marionette:ok", this);
+    this.messageManager.removeMessageListener("Marionette:done", this);
+    this.messageManager.removeMessageListener("Marionette:error", this);
+    this.messageManager.removeMessageListener("Marionette:log", this);
+    this.messageManager.removeMessageListener("Marionette:testLog", this);
+    this.messageManager.removeMessageListener("Marionette:register", this);
+    this.messageManager.removeMessageListener("Marionette:goUrl", this);
+    this.curBrowser = null;
+    this.elementManager.reset();
+  },
+
+  /**
+   * Receives all messages from content messageManager
+   */
+  receiveMessage: function MDA_receiveMessage(message) {
+    switch (message.name) {
+      case "DOMContentLoaded":
+        this.sendOk();
+        this.messageManager.removeMessageListener("DOMContentLoaded", this, true);
+        break;
+      case "Marionette:done":
+        this.sendResponse(message.json.value, message.json.command_id);
+        break;
+      case "Marionette:ok":
+        this.sendOk(message.json.command_id);
+        break;
+      case "Marionette:error":
+        this.sendError(message.json.message, message.json.status, message.json.stacktrace, message.json.command_id);
+        break;
+      case "Marionette:log":
+        //log server-side messages
+        logger.info(message.json.message);
+        break;
+      case "Marionette:testLog":
+        //log messages from tests
+        this.marionetteLog.addLogs(message.json.value);
+        break;
+      case "Marionette:register":
+        // This code processes the content listener's registration information
+        // and either accepts the listener, or ignores it
+        let nullPrevious= (this.browsers[this.curBrowser].curFrameId == null);
+        let curWin = this.getCurrentWindow();
+        let frameObject = curWin.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils).getOuterWindowWithId(message.json.value);
+        let reg = this.browsers[this.curBrowser].register(message.json.value, message.json.href);
+        if (reg) {
+          this.elementManager.seenItems[reg] = frameObject; //add to seenItems
+          if (nullPrevious && (this.browsers[this.curBrowser].curFrameId != null)) {
+            this.sendAsync("newSession", {B2G: (appName == "B2G")});
+            if (this.browsers[this.curBrowser].newSession) {
+              this.sendResponse(reg);
+            }
+          }
+        }
+        return reg;
+      case "Marionette:goUrl":
+        // if content determines that the goUrl call is directed at a top level window (not an iframe)
+        // it calls back into chrome to load the uri.
+        this.browsers[this.curBrowser].loadURI(message.json.value, this);
+        break;
+    }
+  },
+  /**
+   * for non-e10s eventListening
+   */
+  handleEvent: function MDA_handleEvent(evt) {
+    if (evt.type == "DOMContentLoaded") {
+      this.sendOk();
+      this.browsers[this.curBrowser].browser.removeEventListener("DOMContentLoaded", this, false);
+    }
+  },
+};
+
+MarionetteDriverActor.prototype.requestTypes = {
+  "newSession": MarionetteDriverActor.prototype.newSession,
+  "log": MarionetteDriverActor.prototype.log,
+  "getLogs": MarionetteDriverActor.prototype.getLogs,
+  "setContext": MarionetteDriverActor.prototype.setContext,
+  "executeScript": MarionetteDriverActor.prototype.execute,
+  "setScriptTimeout": MarionetteDriverActor.prototype.setScriptTimeout,
+  "executeAsyncScript": MarionetteDriverActor.prototype.executeWithCallback,
+  "executeJSScript": MarionetteDriverActor.prototype.executeJSScript,
+  "setSearchTimeout": MarionetteDriverActor.prototype.setSearchTimeout,
+  "findElement": MarionetteDriverActor.prototype.findElement,
+  "findElements": MarionetteDriverActor.prototype.findElements,
+  "clickElement": MarionetteDriverActor.prototype.clickElement,
+  "goUrl": MarionetteDriverActor.prototype.goUrl,
+  "getUrl": MarionetteDriverActor.prototype.getUrl,
+  "goBack": MarionetteDriverActor.prototype.goBack,
+  "goForward": MarionetteDriverActor.prototype.goForward,
+  "refresh":  MarionetteDriverActor.prototype.refresh,
+  "getWindow":  MarionetteDriverActor.prototype.getWindow,
+  "getWindows":  MarionetteDriverActor.prototype.getWindows,
+  "switchToFrame": MarionetteDriverActor.prototype.switchToFrame,
+  "switchToWindow": MarionetteDriverActor.prototype.switchToWindow,
+  "deleteSession": MarionetteDriverActor.prototype.deleteSession
+};
+
+/**
+ * Creates a BrowserObj. BrowserObjs handle interactions with the
+ * browser, according to the current environment (desktop, b2g, etc.)
+ *
+ * @param nsIDOMWindow win
+ *        The window whose browser needs to be accessed
+ */
+
+function BrowserObj(win) {
+  this.DESKTOP = "desktop";
+  this.B2G = "B2G";
+  this.browser;
+  this.browser_mm;
+  this.tab = null;
+  this.knownFrames = [];
+  this.curFrameId = null;
+  this.startPage = "about:blank";
+  this.mainContentId = null; // used in B2G to identify the homescreen content page
+  this.messageManager = Cc["@mozilla.org/globalmessagemanager;1"].
+                             getService(Ci.nsIChromeFrameMessageManager);
+  this.newSession = true; //used to set curFrameId upon new session
+  this.setBrowser(win);
+}
+
+BrowserObj.prototype = {
+  /**
+   * Set the browser if the application is not B2G
+   *
+   * @param nsIDOMWindow win
+   *        current window reference
+   */
+  setBrowser: function BO_setBrowser(win) {
+    if (appName != "B2G") {
+      this.browser = win.gBrowser; 
+    }
+  },
+  /**
+   * Called when we start a session with this browser.
+   *
+   * In a desktop environment, if newTab is true, it will start 
+   * a new 'about:blank' tab and change focus to this tab.
+   *
+   * This will also set the active messagemanager for this object
+   *
+   * @param boolean newTab
+   *        If true, create new tab
+   */
+  startSession: function BO_startSession(newTab) {
+    if (appName == "B2G") {
+      return;
+    }
+    if (newTab) {
+      this.addTab(this.startPage);
+      //if we have a new tab, make it the selected tab and give it focus
+      this.browser.selectedTab = this.tab;
+      let newTabBrowser = this.browser.getBrowserForTab(this.tab);
+      //focus the tab
+      newTabBrowser.ownerDocument.defaultView.focus();
+    }
+    else {
+      //set this.tab to the currently focused tab
+      this.tab = this.browser.selectedTab;
+      this.browser_mm = this.browser.getBrowserForTab(this.tab).messageManager;
+    }
+  },
+
+  /**
+   * Closes current tab
+   */
+  closeTab: function BO_closeTab() {
+    if (this.tab != null && (appName != "B2G")) {
+      this.browser.removeTab(this.tab);
+      this.tab = null;
+    }
+  },
+
+  /**
+   * Opens a tab with given uri
+   *
+   * @param string uri
+   *      URI to open
+   */
+  addTab: function BO_addTab(uri) {
+    this.tab = this.browser.addTab(uri, true);
+  },
+
+  /**
+   * Load a uri in the current tab
+   *
+   * @param string uri
+   *      URI to load
+   * @param EventListener listener
+   *      event listener fired on load
+   */
+  loadURI: function BO_openURI(uri, listener) {
+    if (appName != "B2G") {
+      this.browser.addEventListener("DOMContentLoaded", listener, false);
+      this.browser.loadURI(uri);
+    }
+    else {
+      this.messageManager.addMessageListener("DOMContentLoaded", listener, true);
+      this.browser.selectedBrowser.loadURI(uri);
+    }
+  },
+
+  /**
+   * Loads content listeners if we don't already have them
+   *
+   * @param string script
+   *        path of script to load
+   * @param nsIDOMWindow frame
+   *        frame to load the script in
+   */
+  loadFrameScript: function BO_loadFrameScript(script, frame) {
+    if (!prefs.getBoolPref("marionette.contentListener")) {
+      frame.window.messageManager.loadFrameScript(script, true);
+      prefs.setBoolPref("marionette.contentListener", true);
+    }
+  },
+
+  /**
+   * Registers a new frame, and sets its current frame id to this frame
+   * if it is not already assigned, and if a) we already have a session 
+   * or b) we're starting a new session and it is the right start frame.
+   *
+   * @param string id
+   *        frame id
+   * @param string href
+   *        frame's href 
+   */
+  register: function BO_register(id, href) {
+    let uid = id + ((appName == "B2G") ? '-b2g' : '');
+    if (this.curFrameId == null) {
+      if ((!this.newSession) || (this.newSession && ((appName == "B2G") || href.indexOf(this.startPage) > -1))) {
+        this.curFrameId = uid;
+        this.mainContentId = uid;
+      }
+    }
+    this.knownFrames.push(uid); //used to deletesessions
+    return uid;
+  },
+}
new file mode 100644
--- /dev/null
+++ b/testing/marionette/marionette-elements.js
@@ -0,0 +1,403 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/**
+ * The ElementManager manages DOM references and interactions with elements.
+ * According to the WebDriver spec (http://code.google.com/p/selenium/wiki/JsonWireProtocol), the
+ * server sends the client an element reference, and maintains the map of reference to element.
+ * The client uses this reference when querying/interacting with the element, and the 
+ * server uses maps this reference to the actual element when it executes the command.
+ */
+
+let EXPORTED_SYMBOLS = ["ElementManager", "CLASS_NAME", "SELECTOR", "ID", "NAME", "LINK_TEXT", "PARTIAL_LINK_TEXT", "TAG", "XPATH"];
+
+let uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
+             .getService(Components.interfaces.nsIUUIDGenerator);
+
+let CLASS_NAME = "class name";
+let SELECTOR = "css selector";
+let ID = "id";
+let NAME = "name";
+let LINK_TEXT = "link text";
+let PARTIAL_LINK_TEXT = "partial link text";
+let TAG = "tag name";
+let XPATH = "xpath";
+
+function ElementException(msg, num, stack) {
+  this.message = msg;
+  this.num = num;
+  this.stack = stack;
+}
+
+/* NOTE: Bug 736592 has been created to replace seenItems with a weakRef map */
+function ElementManager(notSupported) {
+  this.searchTimeout = 0;
+  this.seenItems = {};
+  this.timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+  this.elementStrategies = [CLASS_NAME, SELECTOR, ID, NAME, LINK_TEXT, PARTIAL_LINK_TEXT, TAG, XPATH];
+  for (let i = 0; i < notSupported.length; i++) {
+    this.elementStrategies.splice(this.elementStrategies.indexOf(notSupported[i]), 1);
+  }
+}
+
+ElementManager.prototype = {
+  /**
+   * Reset values
+   */
+  reset: function EM_clear() {
+    this.searchTimeout = 0;
+    this.seenItems = {};
+  },
+
+  /**
+  * Add element to list of seen elements
+  *
+  * @param nsIDOMElement element
+  *        The element to add
+  *
+  * @return string
+  *        Returns the server-assigned reference ID
+  */
+  addToKnownElements: function EM_addToKnownElements(element) {
+    for (let i in this.seenItems) {
+      if (this.seenItems[i] == element) {
+        return i;
+      }
+    }
+    var id = uuidGen.generateUUID().toString();
+    this.seenItems[id] = element;
+    return id;
+  },
+  
+  /**
+   * Retrieve element from its unique ID
+   *
+   * @param String id
+   *        The DOM reference ID
+   * @param nsIDOMWindow win
+   *        The window that contains the element
+   *
+   * @returns nsIDOMElement
+   *        Returns the element or throws Exception if not found
+   */
+  getKnownElement: function EM_getKnownElement(id, win) {
+    let el = this.seenItems[id];
+    if (!el) {
+      throw new ElementException("Element has not been seen before", 17, null);
+    }
+    el = el;
+    if (!(el.ownerDocument == win.document)) {
+      throw new ElementException("Stale element reference", 10, null);
+    }
+    return el;
+  },
+  
+  /**
+   * Convert values to primitives that can be transported over the Marionette
+   * JSON protocol.
+   * 
+   * @param object val
+   *        object to be wrapped
+   *
+   * @return object
+   *        Returns a JSON primitive or Object
+   */
+  wrapValue: function EM_wrapValue(val) {
+    let result;
+    switch(typeof(val)) {
+      case "undefined":
+        result = null;
+        break;
+      case "string":
+      case "number":
+      case "boolean":
+        result = val;
+        break;
+      case "object":
+        if (Object.prototype.toString.call(val) == '[object Array]') {
+          result = [];
+          for (let i in val) {
+            result.push(this.wrapValue(val[i]));
+          }
+        }
+        else if (val == null) {
+          result = null;
+        }
+        // nodeType 1 == 'element'
+        else if (val.nodeType == 1) {
+          for(let i in this.seenItems) {
+            if (this.seenItems[i] == val) {
+              result = {'ELEMENT': i};
+            }
+          }
+          result = {'ELEMENT': this.addToKnownElements(val)};
+        }
+        else {
+          result = {};
+          for (let prop in val) {
+            result[prop] = this.wrapValue(val[prop]);
+          }
+        }
+        break;
+    }
+    return result;
+  },
+  
+  /**
+   * Convert any ELEMENT references in 'args' to the actual elements
+   *
+   * @param object args
+   *        Arguments passed in by client
+   * @param nsIDOMWindow win
+   *        The window that contains the elements
+   *
+   * @returns object
+   *        Returns the objects passed in by the client, with the
+   *        reference IDs replaced by the actual elements.
+   */
+  convertWrappedArguments: function EM_convertWrappedArguments(args, win) {
+    let converted;
+    switch (typeof(args)) {
+      case 'number':
+      case 'string':
+      case 'boolean':
+        converted = args;
+        break;
+      case 'object':
+        if (args == null) {
+          converted = null;
+        }
+        else if (Object.prototype.toString.call(args) == '[object Array]') {
+          converted = [];
+          for (let i in args) {
+            converted.push(this.convertWrappedArguments(args[i], win));
+          }
+        }
+        else if (typeof(args['ELEMENT'] === 'string') &&
+                 args.hasOwnProperty('ELEMENT')) {
+          converted = this.getKnownElement(args['ELEMENT'],  win);
+          if (converted == null)
+            throw new ElementException("Unknown element: " + args['ELEMENT'], 500, null);
+        }
+        else {
+          converted = {};
+          for (let prop in args) {
+            converted[prop] = this.convertWrappedArguments(args[prop], win);
+          }
+        }
+        break;
+    }
+    return converted;
+  },
+  
+  /*
+   * Execute* helpers
+   */
+  
+  /**
+   * Return an object with any namedArgs applied to it. Used
+   * to let clients use given names when refering to arguments
+   * in execute calls, instead of using the arguments list.
+   *
+   * @param object args
+   *        list of arguments being passed in
+   *
+   * @return object
+   *        If '__marionetteArgs' is in args, then
+   *        it will return an object with these arguments
+   *        as its members.
+   */
+  applyNamedArgs: function EM_applyNamedArgs(args) {
+    namedArgs = {};
+    args.forEach(function(arg) {
+      if (typeof(arg['__marionetteArgs']) === 'object') {
+        for (let prop in arg['__marionetteArgs']) {
+          namedArgs[prop] = arg['__marionetteArgs'][prop];
+        }
+      }
+    });
+    return namedArgs;
+  },
+  
+  /**
+   * Find an element or elements starting at the document root 
+   * using the given search strategy. Search
+   * will continue until the search timelimit has been reached.
+   *
+   * @param object values
+   *        The 'using' member of values will tell us which search
+   *        method to use. The 'value' member tells us the value we
+   *        are looking for.
+   *        If this object has a 'time' member, this number will be
+   *        used to see if we have hit the search timelimit.
+   * @param nsIDOMElement rootNode
+   *        The document root
+   * @param function notify
+   *        The notification callback used when we are returning
+   * @param boolean all
+   *        If true, all found elements will be returned.
+   *        If false, only the first element will be returned.
+   *
+   * @return nsIDOMElement or list of nsIDOMElements
+   *        Returns the element(s) by calling the notify function.
+   */
+  find: function EM_find(values, rootNode, notify, all) {
+    let startTime = values.time ? values.time : new Date().getTime();
+    if (this.elementStrategies.indexOf(values.using) < 0) {
+      throw new ElementException("No such strategy.", 17, null);
+    }
+    let found = all ? this.findElements(values.using, values.value, rootNode) : this.findElement(values.using, values.value, rootNode);
+    if (found) {
+      let type = Object.prototype.toString.call(found);
+      if ((type == '[object Array]') || (type == '[object HTMLCollection]')) {
+        let ids = []
+        for (let i = 0 ; i < found.length ; i++) {
+          ids.push(this.addToKnownElements(found[i]));
+        }
+        notify(ids);
+      }
+      else {
+        let id = this.addToKnownElements(found);
+        notify(id);
+      }
+      return;
+    } else {
+      if (this.searchTimeout == 0 || new Date().getTime() - startTime > this.searchTimeout) {
+        throw new ElementException("Unable to locate element: " + values.value, 7, null);
+      } else {
+        values.time = startTime;
+        this.timer.initWithCallback(this.find.bind(this, values, rootNode, notify, all), 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+      }
+    }
+  },
+  
+  /**
+   * Helper method to find. Finds one element using find's criteria
+   * 
+   * @param string using
+   *        String identifying which search method to use
+   * @param string value
+   *        Value to look for
+   * @param nsIDOMElement rootNode
+   *        Document root
+   *
+   * @return nsIDOMElement
+   *        Returns found element or throws Exception if not found
+   */
+  findElement: function EM_findElement(using, value, rootNode) {
+    let element;
+    switch (using) {
+      case ID:
+        element = rootNode.getElementById(value);
+        break;
+      case NAME:
+        element = rootNode.getElementsByName(value)[0];
+        break;
+      case CLASS_NAME:
+        element = rootNode.getElementsByClassName(value)[0];
+        break;
+      case TAG:
+        element = rootNode.getElementsByTagName(value)[0];
+        break;
+      case XPATH:
+        element = rootNode.evaluate(value, rootNode, null,
+                    Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).
+                    singleNodeValue;
+        break;
+      case LINK_TEXT:
+      case PARTIAL_LINK_TEXT:
+        let allLinks = rootNode.getElementsByTagName('A');
+        for (let i = 0; i < allLinks.length && !element; i++) {
+          let text = allLinks[i].text;
+          if (PARTIAL_LINK_TEXT == using) {
+            if (text.indexOf(value) != -1) {
+              element = allLinks[i];
+            }
+          } else if (text == value) {
+            element = allLinks[i];
+          }
+        }
+        break;
+      case SELECTOR:
+        element = rootNode.querySelector(value);
+        break;
+      default:
+        throw new ElementException("No such strategy", 500, null);
+    }
+    return element;
+  },
+
+  /**
+   * Helper method to find. Finds all element using find's criteria
+   * 
+   * @param string using
+   *        String identifying which search method to use
+   * @param string value
+   *        Value to look for
+   * @param nsIDOMElement rootNode
+   *        Document root
+   *
+   * @return nsIDOMElement
+   *        Returns found elements or throws Exception if not found
+   */
+  findElements: function EM_findElements(using, value, rootNode) {
+    let elements = [];
+    switch (using) {
+      case ID:
+        value = './/*[@id="' + value + '"]';
+      case XPATH:
+        values = rootNode.evaluate(value, rootNode, null,
+                    Components.interfaces.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null)
+        let element = values.iterateNext();
+        while (element) {
+          elements.push(element);
+          element = values.iterateNext();
+        }
+        break;
+      case NAME:
+        elements = rootNode.getElementsByName(value);
+        break;
+      case CLASS_NAME:
+        elements = rootNode.getElementsByClassName(value);
+        break;
+      case TAG:
+        elements = rootNode.getElementsByTagName(value);
+        break;
+      case LINK_TEXT:
+      case PARTIAL_LINK_TEXT:
+        let allLinks = rootNode.getElementsByTagName('A');
+        for (let i = 0; i < allLinks.length; i++) {
+          let text = allLinks[i].text;
+          if (PARTIAL_LINK_TEXT == using) {
+            if (text.indexOf(value) != -1) {
+              elements.push(allLinks[i]);
+            }
+          } else if (text == value) {
+            elements.push(allLinks[i]);
+          }
+        }
+        break;
+      case SELECTOR:
+        elements = rootNode.querySelectorAll(value);
+        break;
+      default:
+        throw new ElementException("No such strategy", 500, null);
+    }
+    return elements;
+  },
+
+  /**
+   * Sets the timeout for searching for elements with find element
+   * 
+   * @param number value
+   *        Timeout value in milliseconds
+   */
+  setSearchTimeout: function EM_setSearchTimeout(value) {
+    this.searchTimeout = parseInt(value);
+    if(isNaN(this.searchTimeout)){
+      throw new ElementException("Not a Number", 500, null);
+    }
+  },
+}
new file mode 100644
--- /dev/null
+++ b/testing/marionette/marionette-listener.js
@@ -0,0 +1,534 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 Cu = Components.utils;
+let uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
+             .getService(Components.interfaces.nsIUUIDGenerator);
+
+let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+             .getService(Components.interfaces.mozIJSSubScriptLoader);
+loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
+loader.loadSubScript("chrome://marionette/content/marionette-log-obj.js");
+Components.utils.import("chrome://marionette/content/marionette-elements.js");
+let marionetteLogObj = new MarionetteLogObj();
+
+let isB2G = false;
+
+let marionetteTimeout = null;
+let winUtil = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
+let listenerId = null; //unique ID of this listener
+let activeFrame = null;
+let win = content;
+let elementManager = new ElementManager([]);
+
+/**
+ * Called when listener is first started up. 
+ * The listener sends its unique window ID and its current URI to the actor.
+ * If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
+ */
+function registerSelf() {
+  let register = sendSyncMessage("Marionette:register", {value: winUtil.outerWindowID, href: content.location.href});
+  
+  if (register[0]) {
+    listenerId = register[0];
+    startListeners();
+  }
+}
+
+/**
+ * Start all message listeners
+ */
+function startListeners() {
+  addMessageListener("Marionette:newSession" + listenerId, newSession);
+  addMessageListener("Marionette:executeScript" + listenerId, executeScript);
+  addMessageListener("Marionette:setScriptTimeout" + listenerId, setScriptTimeout);
+  addMessageListener("Marionette:executeAsyncScript" + listenerId, executeAsyncScript);
+  addMessageListener("Marionette:executeJSScript" + listenerId, executeJSScript);
+  addMessageListener("Marionette:setSearchTimeout" + listenerId, setSearchTimeout);
+  addMessageListener("Marionette:goUrl" + listenerId, goUrl);
+  addMessageListener("Marionette:getUrl" + listenerId, getUrl);
+  addMessageListener("Marionette:goBack" + listenerId, goBack);
+  addMessageListener("Marionette:goForward" + listenerId, goForward);
+  addMessageListener("Marionette:refresh" + listenerId, refresh);
+  addMessageListener("Marionette:findElementContent" + listenerId, findElementContent);
+  addMessageListener("Marionette:findElementsContent" + listenerId, findElementsContent);
+  addMessageListener("Marionette:clickElement" + listenerId, clickElement);
+  addMessageListener("Marionette:switchToFrame" + listenerId, switchToFrame);
+  addMessageListener("Marionette:deleteSession" + listenerId, deleteSession);
+  addMessageListener("Marionette:sleepSession" + listenerId, sleepSession);
+}
+
+/**
+ * Called when we start a new session. It registers the
+ * current environment, and resets all values
+ */
+function newSession(msg) {
+  isB2G = msg.json.B2G;
+  resetValues();
+}
+ 
+/**
+ * Puts the current session to sleep, so all listeners are removed except
+ * for the 'restart' listener. This is used to keep the content listener
+ * alive for reuse in B2G instead of reloading it each time.
+ */
+function sleepSession(msg) {
+  deleteSession();
+  addMessageListener("Marionette:restart", restart);
+}
+
+/**
+ * Restarts all our listeners after this listener was put to sleep
+ */
+function restart() {
+  removeMessageListener("Marionette:restart", restart);
+  registerSelf();
+}
+
+/**
+ * Removes all listeners
+ */
+function deleteSession(msg) {
+  removeMessageListener("Marionette:newSession" + listenerId, newSession);
+  removeMessageListener("Marionette:executeScript" + listenerId, executeScript);
+  removeMessageListener("Marionette:setScriptTimeout" + listenerId, setScriptTimeout);
+  removeMessageListener("Marionette:executeAsyncScript" + listenerId, executeAsyncScript);
+  removeMessageListener("Marionette:executeJSScript" + listenerId, executeJSScript);
+  removeMessageListener("Marionette:setSearchTimeout" + listenerId, setSearchTimeout);
+  removeMessageListener("Marionette:goUrl" + listenerId, goUrl);
+  removeMessageListener("Marionette:getUrl" + listenerId, getUrl);
+  removeMessageListener("Marionette:goBack" + listenerId, goBack);
+  removeMessageListener("Marionette:goForward" + listenerId, goForward);
+  removeMessageListener("Marionette:refresh" + listenerId, refresh);
+  removeMessageListener("Marionette:findElementContent" + listenerId, findElementContent);
+  removeMessageListener("Marionette:findElementsContent" + listenerId, findElementsContent);
+  removeMessageListener("Marionette:clickElement" + listenerId, clickElement);
+  removeMessageListener("Marionette:switchToFrame" + listenerId, switchToFrame);
+  removeMessageListener("Marionette:deleteSession" + listenerId, deleteSession);
+  removeMessageListener("Marionette:sleepSession" + listenerId, sleepSession);
+  this.elementManager.reset();
+}
+
+/*
+ * Helper methods 
+ */
+
+/**
+ * Generic method to send a message to the server
+ */
+function sendToServer(msg, value, command_id) {
+  if (command_id) {
+    value.command_id = command_id;
+  }
+  sendAsyncMessage(msg, value);
+}
+
+/**
+ * Send response back to server
+ */
+function sendResponse(value, command_id) {
+  sendToServer("Marionette:done", value, command_id);
+}
+
+/**
+ * Send ack back to server
+ */
+function sendOk(command_id) {
+  sendToServer("Marionette:ok", {}, command_id);
+}
+
+/**
+ * Send log message to server
+ */
+function sendLog(msg) {
+  sendToServer("Marionette:log", { message: msg });
+}
+
+/**
+ * Send error message to server
+ */
+function sendError(message, status, trace, command_id) {
+  let error_msg = { message: message, status: status, stacktrace: trace };
+  sendToServer("Marionette:error", error_msg, command_id);
+}
+
+/**
+ * Clear test values after completion of test
+ */
+function resetValues() {
+  marionetteTimeout = null;
+}
+
+/**
+ * send error when we detect an unload event during async scripts
+ */
+function errUnload() {
+  sendError("unload was called", 17, null);
+}
+
+
+/*
+ * Marionette Methods
+ */
+
+/**
+ * Returns a content sandbox that can be used by the execute_foo functions.
+ */
+function createExecuteContentSandbox(aWindow, marionette, args) {
+  try {
+    args = elementManager.convertWrappedArguments(args, aWindow);
+  }
+  catch(e) {
+    sendError(e.message, e.num, e.stack);
+    return;
+  }
+
+  let sandbox = new Cu.Sandbox(aWindow);
+  sandbox.window = aWindow;
+  sandbox.document = sandbox.window.document;
+  sandbox.navigator = sandbox.window.navigator;
+  sandbox.__namedArgs = elementManager.applyNamedArgs(args);
+  sandbox.__marionetteParams = args;
+  sandbox.__proto__ = sandbox.window;
+
+  marionette.exports.forEach(function(fn) {
+    sandbox[fn] = marionette[fn].bind(marionette);
+  });
+
+  return sandbox;
+}
+
+/**
+ * Execute the given script either as a function body (executeScript)
+ * or directly (for 'mochitest' like JS Marionette tests)
+ */
+function executeScript(msg, directInject) {
+  let script = msg.json.value;
+  let marionette = new Marionette(false, win, "content", marionetteLogObj);
+
+  let sandbox = createExecuteContentSandbox(win, marionette, msg.json.args);
+  if (!sandbox)
+    return;
+
+  sandbox.finish = function sandbox_finish() {
+    return marionette.generate_results();
+  };
+
+  try {
+    if (directInject) {
+      let res = Cu.evalInSandbox(script, sandbox, "1.8");
+      sendSyncMessage("Marionette:testLog", {value: elementManager.wrapValue(marionetteLogObj.getLogs())});
+      marionetteLogObj.clearLogs();
+      if (res == undefined || res.passed == undefined) {
+        sendError("Marionette.finish() not called", 17, null);
+      }
+      else {
+        sendResponse({value: elementManager.wrapValue(res)});
+      }
+    }
+    else {
+      let scriptSrc = "let __marionetteFunc = function(){" + script + "};" +
+                      "__marionetteFunc.apply(null, __marionetteParams);";
+      let res = Cu.evalInSandbox(scriptSrc, sandbox, "1.8");
+      sendSyncMessage("Marionette:testLog", {value: elementManager.wrapValue(marionetteLogObj.getLogs())});
+      marionetteLogObj.clearLogs();
+      sendResponse({value: elementManager.wrapValue(res)});
+    }
+  }
+  catch (e) {
+    // 17 = JavascriptException
+    sendError(e.name + ': ' + e.message, 17, e.stack);
+  }
+}
+
+/**
+ * Function to set the timeout of asynchronous scripts
+ */
+function setScriptTimeout(msg) {
+  marionetteTimeout = msg.json.value;
+}
+
+/**
+ * Execute async script
+ */
+function executeAsyncScript(msg) {
+  executeWithCallback(msg);
+}
+
+/**
+ * Execute pure JS test. Handles both async and sync cases.
+ */
+function executeJSScript(msg) {
+  if (msg.json.timeout) {
+    executeWithCallback(msg, msg.json.timeout);
+  }
+  else {
+    executeScript(msg, true);
+  }
+}
+
+/**
+ * This function is used by executeAsync and executeJSScript to execute a script
+ * in a sandbox. 
+ * 
+ * For executeJSScript, it will return a message only when the finish() method is called.
+ * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1] 
+ * method is called, or if it times out.
+ */
+function executeWithCallback(msg, timeout) {
+  win.addEventListener("unload", errUnload, false);
+  let script = msg.json.value;
+  let command_id = msg.json.id;
+
+  // Error code 28 is scriptTimeout, but spec says execute_async should return 21 (Timeout),
+  // see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/execute_async.
+  // However Selenium code returns 28, see
+  // http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js.
+  // We'll stay compatible with the Selenium code.
+  let timeoutId = win.setTimeout(function() {
+    contentAsyncReturnFunc('timed out', 28);
+  }, marionetteTimeout);
+  win.addEventListener('error', function win__onerror(evt) {
+    win.removeEventListener('error', win__onerror, true);
+    contentAsyncReturnFunc(evt, 17);
+    return true;
+  }, true);
+
+  function contentAsyncReturnFunc(value, status) {
+    win.removeEventListener("unload", errUnload, false);
+
+    /* clear all timeouts potentially generated by the script*/
+    for(let i=0; i<=timeoutId; i++) {
+      win.clearTimeout(i);
+    }
+
+    sendSyncMessage("Marionette:testLog", {value: elementManager.wrapValue(marionetteLogObj.getLogs())});
+    marionetteLogObj.clearLogs();
+    if (status == 0){
+      sendResponse({value: elementManager.wrapValue(value), status: status}, command_id);
+    }
+    else {
+      sendError(value, status, null, command_id);
+    }
+  };
+
+  let scriptSrc;
+  if (timeout) {
+    if (marionetteTimeout == null || marionetteTimeout == 0) {
+      sendError("Please set a timeout", 21, null);
+    }
+    scriptSrc = script;
+  }
+  else {
+    scriptSrc = "let marionetteScriptFinished = function(value) { return asyncComplete(value,0);};" +
+                "__marionetteParams.push(marionetteScriptFinished);" +
+                "let __marionetteFunc = function() { " + script + "};" +
+                "__marionetteFunc.apply(null, __marionetteParams); ";
+  }
+
+  let marionette = new Marionette(true, win, "content", marionetteLogObj);
+
+  let sandbox = createExecuteContentSandbox(win, marionette, msg.json.args);
+  if (!sandbox)
+    return;
+
+  sandbox.asyncComplete = contentAsyncReturnFunc;
+  sandbox.finish = function sandbox_finish() {
+    contentAsyncReturnFunc(marionette.generate_results(), 0);
+  };
+
+  try {
+   Cu.evalInSandbox(scriptSrc, sandbox, "1.8");
+  } catch (e) {
+    // 17 = JavascriptException
+    sendError(e.name + ': ' + e.message, 17, e.stack);
+  }
+}
+
+/**
+ * Function to set the timeout period for element searching 
+ */
+function setSearchTimeout(msg) {
+  try {
+    elementManager.setSearchTimeout(msg.json.value);
+  }
+  catch (e) {
+    sendError(e.message, e.num, e.stack);
+    return;
+  }
+  sendOk();
+}
+
+/**
+ * Navigate to URI. Handles the case where we navigate within an iframe.
+ * All other navigation is handled by the server (in chrome space).
+ */
+function goUrl(msg) {
+  if (activeFrame != null) {
+    win.document.location = msg.json.value;
+    //TODO: replace this with event firing when Bug 720714 is resolved
+    let checkTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+    let checkLoad = function () { 
+                      if (win.document.readyState == "complete") { 
+                        sendOk();
+                      } 
+                      else { 
+                        checkTimer.initWithCallback(checkLoad, 100, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+                      }
+                    };
+    checkLoad();
+  }
+  else {
+    sendAsyncMessage("Marionette:goUrl", {value: msg.json.value});
+  }
+}
+
+/**
+ * Get the current URI
+ */
+function getUrl(msg) {
+  sendResponse({value: win.location.href});
+}
+
+/**
+ * Go back in history 
+ */
+function goBack(msg) {
+  win.history.back();
+  sendOk();
+}
+
+/**
+ * Go forward in history 
+ */
+function goForward(msg) {
+  win.history.forward();
+  sendOk();
+}
+
+/**
+ * Refresh the page
+ */
+function refresh(msg) {
+  win.location.reload(true);
+  let listen = function() { removeEventListener("DOMContentLoaded", arguments.callee, false); sendOk() } ;
+  addEventListener("DOMContentLoaded", listen, false);
+}
+
+/**
+ * Find an element in the document using requested search strategy 
+ */
+function findElementContent(msg) {
+  //Todo: extend to support findChildElement
+  let id;
+  try {
+    let notify = function(id) { sendResponse({value:id});};
+    id = elementManager.find(msg.json, win.document, notify, false);
+  }
+  catch (e) {
+    sendError(e.message, e.num, e.stack);
+    return;
+  }
+}
+
+/**
+ * Find elements in the document using requested search strategy 
+ */
+function findElementsContent(msg) {
+  //Todo: extend to support findChildElement
+  let id;
+  try {
+    let notify = function(id) { sendResponse({value:id});};
+    id = elementManager.find(msg.json, win.document, notify, true);
+  }
+  catch (e) {
+    sendError(e.message, e.num, e.stack);
+    return;
+  }
+}
+
+/**
+ * Send click event to element
+ */
+function clickElement(msg) {
+  let el;
+  try {
+    el = elementManager.getKnownElement(msg.json.element, win);
+  }
+  catch (e) {
+    sendError(e.message, e.num, e.stack);
+    return;
+  }
+  el.click();
+  sendOk();
+}
+
+/**
+ * Switch to frame given either the server-assigned element id,
+ * its index in window.frames, or the iframe's name or id.
+ */
+function switchToFrame(msg) {
+  let foundFrame = null;
+  if ((msg.json.value == null) && (msg.json.element == null)) {
+    win = content;
+    activeFrame = null;
+    content.focus();
+    sendOk();
+    return;
+  }
+  if (msg.json.element != undefined) {
+    if (elementManager.seenItems[msg.json.element] != undefined) {
+      let wantedFrame = elementManager.getKnownElement(msg.json.element, win);//HTMLIFrameElement
+      let numFrames = win.frames.length;
+      for (let i = 0; i < numFrames; i++) {
+        if (win.frames[i].frameElement == wantedFrame) {
+          win = win.frames[i]; 
+          activeFrame = i;
+          win.focus();
+          sendOk();
+          return;
+        }
+      }
+    }
+  }
+  switch(typeof(msg.json.value)) {
+    case "string" :
+      let foundById = null;
+      let numFrames = win.frames.length;
+      for (let i = 0; i < numFrames; i++) {
+        //give precedence to name
+        let frame = win.frames[i];
+        let frameElement = frame.frameElement;
+        if (frameElement.name == msg.json.value) {
+          foundFrame = i;
+          break;
+        } else if ((foundById == null) && (frameElement.id == msg.json.value)) {
+          foundById = i;
+        }
+      }
+      if ((foundFrame == null) && (foundById != null)) {
+        foundFrame = foundById;
+      }
+      break;
+    case "number":
+      if (win.frames[msg.json.value] != undefined) {
+        foundFrame = msg.json.value;
+      }
+      break;
+  }
+  //TODO: implement index
+  if (foundFrame != null) {
+    let frameWindow = win.frames[foundFrame];
+    activeFrame = foundFrame;
+    win = frameWindow;
+    win.focus();
+    sendOk();
+  } else {
+    sendError("Unable to locate frame: " + msg.json.value, 8, null);
+  }
+}
+
+//call register self when we get loaded
+registerSelf();
new file mode 100644
--- /dev/null
+++ b/testing/marionette/marionette-log-obj.js
@@ -0,0 +1,45 @@
+/* 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/. */
+
+function MarionetteLogObj() {
+  this.logs = [];
+}
+MarionetteLogObj.prototype = {
+  /**
+   * Log message. Accepts user defined log-level.
+   * @param msg String
+   *        The message to be logged
+   * @param level String
+   *        The logging level to be used
+   */
+  log: function ML_log(msg, level) {
+    let lev = level ? level : "INFO";
+    this.logs.push( [lev, msg, (new Date()).toString()]);
+  },
+
+  /**
+   * Add a list of logs to its list
+   * @param msgs Object
+   *        Takes a list of strings
+   */
+  addLogs: function ML_addLogs(msgs) {
+    for (let i = 0; i < msgs.length; i++) {
+      this.logs.push(msgs[i]);
+    }
+  },
+  
+  /**
+   * Return all logged messages.
+   */
+  getLogs: function ML_getLogs() {
+    return this.logs;
+  },
+
+  /**
+   * Clears the logs
+   */
+  clearLogs: function ML_clearLogs() {
+    this.logs = [];
+  },
+}
new file mode 100644
--- /dev/null
+++ b/testing/marionette/marionette-simpletest.js
@@ -0,0 +1,131 @@
+/* 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/. */
+/*
+ * The Marionette object, passed to the script context.
+ */
+
+function Marionette(is_async, window, context, logObj) {
+  this.is_async = is_async;
+  this.window = window;
+  this.tests = [];
+  this.logObj = logObj;
+  this.context = context;
+  this.exports = ['ok', 'is', 'isnot', 'log', 'getLogs', 'generate_results', 'waitFor'];
+}
+
+Marionette.prototype = {
+  ok: function Marionette__ok(condition, name, diag) {
+    let test = {'result': !!condition, 'name': name, 'diag': diag};
+    this.logResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL");
+    this.tests.push(test);
+  },
+
+  is: function Marionette__is(a, b, name) {
+    let pass = (a == b);
+    let diag = pass ? this.repr(a) + " should equal " + this.repr(b)
+                    : "got " + this.repr(a) + ", expected " + this.repr(b);
+    this.ok(pass, name, diag);
+  },
+
+  isnot: function Marionette__isnot (a, b, name) {
+    let pass = (a != b);
+    let diag = pass ? this.repr(a) + " should not equal " + this.repr(b)
+                    : "didn't expect " + this.repr(a) + ", but got it";
+    this.ok(pass, name, diag);
+  },
+
+  log: function Marionette__log(msg, level) {
+    if (this.logObj != null) {
+      this.logObj.log(msg, level);
+    }
+  },
+
+  getLogs: function Marionette__getLogs() {
+    if (this.logObj != null) {
+      this.logObj.getLogs();
+    }
+  },
+
+  generate_results: function Marionette__generate_results() {
+    let passed = 0;
+    let failed = 0;
+    let failures = [];
+    for (let i in this.tests) {
+      if(this.tests[i].result) {
+        passed++;
+      }
+      else {
+        failed++;
+        failures.push({'name': this.tests[i].name,
+                       'diag': this.tests[i].diag});
+      }
+    }
+    return {"passed": passed, "failed": failed, "failures": failures};
+  },
+
+  logToFile: function Marionette__logToFile(file) {
+    //TODO
+  },
+
+  logResult: function Marionette__logResult(test, passString, failString) {
+    //TODO: dump to file
+    let resultString = test.result ? passString : failString;
+    let diagnostic = test.name + (test.diag ? " - " + test.diag : "");
+    let msg = [resultString, diagnostic].join(" | ");
+    dump("MARIONETTE TEST RESULT:" + msg + "\n");
+  },
+
+  repr: function Marionette__repr(o) {
+      if (typeof(o) == "undefined") {
+          return "undefined";
+      } else if (o === null) {
+          return "null";
+      }
+      try {
+          if (typeof(o.__repr__) == 'function') {
+              return o.__repr__();
+          } else if (typeof(o.repr) == 'function' && o.repr != arguments.callee) {
+              return o.repr();
+          }
+     } catch (e) {
+     }
+     try {
+          if (typeof(o.NAME) == 'string' && (
+                  o.toString == Function.prototype.toString ||
+                  o.toString == Object.prototype.toString
+              )) {
+              return o.NAME;
+          }
+      } catch (e) {
+      }
+      let ostring;
+      try {
+          ostring = (o + "");
+      } catch (e) {
+          return "[" + typeof(o) + "]";
+      }
+      if (typeof(o) == "function") {
+          o = ostring.replace(/^\s+/, "");
+          let idx = o.indexOf("{");
+          if (idx != -1) {
+              o = o.substr(0, idx) + "{...}";
+          }
+      }
+      return ostring;
+  },
+
+  defaultWaitForTimeout: 10000,
+  waitFor: function test_waitFor(callback, test, timeout) {
+      if (test()) {
+          callback();
+          return;
+      }
+      timeout = timeout || Date.now();
+      if (Date.now() - timeout > this.defaultWaitForTimeout) {
+          throw 'waitFor timeout';
+      }
+      this.window.setTimeout(this.waitFor.bind(this), 100, callback, test, timeout);
+  },
+};
+
new file mode 100644
--- /dev/null
+++ b/testing/marionette/tests/Makefile.in
@@ -0,0 +1,17 @@
+# 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/.
+
+DEPTH           = ../../..
+topsrcdir       = @top_srcdir@
+srcdir          = @srcdir@
+VPATH           = @srcdir@
+relativesrcdir = testing/marionette/tests
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE          = test_marionette
+
+XPCSHELL_TESTS = unit
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/testing/marionette/tests/unit/head_mar.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource:///modules/devtools/dbg-server.jsm");
+Cu.import("resource:///modules/devtools/dbg-client.jsm");
new file mode 100644
--- /dev/null
+++ b/testing/marionette/tests/unit/test_marionette_err.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test()
+{
+  //DebuggerServer.addActors("resource:///modules/marionette-actors.js");
+  //DebuggerServer.init();
+
+  add_test(test_error);
+
+  run_next_test();
+}
+function test_error()
+{
+  //DebuggerServer.openListener(2828, true);
+  do_test_pending();
+  received = false;
+  id = "";
+
+  let transport = debuggerSocketConnect("127.0.0.1", 2828);
+  transport.hooks = {
+    onPacket: function(aPacket) {
+      this.onPacket = function(aPacket) {
+        if (received) {
+          do_check_eq(aPacket.from, id);
+          if(aPacket.error == undefined) {
+            do_throw("Expected error, instead received 'done' packet!");
+            transport.close();
+          }
+          else {
+            transport.close();
+          }
+        }
+        else {
+          received = true;
+          id = aPacket.id;
+          transport.send({to: id,
+                        type: "nonExistent",
+                        });
+        }
+      }
+      transport.send({to: "root",
+                      type: "getMarionetteID",
+                      });
+    },
+    onClosed: function(aStatus) {
+      do_check_eq(aStatus, 0);
+      do_test_finished();
+      run_next_test();
+    },
+  };
+  transport.ready();
+}
new file mode 100644
--- /dev/null
+++ b/testing/marionette/tests/unit/test_marionette_exec.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test()
+{
+  //DebuggerServer.addActors("resource:///modules/marionette-actors.js");
+  //DebuggerServer.init();
+
+  add_test(test_execute);
+  run_next_test();
+}
+
+function test_execute()
+{
+  //DebuggerServer.openListener(2828, true);
+  do_test_pending();
+  got_session = false;
+  received = false;
+  id = "";
+
+  let transport = debuggerSocketConnect("127.0.0.1", 2828);
+  transport.hooks = {
+    onPacket: function(aPacket) {
+      this.onPacket = function(aPacket) {
+        if(!got_session) {
+          got_session=true;
+          id = aPacket.id;
+          transport.send({to: id,
+                        type: "newSession",
+                        });
+        } 
+        else {
+        if (received) {
+          do_check_eq(aPacket.from, id);
+          if(aPacket.value == "3") {
+            transport.send({to: id,
+                          type: "executeScript",
+                          value: "return 5+arguments[0];",
+                          args: [1],
+                          });
+          }
+          if(aPacket.value == "6") {
+              transport.send({to: id,
+                            type: "deleteSession"
+                            });
+            transport.close();
+          }
+          if(aPacket.error != undefined) {
+            do_throw("Received error: " + aPacket.error);
+            transport.close();
+          }
+        }
+        else {
+          received = true;
+          do_check_eq('session', aPacket.value);
+          transport.send({to: id,
+                        type: "executeScript",
+                        value: "alert('asdf'); return 2+1;",
+                        });
+        }
+      }
+      }
+      transport.send({to: "root",
+                      type: "getMarionetteID",
+                      });
+    },
+    onClosed: function(aStatus) {
+      do_check_eq(aStatus, 0);
+      do_test_finished();
+      run_next_test();
+      delete transport;
+    },
+  };
+  transport.ready();
+}
new file mode 100644
--- /dev/null
+++ b/testing/marionette/tests/unit/test_marionette_execAsync.js
@@ -0,0 +1,193 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test()
+{
+  add_test(test_executeAsync);
+  add_test(test_executeAsyncTimeout);
+  add_test(test_executeAsyncUnload); //TODO: fix unload listener
+  run_next_test();
+}
+
+function test_executeAsync()
+{
+  do_test_pending();
+  got_session = false;
+  received = false;
+  id = "";
+
+  let transport = debuggerSocketConnect("127.0.0.1", 2828);
+  transport.hooks = {
+    onPacket: function(aPacket) {
+      this.onPacket = function(aPacket) {
+        if(!got_session) {
+          got_session=true;
+          id = aPacket.id;
+          transport.send({to: id,
+                        type: "newSession",
+                        });
+        } 
+        else {
+        if (received) {
+          do_check_eq(aPacket.from, id);
+          if(aPacket.ok == true) {
+            transport.send({to: id,
+                          type: "executeAsyncScript",
+                          value: "arguments[arguments.length - 1](5+1);",
+                          });
+          }
+          else if(aPacket.value == "6") {
+              transport.send({to: id,
+                            type: "deleteSession",
+                            });
+            transport.close();
+          }
+          else if(aPacket.error != undefined) {
+            do_throw("Received error: " + aPacket.error);
+            transport.close();
+          }
+        }
+        else {
+          received = true;
+          do_check_eq('session', aPacket.value);
+          transport.send({to: id,
+                        type: "setScriptTimeout",
+                        value: "2000",
+                        });
+        }
+      }
+      }
+      transport.send({to: "root",
+                      type: "getMarionetteID",
+                      });
+    },
+    onClosed: function(aStatus) {
+      do_check_eq(aStatus, 0);
+      do_test_finished();
+      delete transport;
+      run_next_test();
+    },
+  };
+  transport.ready();
+}
+
+function test_executeAsyncTimeout()
+{
+  do_test_pending();
+  got_session = false;
+  received = false;
+  id = "";
+
+  let transport = debuggerSocketConnect("127.0.0.1", 2828);
+  transport.hooks = {
+    onPacket: function(aPacket) {
+      this.onPacket = function(aPacket) {
+        if(!got_session) {
+          got_session=true;
+          id = aPacket.id;
+          transport.send({to: id,
+                        type: "newSession",
+                        });
+        } 
+        else {
+        if (received) {
+          do_check_eq(aPacket.from, id);
+          if(aPacket.ok == true) {
+            transport.send({to: id,
+                          type: "executeAsyncScript",
+                          value: "window.setTimeout(arguments[arguments.length - 1], 5000, 6);",
+                          });
+          }
+          else if(aPacket.value == "6") {
+            do_throw("Should have timed out!");
+            transport.close();
+          }
+          else if(aPacket.error != undefined) {
+            do_check_eq(aPacket.error.message, "timed out");
+            do_check_eq(aPacket.error.status, 28);
+            transport.close();
+          }
+        }
+        else {
+          received = true;
+          do_check_eq('session', aPacket.value);
+          transport.send({to: id,
+                        type: "setScriptTimeout",
+                        value: "2000",
+                        });
+        }
+      }
+      }
+      transport.send({to: "root",
+                      type: "getMarionetteID",
+                      });
+    },
+    onClosed: function(aStatus) {
+      do_check_eq(aStatus, 0);
+      do_test_finished();
+      delete transport;
+      run_next_test();
+    },
+  };
+  transport.ready();
+}
+
+function test_executeAsyncUnload()
+{
+  do_test_pending();
+  got_session = false;
+  received = false;
+  id = "";
+
+  let transport = debuggerSocketConnect("127.0.0.1", 2828);
+  transport.hooks = {
+    onPacket: function(aPacket) {
+      this.onPacket = function(aPacket) {
+        if(!got_session) {
+          got_session=true;
+          id = aPacket.id;
+          transport.send({to: id,
+                        type: "newSession",
+                        });
+        } 
+        else {
+        if (received) {
+          do_check_eq(aPacket.from, id);
+          if(aPacket.ok == true) {
+            transport.send({to: id,
+                          type: "executeAsyncScript",
+                          value: "window.location.reload();",
+                          });
+          }
+          else if(aPacket.value == "6") {
+            do_throw("Should have thrown unload error!");
+            transport.close();
+          }
+          else if(aPacket.error != undefined) {
+            do_check_eq(aPacket.error.status, 17);
+            transport.close();
+          }
+        }
+        else {
+          received = true;
+          do_check_eq('session', aPacket.value);
+          transport.send({to: id,
+                        type: "setScriptTimeout",
+                        value: "2000",
+                        });
+        }
+      }
+      }
+      transport.send({to: "root",
+                      type: "getMarionetteID",
+                      });
+    },
+    onClosed: function(aStatus) {
+      do_check_eq(aStatus, 0);
+      do_test_finished();
+      delete transport;
+      run_next_test();
+    },
+  };
+  transport.ready();
+}
new file mode 100644
--- /dev/null
+++ b/testing/marionette/tests/unit/test_marionette_execjs.js
@@ -0,0 +1,365 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test()
+{
+  add_test(test_execute);
+  add_test(test_execute_async);
+  add_test(test_execute_async_timeout);
+  add_test(test_execute_chrome);
+  add_test(test_execute_async_chrome);
+  add_test(test_execute_async_timeout_chrome);
+  run_next_test();
+}
+
+function test_execute()
+{
+  do_test_pending();
+  got_session = false;
+  received = false;
+  id = "";
+
+  let transport = debuggerSocketConnect("127.0.0.1", 2828);
+  transport.hooks = {
+    onPacket: function(aPacket) {
+      this.onPacket = function(aPacket) {
+        if(!got_session) {
+          got_session=true;
+          id = aPacket.id;
+          transport.send({to: id,
+                          type: "newSession",
+                        });
+        } 
+        else {
+          if (received) {
+            do_check_eq(aPacket.from, id);
+            do_check_eq(aPacket.value.passed, 1);
+            do_check_eq(aPacket.value.failed, 0);
+            transport.close();
+          }
+          else {
+            received = true;
+            do_check_eq('mobile', aPacket.value);
+            transport.send({to: id,
+                        type: "executeJSScript",
+                        value: "Marionette.is(1,1, 'should return 1'); Marionette.finish();",
+                        timeout: false
+                        });
+          }
+        }
+      }
+      transport.send({to: "root",
+                      type: "getMarionetteID",
+                      });
+    },
+    onClosed: function(aStatus) {
+      do_check_eq(aStatus, 0);
+      do_test_finished();
+      run_next_test();
+      delete transport;
+    },
+  };
+  transport.ready();
+}
+
+function test_execute_async()
+{
+  do_test_pending();
+  got_session = false;
+  received = false;
+  received2 = false;
+  id = "";
+
+  let transport = debuggerSocketConnect("127.0.0.1", 2828);
+  transport.hooks = {
+    onPacket: function(aPacket) {
+      this.onPacket = function(aPacket) {
+        if(!got_session) {
+          got_session=true;
+          id = aPacket.id;
+          transport.send({to: id,
+                          type: "newSession",
+                        });
+        } 
+        else {
+          if (received) {
+            if(received2) {
+              do_check_eq(aPacket.from, id);
+              do_check_eq(aPacket.value.passed, 1);
+              do_check_eq(aPacket.value.failed, 0);
+              transport.close();
+            }
+            else {
+              received2 = true;
+              transport.send({to: id,
+                          type: "executeJSScript",
+                          value: "Marionette.is(1,1, 'should return 1'); Marionette.finish();",
+                          timeout: true 
+                          });
+            }
+          }
+          else {
+            received = true;
+            do_check_eq('mobile', aPacket.value);
+            transport.send({to: id,
+                          type: "setScriptTimeout",
+                          value: "2000",
+                          });
+            }
+          }
+        }
+      transport.send({to: "root",
+                      type: "getMarionetteID",
+                      });
+    },
+    onClosed: function(aStatus) {
+      do_check_eq(aStatus, 0);
+      do_test_finished();
+      run_next_test();
+      delete transport;
+    },
+  };
+  transport.ready();
+}
+
+function test_execute_async_timeout()
+{
+  do_test_pending();
+  got_session = false;
+  received = false;
+  received2 = false;
+  id = "";
+
+  let transport = debuggerSocketConnect("127.0.0.1", 2828);
+  transport.hooks = {
+    onPacket: function(aPacket) {
+      this.onPacket = function(aPacket) {
+        if(!got_session) {
+          got_session=true;
+          id = aPacket.id;
+          transport.send({to: id,
+                          type: "newSession",
+                        });
+        } 
+        else {
+          if (received) {
+            if(received2) {
+              do_check_eq(aPacket.from, id);
+              do_check_eq(aPacket.error.status, 28);
+              transport.close();
+            }
+            else {
+              received2 = true;
+              transport.send({to: id,
+                          type: "executeJSScript",
+                          value: "Marionette.is(1,1, 'should return 1');",
+                          timeout: true 
+                          });
+            }
+          }
+          else {
+            received = true;
+            do_check_eq('mobile', aPacket.value);
+            transport.send({to: id,
+                          type: "setScriptTimeout",
+                          value: "2000",
+                          });
+            }
+          }
+        }
+      transport.send({to: "root",
+                      type: "getMarionetteID",
+                      });
+    },
+    onClosed: function(aStatus) {
+      do_check_eq(aStatus, 0);
+      do_test_finished();
+      run_next_test();
+      delete transport;
+    },
+  };
+  transport.ready();
+}
+
+function test_execute_chrome()
+{
+  do_test_pending();
+  got_session = false;
+  got_context = false;
+  received = false;
+  id = "";
+
+  let transport = debuggerSocketConnect("127.0.0.1", 2828);
+  transport.hooks = {
+    onPacket: function(aPacket) {
+      this.onPacket = function(aPacket) {
+        if(!got_session) {
+          got_session=true;
+          id = aPacket.id;
+          transport.send({to: id,
+                          type: "newSession",
+                        });
+        } 
+        else if (!got_context) {
+          got_context = true;
+          do_check_eq('mobile', aPacket.value);
+          transport.send({to: id,
+                          type: "setContext",
+                          value: "chrome",
+                         });
+        }
+        else if (!received) {
+          received = true;
+          transport.send({to: id,
+                      type: "executeJSScript",
+                      value: "Marionette.is(1,1, 'should return 1'); Marionette.finish();",
+                      timeout: false
+                      });
+        }
+        else {
+          do_check_eq(aPacket.from, id);
+          do_check_eq(aPacket.value.passed, 1);
+          do_check_eq(aPacket.value.failed, 0);
+          transport.close();
+        }
+      }
+     transport.send({to: "root",
+                     type: "getMarionetteID",
+                    });
+    },
+    onClosed: function(aStatus) {
+      do_check_eq(aStatus, 0);
+      do_test_finished();
+      run_next_test();
+      delete transport;
+    },
+  };
+  transport.ready();
+}
+
+function test_execute_async_chrome()
+{
+  do_test_pending();
+  got_session = false;
+  got_context = false;
+  received = false;
+  received2 = false;
+  id = "";
+
+  let transport = debuggerSocketConnect("127.0.0.1", 2828);
+  transport.hooks = {
+    onPacket: function(aPacket) {
+      this.onPacket = function(aPacket) {
+        if(!got_session) {
+          got_session=true;
+          id = aPacket.id;
+          transport.send({to: id,
+                          type: "newSession",
+                        });
+        } 
+        else if (!got_context) {
+          got_context = true;
+          do_check_eq('mobile', aPacket.value);
+          transport.send({to: id,
+                          type: "setContext",
+                          value: "chrome",
+                         });
+        }
+        else if (!received) {
+          received = true;
+          transport.send({to: id,
+                        type: "setScriptTimeout",
+                        value: "2000",
+                        });
+        }
+        else if (!received2) {
+          received2 = true;
+          transport.send({to: id,
+                          type: "executeJSScript",
+                          value: "Marionette.is(1,1, 'should return 1'); Marionette.finish();",
+                          timeout: true 
+                          });
+        }
+        else {
+          do_check_eq(aPacket.from, id);
+          do_check_eq(aPacket.value.passed, 1);
+          do_check_eq(aPacket.value.failed, 0);
+          transport.close();
+        }
+      }
+      transport.send({to: "root",
+                      type: "getMarionetteID",
+                      });
+    },
+    onClosed: function(aStatus) {
+      do_check_eq(aStatus, 0);
+      do_test_finished();
+      run_next_test();
+      delete transport;
+    },
+  };
+  transport.ready();
+}
+
+function test_execute_async_timeout_chrome()
+{
+  do_test_pending();
+  got_session = false;
+  got_context = false;
+  received = false;
+  received2 = false;
+  id = "";
+
+  let transport = debuggerSocketConnect("127.0.0.1", 2828);
+  transport.hooks = {
+    onPacket: function(aPacket) {
+      this.onPacket = function(aPacket) {
+        if(!got_session) {
+          got_session=true;
+          id = aPacket.id;
+          transport.send({to: id,
+                          type: "newSession",
+                        });
+        } 
+        else if (!got_context) {
+          got_context = true;
+          do_check_eq('mobile', aPacket.value);
+          transport.send({to: id,
+                          type: "setContext",
+                          value: "chrome",
+                         });
+        }
+        else if (!received) {
+            received = true;
+            transport.send({to: id,
+                          type: "setScriptTimeout",
+                          value: "2000",
+                          });
+       }
+       else if (!received2) {
+              received2 = true;
+              transport.send({to: id,
+                          type: "executeJSScript",
+                          value: "Marionette.is(1,1, 'should return 1');",
+                          timeout: true 
+                          });
+       }
+       else {
+              do_check_eq(aPacket.from, id);
+              do_check_eq(aPacket.error.status, 28);
+              transport.close();
+       }
+      }
+      transport.send({to: "root",
+                      type: "getMarionetteID",
+                      });
+    },
+    onClosed: function(aStatus) {
+      do_check_eq(aStatus, 0);
+      do_test_finished();
+      run_next_test();
+      delete transport;
+    },
+  };
+  transport.ready();
+}
new file mode 100644
--- /dev/null
+++ b/testing/marionette/tests/unit/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head = head_mar.js
+tail =
+
+[test_marionette_exec.js]
+[test_marionette_execjs.js]
+[test_marionette_execAsync.js]
+[test_marionette_err.js]
--- a/testing/xpcshell/xpcshell.ini
+++ b/testing/xpcshell/xpcshell.ini
@@ -41,16 +41,17 @@ skip-if = os == "android"
 [include:toolkit/content/tests/unit/xpcshell.ini]
 [include:toolkit/mozapps/downloads/tests/unit/xpcshell.ini]
 [include:toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini]
 [include:toolkit/mozapps/extensions/test/xpcshell-unpack/xpcshell.ini]
 [include:toolkit/mozapps/update/test_timermanager/unit/xpcshell.ini]
 [include:toolkit/mozapps/update/test_svc/unit/xpcshell.ini]
 [include:toolkit/mozapps/update/test/unit/xpcshell.ini]
 [include:security/manager/ssl/tests/unit/xpcshell.ini]
+[include:testing/marionette/tests/unit/xpcshell.ini]
 [include:testing/xpcshell/example/unit/xpcshell.ini]
 [include:xpcom/tests/unit/xpcshell.ini]
 [include:modules/libpref/test/unit/xpcshell.ini]
 [include:intl/strres/tests/unit/xpcshell.ini]
 [include:intl/unicharutil/tests/unit/xpcshell.ini]
 [include:intl/uconv/tests/unit/xpcshell.ini]
 [include:netwerk/test/unit/xpcshell.ini]
 [include:netwerk/test/httpserver/test/xpcshell.ini]
--- a/toolkit/toolkit-tiers.mk
+++ b/toolkit/toolkit-tiers.mk
@@ -268,15 +268,19 @@ endif
 ifdef MOZ_LEAKY
 tier_platform_dirs        += tools/leaky
 endif
 
 ifdef MOZ_MAPINFO
 tier_platform_dirs	+= tools/codesighs
 endif
 
+ifdef ENABLE_MARIONETTE
+tier_platform_dirs += testing/marionette
+endif
+
 ifdef ENABLE_TESTS
 tier_platform_dirs += testing/mochitest
 tier_platform_dirs += testing/xpcshell
 tier_platform_dirs += testing/tools/screenshot
 tier_platform_dirs += testing/peptest
 tier_platform_dirs += testing/mozbase
 endif