Bug 982610 - Update TPS to use latest Mozmill 2.0.6. r=hskupin, a=testonly DONTBUILD
authorCosmin Malutan <cosmin.malutan@softvision.ro>
Thu, 22 May 2014 18:25:50 +0200
changeset 193405 c32d46efa75731ea1d4d9a57c0dd75845df3218a
parent 193404 8d180eb195d223e670e0f545cf4e9b2be47a730d
child 193406 b1f29e6adc698063079cc8c0b91561472af82d3e
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershskupin, testonly
bugs982610
milestone30.0
Bug 982610 - Update TPS to use latest Mozmill 2.0.6. r=hskupin, a=testonly DONTBUILD
services/sync/tests/tps/mozmill_sanity.js
services/sync/tests/tps/mozmill_sanity2.js
services/sync/tps/extensions/mozmill/chrome.manifest
services/sync/tps/extensions/mozmill/defaults/preferences/debug.js
services/sync/tps/extensions/mozmill/install.rdf
services/sync/tps/extensions/mozmill/resource/driver/controller.js
services/sync/tps/extensions/mozmill/resource/driver/elementslib.js
services/sync/tps/extensions/mozmill/resource/driver/mozelement.js
services/sync/tps/extensions/mozmill/resource/driver/mozmill.js
services/sync/tps/extensions/mozmill/resource/driver/msgbroker.js
services/sync/tps/extensions/mozmill/resource/modules/assertions.js
services/sync/tps/extensions/mozmill/resource/modules/controller.js
services/sync/tps/extensions/mozmill/resource/modules/driver.js
services/sync/tps/extensions/mozmill/resource/modules/elementslib.js
services/sync/tps/extensions/mozmill/resource/modules/errors.js
services/sync/tps/extensions/mozmill/resource/modules/frame.js
services/sync/tps/extensions/mozmill/resource/modules/init.js
services/sync/tps/extensions/mozmill/resource/modules/inspection.js
services/sync/tps/extensions/mozmill/resource/modules/jum.js
services/sync/tps/extensions/mozmill/resource/modules/l10n.js
services/sync/tps/extensions/mozmill/resource/modules/mozelement.js
services/sync/tps/extensions/mozmill/resource/modules/mozmill.js
services/sync/tps/extensions/mozmill/resource/modules/stack.js
services/sync/tps/extensions/mozmill/resource/modules/utils.js
services/sync/tps/extensions/mozmill/resource/modules/windows.js
services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js
services/sync/tps/extensions/mozmill/resource/stdlib/arrays.js
services/sync/tps/extensions/mozmill/resource/stdlib/dom.js
services/sync/tps/extensions/mozmill/resource/stdlib/httpd.js
services/sync/tps/extensions/mozmill/resource/stdlib/objects.js
services/sync/tps/extensions/mozmill/resource/stdlib/os.js
services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js
services/sync/tps/extensions/mozmill/resource/stdlib/strings.js
services/sync/tps/extensions/mozmill/resource/stdlib/utils.js
services/sync/tps/extensions/tps/resource/mozmill/sync.jsm
services/sync/tps/extensions/tps/resource/tps.jsm
--- a/services/sync/tests/tps/mozmill_sanity.js
+++ b/services/sync/tests/tps/mozmill_sanity.js
@@ -1,28 +1,29 @@
 /* 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/. */
 
-Components.utils.import('resource://tps/mozmill/sync.jsm');
+Components.utils.import('resource://tps/tps.jsm');
 
 var setupModule = function(module) {
-  controller = mozmill.getBrowserController();
+  module.controller = mozmill.getBrowserController();
   assert.ok(true, "SetupModule passes");
 }
 
 var setupTest = function(module) {
   assert.ok(true, "SetupTest passes");
 }
 
 var testTestStep = function() {
   assert.ok(true, "test Passes");
   controller.open("http://www.mozilla.org");
-  TPS.SetupSyncAccount();
-  assert.equal(TPS.Sync(SYNC_WIPE_SERVER), 0, "sync succeeded");
+
+  TPS.Login();
+  TPS.Sync(ACTIONS.ACTION_SYNC_WIPE_CLIENT);
 }
 
 var teardownTest = function () {
   assert.ok(true, "teardownTest passes");
 }
 
 var teardownModule = function() {
   assert.ok(true, "teardownModule passes");
--- a/services/sync/tests/tps/mozmill_sanity2.js
+++ b/services/sync/tests/tps/mozmill_sanity2.js
@@ -1,53 +1,15 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-var jum = {}; Components.utils.import('resource://mozmill/modules/jum.js', jum);
+/* 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/. */
 
 var setupModule = function(module) {
   module.controller = mozmill.getBrowserController();
 };
 
 var testGetNode = function() {
   controller.open("about:support");
   controller.waitForPageLoad();
 
-  var appbox = new elementslib.ID(controller.tabs.activeTab, "application-box");
-  jum.assert(appbox.getNode().innerHTML == 'Firefox', 'correct app name');
+  var appbox = findElement.ID(controller.tabs.activeTab, "application-box");
+  assert.waitFor(() => appbox.getNode().textContent == 'Firefox', 'correct app name');
 };
-
-const NAV_BAR             = '/id("main-window")/id("tab-view-deck")/{"flex":"1"}' +
-                            '/id("navigator-toolbox")/id("nav-bar")';
-const SEARCH_BAR          = NAV_BAR + '/id("search-container")/id("searchbar")';
-const SEARCH_TEXTBOX      = SEARCH_BAR      + '/anon({"anonid":"searchbar-textbox"})';
-const SEARCH_DROPDOWN     = SEARCH_TEXTBOX  + '/[0]/anon({"anonid":"searchbar-engine-button"})';
-const SEARCH_POPUP        = SEARCH_DROPDOWN + '/anon({"anonid":"searchbar-popup"})';
-const SEARCH_INPUT        = SEARCH_TEXTBOX  + '/anon({"class":"autocomplete-textbox-container"})' +
-                                              '/anon({"anonid":"textbox-input-box"})' +
-                                              '/anon({"anonid":"input"})';
-const SEARCH_CONTEXT      = SEARCH_TEXTBOX  + '/anon({"anonid":"textbox-input-box"})' +
-                                              '/anon({"anonid":"input-box-contextmenu"})';
-const SEARCH_GO_BUTTON    = SEARCH_TEXTBOX  + '/anon({"class":"search-go-container"})' +
-                                              '/anon({"class":"search-go-button"})';
-const SEARCH_AUTOCOMPLETE = '/id("main-window")/id("mainPopupSet")/id("PopupAutoComplete")';
-
-var testLookupExpressions = function() {
-  var item;
-  item = new elementslib.Lookup(controller.window.document, NAV_BAR);
-  controller.click(item);
-  item = new elementslib.Lookup(controller.window.document, SEARCH_BAR);
-  controller.click(item);
-  item = new elementslib.Lookup(controller.window.document, SEARCH_TEXTBOX);
-  controller.click(item);
-  item = new elementslib.Lookup(controller.window.document, SEARCH_DROPDOWN);
-  controller.click(item);
-  item = new elementslib.Lookup(controller.window.document, SEARCH_POPUP);
-  controller.click(item);
-  item = new elementslib.Lookup(controller.window.document, SEARCH_INPUT);
-  controller.click(item);
-  item = new elementslib.Lookup(controller.window.document, SEARCH_CONTEXT);
-  controller.click(item);
-  item = new elementslib.Lookup(controller.window.document, SEARCH_GO_BUTTON);
-  controller.click(item);
-  item = new elementslib.Lookup(controller.window.document, SEARCH_AUTOCOMPLETE);
-  controller.click(item);
-};
old mode 100644
new mode 100755
deleted file mode 100644
--- a/services/sync/tps/extensions/mozmill/defaults/preferences/debug.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/* 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/. */
-
-/* debugging prefs */
-pref("browser.dom.window.dump.enabled", true);
-pref("javascript.options.showInConsole", true);
old mode 100644
new mode 100755
--- a/services/sync/tps/extensions/mozmill/install.rdf
+++ b/services/sync/tps/extensions/mozmill/install.rdf
@@ -1,63 +1,28 @@
 <?xml version="1.0"?>
 <!-- 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/. -->
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
    <Description about="urn:mozilla:install-manifest">
      <em:id>mozmill@mozilla.com</em:id>
-     <em:name>MozMill</em:name>
-     <em:version>2.0b1</em:version>
-     <em:creator>Adam Christian</em:creator>
-     <em:description>A testing extension based on the Windmill Testing Framework client source</em:description>
+     <em:name>Mozmill</em:name>
+     <em:version>2.0.6</em:version>
+     <em:description>UI Automation tool for Mozilla applications</em:description>
      <em:unpack>true</em:unpack>
+
+     <em:creator>Mozilla Automation and Testing Team</em:creator>
+     <em:contributor>Adam Christian</em:contributor>
+     <em:contributor>Mikeal Rogers</em:contributor>
+
      <em:targetApplication>
-       <!-- Firefox -->
-       <Description>
-         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-         <em:minVersion>3.5</em:minVersion>
-         <em:maxVersion>12.*</em:maxVersion>
-       </Description>
-     </em:targetApplication>
-     <em:targetApplication>
-       <!-- Thunderbird -->
-       <Description>
-         <em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id>
-         <em:minVersion>3.0a1pre</em:minVersion>
-         <em:maxVersion>9.*</em:maxVersion>
-       </Description>
-     </em:targetApplication>
-     <em:targetApplication>
-       <!-- Sunbird -->
        <Description>
-         <em:id>{718e30fb-e89b-41dd-9da7-e25a45638b28}</em:id>
-         <em:minVersion>0.6a1</em:minVersion>
-         <em:maxVersion>1.0pre</em:maxVersion>
-       </Description>
-     </em:targetApplication>
-     <em:targetApplication>
-       <!-- SeaMonkey -->
-       <Description>
-         <em:id>{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}</em:id>
-         <em:minVersion>2.0a1</em:minVersion>
-         <em:maxVersion>9.*</em:maxVersion>
+         <em:id>toolkit@mozilla.org</em:id>
+         <em:minVersion>10.0</em:minVersion>
+         <em:maxVersion>31.*</em:maxVersion>
        </Description>
      </em:targetApplication>
-	   <em:targetApplication>
-	      <!-- Songbird -->
-	      <Description>
-	        <em:id>songbird@songbirdnest.com</em:id>
-	        <em:minVersion>0.3pre</em:minVersion>
-	        <em:maxVersion>1.3.0a</em:maxVersion>
-	      </Description>
-	   </em:targetApplication>
-	   <em:targetApplication>
-         <Description>
-          <em:id>toolkit@mozilla.org</em:id>
-          <em:minVersion>1.9.1</em:minVersion>
-          <em:maxVersion>9.*</em:maxVersion>
-         </Description>
-     </em:targetApplication>
    </Description>
 </RDF>
new file mode 100644
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/driver/controller.js
@@ -0,0 +1,1150 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["MozMillController", "globalEventRegistry",
+                        "sleep", "windowMap"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+var EventUtils = {}; Cu.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
+
+var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
+var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
+var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
+var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
+var mozelement = {}; Cu.import('resource://mozmill/driver/mozelement.js', mozelement);
+var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
+var windows = {}; Cu.import('resource://mozmill/modules/windows.js', windows);
+
+// Declare most used utils functions in the controller namespace
+var assert = new assertions.Assert();
+var waitFor = assert.waitFor;
+
+var sleep = utils.sleep;
+
+// For Mozmill 1.5 backward compatibility
+var windowMap = windows.map;
+
+waitForEvents = function () {
+}
+
+waitForEvents.prototype = {
+  /**
+   * Initialize list of events for given node
+   */
+  init: function waitForEvents_init(node, events) {
+    if (node.getNode != undefined)
+      node = node.getNode();
+
+    this.events = events;
+    this.node = node;
+    node.firedEvents = {};
+    this.registry = {};
+
+    for each (var e in events) {
+      var listener = function (event) {
+        this.firedEvents[event.type] = true;
+      }
+
+      this.registry[e] = listener;
+      this.registry[e].result = false;
+      this.node.addEventListener(e, this.registry[e], true);
+    }
+  },
+
+  /**
+   * Wait until all assigned events have been fired
+   */
+  wait: function waitForEvents_wait(timeout, interval) {
+    for (var e in this.registry) {
+      assert.waitFor(function () {
+        return this.node.firedEvents[e] == true;
+      }, "waitForEvents.wait(): Event '" + ex + "' has been fired.", timeout, interval);
+
+      this.node.removeEventListener(e, this.registry[e], true);
+    }
+  }
+}
+
+/**
+ * Class to handle menus and context menus
+ *
+ * @constructor
+ * @param {MozMillController} controller
+ *        Mozmill controller of the window under test
+ * @param {string} menuSelector
+ *        jQuery like selector string of the element
+ * @param {object} document
+ *        Document to use for finding the menu
+ *        [optional - default: aController.window.document]
+ */
+var Menu = function (controller, menuSelector, document) {
+  this._controller = controller;
+  this._menu = null;
+
+  document = document || controller.window.document;
+  var node = document.querySelector(menuSelector);
+  if (node) {
+    // We don't unwrap nodes automatically yet (Bug 573185)
+    node = node.wrappedJSObject || node;
+    this._menu = new mozelement.Elem(node);
+  } else {
+    throw new Error("Menu element '" + menuSelector + "' not found.");
+  }
+}
+
+Menu.prototype = {
+
+  /**
+   * Open and populate the menu
+   *
+   * @param {ElemBase} contextElement
+   *        Element whose context menu has to be opened
+   * @returns {Menu} The Menu instance
+   */
+  open: function Menu_open(contextElement) {
+    // We have to open the context menu
+    var menu = this._menu.getNode();
+    if ((menu.localName == "popup" || menu.localName == "menupopup") &&
+        contextElement && contextElement.exists()) {
+      this._controller.rightClick(contextElement);
+      assert.waitFor(function () {
+        return menu.state == "open";
+      }, "Context menu has been opened.");
+    }
+
+    // Run through the entire menu and populate with dynamic entries
+    this._buildMenu(menu);
+
+    return this;
+  },
+
+  /**
+   * Close the menu
+   *
+   * @returns {Menu} The Menu instance
+   */
+  close: function Menu_close() {
+    var menu = this._menu.getNode();
+
+    this._controller.keypress(this._menu, "VK_ESCAPE", {});
+    assert.waitFor(function () {
+      return menu.state == "closed";
+    }, "Context menu has been closed.");
+
+    return this;
+  },
+
+  /**
+   * Retrieve the specified menu entry
+   *
+   * @param {string} itemSelector
+   *        jQuery like selector string of the menu item
+   * @returns {ElemBase} Menu element
+   * @throws Error If menu element has not been found
+   */
+  getItem: function Menu_getItem(itemSelector) {
+    // Run through the entire menu and populate with dynamic entries
+    this._buildMenu(this._menu.getNode());
+
+    var node = this._menu.getNode().querySelector(itemSelector);
+
+    if (!node) {
+      throw new Error("Menu entry '" + itemSelector + "' not found.");
+    }
+
+    return new mozelement.Elem(node);
+  },
+
+  /**
+   * Click the specified menu entry
+   *
+   * @param {string} itemSelector
+   *        jQuery like selector string of the menu item
+   *
+   * @returns {Menu} The Menu instance
+   */
+  click: function Menu_click(itemSelector) {
+    this._controller.click(this.getItem(itemSelector));
+
+    return this;
+  },
+
+  /**
+   * Synthesize a keypress against the menu
+   *
+   * @param {string} key
+   *        Key to press
+   * @param {object} modifier
+   *        Key modifiers
+   * @see MozMillController#keypress
+   *
+   * @returns {Menu} The Menu instance
+   */
+  keypress: function Menu_keypress(key, modifier) {
+    this._controller.keypress(this._menu, key, modifier);
+
+    return this;
+  },
+
+  /**
+   * Opens the context menu, click the specified entry and
+   * make sure that the menu has been closed.
+   *
+   * @param {string} itemSelector
+   *        jQuery like selector string of the element
+   * @param {ElemBase} contextElement
+   *        Element whose context menu has to be opened
+   *
+   * @returns {Menu} The Menu instance
+   */
+  select: function Menu_select(itemSelector, contextElement) {
+    this.open(contextElement);
+    this.click(itemSelector);
+    this.close();
+  },
+
+  /**
+   * Recursive function which iterates through all menu elements and
+   * populates the menus with dynamic menu entries.
+   *
+   * @param {node} menu
+   *        Top menu node whose elements have to be populated
+   */
+  _buildMenu: function Menu__buildMenu(menu) {
+    var items = menu ? menu.childNodes : null;
+
+    Array.forEach(items, function (item) {
+      // When we have a menu node, fake a click onto it to populate
+      // the sub menu with dynamic entries
+      if (item.tagName == "menu") {
+        var popup = item.querySelector("menupopup");
+
+        if (popup) {
+          var popupEvent = this._controller.window.document.createEvent("MouseEvent");
+          popupEvent.initMouseEvent("popupshowing", true, true,
+                                    this._controller.window, 0, 0, 0, 0, 0,
+                                    false, false, false, false, 0, null);
+          popup.dispatchEvent(popupEvent);
+
+          this._buildMenu(popup);
+        }
+      }
+    }, this);
+  }
+};
+
+var MozMillController = function (window) {
+  this.window = window;
+
+  this.mozmillModule = {};
+  Cu.import('resource://mozmill/driver/mozmill.js', this.mozmillModule);
+
+  var self = this;
+  assert.waitFor(function () {
+    return window != null && self.isLoaded();
+  }, "controller(): Window has been initialized.");
+
+  // Ensure to focus the window which will move it virtually into the foreground
+  // when focusmanager.testmode is set enabled.
+  this.window.focus();
+
+  var windowType = window.document.documentElement.getAttribute('windowtype');
+  if (controllerAdditions[windowType] != undefined ) {
+    this.prototype = new utils.Copy(this.prototype);
+    controllerAdditions[windowType](this);
+    this.windowtype = windowType;
+  }
+}
+
+/**
+ * Returns the global browser object of the window
+ *
+ * @returns {Object} The browser object
+ */
+MozMillController.prototype.__defineGetter__("browserObject", function () {
+  return utils.getBrowserObject(this.window);
+});
+
+// constructs a MozMillElement from the controller's window
+MozMillController.prototype.__defineGetter__("rootElement", function () {
+  if (this._rootElement == undefined) {
+    let docElement = this.window.document.documentElement;
+    this._rootElement = new mozelement.MozMillElement("Elem", docElement);
+  }
+
+  return this._rootElement;
+});
+
+MozMillController.prototype.sleep = utils.sleep;
+MozMillController.prototype.waitFor = assert.waitFor;
+
+// Open the specified url in the current tab
+MozMillController.prototype.open = function (url) {
+  switch (this.mozmillModule.Application) {
+    case "Firefox":
+    case "MetroFirefox":
+      // Stop a running page load to not overlap requests
+      if (this.browserObject.selectedBrowser) {
+        this.browserObject.selectedBrowser.stop();
+      }
+
+      this.browserObject.loadURI(url);
+      break;
+
+    default:
+      throw new Error("MozMillController.open not supported.");
+  }
+
+  broker.pass({'function':'Controller.open()'});
+}
+
+/**
+ * Take a screenshot of specified node
+ *
+ * @param {Element} node
+ *        The window or DOM element to capture
+ * @param {String} name
+ *        The name of the screenshot used in reporting and as filename
+ * @param {Boolean} save
+ *        If true saves the screenshot as 'name.jpg' in tempdir,
+ *        otherwise returns a dataURL
+ * @param {Element[]} highlights
+ *        A list of DOM elements to highlight by drawing a red rectangle around them
+ *
+ * @returns {Object} Object which contains properties like filename, dataURL,
+ *          name and timestamp of the screenshot
+ */
+MozMillController.prototype.screenshot = function (node, name, save, highlights) {
+  if (!node) {
+    throw new Error("node is undefined");
+  }
+
+  // Unwrap the node and highlights
+  if ("getNode" in node) {
+    node = node.getNode();
+  }
+
+  if (highlights) {
+    for (var i = 0; i < highlights.length; ++i) {
+      if ("getNode" in highlights[i]) {
+        highlights[i] = highlights[i].getNode();
+      }
+    }
+  }
+
+  // If save is false, a dataURL is used
+  // Include both in the report anyway to avoid confusion and make the report easier to parse
+  var screenshot = {"filename": undefined,
+                    "dataURL": utils.takeScreenshot(node, highlights),
+                    "name": name,
+                    "timestamp": new Date().toLocaleString()};
+
+  if (!save) {
+    return screenshot;
+  }
+
+  // Save the screenshot to disk
+
+  let {filename, failure} = utils.saveDataURL(screenshot.dataURL, name);
+  screenshot.filename = filename;
+  screenshot.failure = failure;
+
+  if (failure) {
+    broker.log({'function': 'controller.screenshot()',
+                'message': 'Error writing to file: ' + screenshot.filename});
+  } else {
+    // Send the screenshot object to python over jsbridge
+    broker.sendMessage("screenshot", screenshot);
+    broker.pass({'function': 'controller.screenshot()'});
+  }
+
+  return screenshot;
+}
+
+/**
+ * Checks if the specified window has been loaded
+ *
+ * @param {DOMWindow} [aWindow=this.window] Window object to check for loaded state
+ */
+MozMillController.prototype.isLoaded = function (aWindow) {
+  var win = aWindow || this.window;
+
+  return windows.map.getValue(utils.getWindowId(win), "loaded") || false;
+};
+
+MozMillController.prototype.__defineGetter__("waitForEvents", function () {
+  if (this._waitForEvents == undefined) {
+    this._waitForEvents = new waitForEvents();
+  }
+
+  return this._waitForEvents;
+});
+
+/**
+ * Wrapper function to create a new instance of a menu
+ * @see Menu
+ */
+MozMillController.prototype.getMenu = function (menuSelector, document) {
+  return new Menu(this, menuSelector, document);
+};
+
+MozMillController.prototype.__defineGetter__("mainMenu", function () {
+  return this.getMenu("menubar");
+});
+
+MozMillController.prototype.__defineGetter__("menus", function () {
+  logDeprecated('controller.menus', 'Use controller.mainMenu instead');
+});
+
+MozMillController.prototype.waitForImage = function (aElement, timeout, interval) {
+  this.waitFor(function () {
+    return aElement.getNode().complete == true;
+  }, "timeout exceeded for waitForImage " + aElement.getInfo(), timeout, interval);
+
+  broker.pass({'function':'Controller.waitForImage()'});
+}
+
+MozMillController.prototype.startUserShutdown = function (timeout, restart, next, resetProfile) {
+  if (restart && resetProfile) {
+    throw new Error("You can't have a user-restart and reset the profile; there is a race condition");
+  }
+
+  let shutdownObj = {
+    'user': true,
+    'restart': Boolean(restart),
+    'next': next,
+    'resetProfile': Boolean(resetProfile),
+    'timeout': timeout
+  };
+
+  broker.sendMessage('shutdown', shutdownObj);
+}
+
+/**
+ * Restart the application
+ *
+ * @param {string} aNext
+ *        Name of the next test function to run after restart
+ * @param {boolean} [aFlags=undefined]
+ *        Additional flags how to handle the shutdown or restart. The attributes
+ *        eRestarti386 (0x20) and eRestartx86_64 (0x30) have not been documented yet.
+ * @see https://developer.mozilla.org/nsIAppStartup#Attributes
+ */
+MozMillController.prototype.restartApplication = function (aNext, aFlags) {
+  var flags = Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart;
+
+  if (aFlags) {
+    flags |= aFlags;
+  }
+
+  broker.sendMessage('shutdown', {'user': false,
+                                  'restart': true,
+                                  'flags': flags,
+                                  'next': aNext,
+                                  'timeout': 0 });
+
+  // We have to ensure to stop the test from continuing until the application is
+  // shutting down. The only way to do that is by throwing an exception.
+  throw new errors.ApplicationQuitError();
+}
+
+/**
+ * Stop the application
+ *
+ * @param {boolean} [aResetProfile=false]
+ *        Whether to reset the profile during restart
+ * @param {boolean} [aFlags=undefined]
+ *        Additional flags how to handle the shutdown or restart. The attributes
+ *        eRestarti386 and eRestartx86_64 have not been documented yet.
+ * @see https://developer.mozilla.org/nsIAppStartup#Attributes
+ */
+MozMillController.prototype.stopApplication = function (aResetProfile, aFlags) {
+  var flags = Ci.nsIAppStartup.eAttemptQuit;
+
+  if (aFlags) {
+    flags |= aFlags;
+  }
+
+  broker.sendMessage('shutdown', {'user': false,
+                                  'restart': false,
+                                  'flags': flags,
+                                  'resetProfile': aResetProfile,
+                                  'timeout': 0 });
+
+  // We have to ensure to stop the test from continuing until the application is
+  // shutting down. The only way to do that is by throwing an exception.
+  throw new errors.ApplicationQuitError();
+}
+
+//Browser navigation functions
+MozMillController.prototype.goBack = function () {
+  this.window.content.history.back();
+  broker.pass({'function':'Controller.goBack()'});
+
+  return true;
+}
+
+MozMillController.prototype.goForward = function () {
+  this.window.content.history.forward();
+  broker.pass({'function':'Controller.goForward()'});
+
+  return true;
+}
+
+MozMillController.prototype.refresh = function () {
+  this.window.content.location.reload(true);
+  broker.pass({'function':'Controller.refresh()'});
+
+  return true;
+}
+
+function logDeprecated(funcName, message) {
+  broker.log({'function': funcName + '() - DEPRECATED',
+              'message': funcName + '() is deprecated. ' + message});
+}
+
+function logDeprecatedAssert(funcName) {
+   logDeprecated('controller.' + funcName,
+                 '. Use the generic `assertion` module instead.');
+}
+
+MozMillController.prototype.assertText = function (el, text) {
+  logDeprecatedAssert("assertText");
+
+  var n = el.getNode();
+
+  if (n && n.innerHTML == text) {
+    broker.pass({'function': 'Controller.assertText()'});
+  } else {
+    throw new Error("could not validate element " + el.getInfo() +
+                    " with text "+ text);
+  }
+
+  return true;
+};
+
+/**
+ * Assert that a specified node exists
+ */
+MozMillController.prototype.assertNode = function (el) {
+  logDeprecatedAssert("assertNode");
+
+  //this.window.focus();
+  var element = el.getNode();
+  if (!element) {
+    throw new Error("could not find element " + el.getInfo());
+  }
+
+  broker.pass({'function': 'Controller.assertNode()'});
+  return true;
+};
+
+/**
+ * Assert that a specified node doesn't exist
+ */
+MozMillController.prototype.assertNodeNotExist = function (el) {
+  logDeprecatedAssert("assertNodeNotExist");
+
+  try {
+    var element = el.getNode();
+  } catch (e) {
+    broker.pass({'function': 'Controller.assertNodeNotExist()'});
+  }
+
+  if (element) {
+    throw new Error("Unexpectedly found element " + el.getInfo());
+  } else {
+    broker.pass({'function':'Controller.assertNodeNotExist()'});
+  }
+
+  return true;
+};
+
+/**
+ * Assert that a form element contains the expected value
+ */
+MozMillController.prototype.assertValue = function (el, value) {
+  logDeprecatedAssert("assertValue");
+
+  var n = el.getNode();
+
+  if (n && n.value == value) {
+    broker.pass({'function': 'Controller.assertValue()'});
+  } else {
+    throw new Error("could not validate element " + el.getInfo() +
+                    " with value " + value);
+  }
+
+  return false;
+};
+
+/**
+ * Check if the callback function evaluates to true
+ */
+MozMillController.prototype.assert = function (callback, message, thisObject) {
+  logDeprecatedAssert("assert");
+
+  utils.assert(callback, message, thisObject);
+  broker.pass({'function': ": controller.assert('" + callback + "')"});
+
+  return true;
+}
+
+/**
+ * Assert that a provided value is selected in a select element
+ */
+MozMillController.prototype.assertSelected = function (el, value) {
+  logDeprecatedAssert("assertSelected");
+
+  var n = el.getNode();
+  var validator = value;
+
+  if (n && n.options[n.selectedIndex].value == validator) {
+    broker.pass({'function':'Controller.assertSelected()'});
+  } else {
+    throw new Error("could not assert value for element " + el.getInfo() +
+                    " with value " + value);
+  }
+
+  return true;
+};
+
+/**
+ * Assert that a provided checkbox is checked
+ */
+MozMillController.prototype.assertChecked = function (el) {
+  logDeprecatedAssert("assertChecked");
+
+  var element = el.getNode();
+
+  if (element && element.checked == true) {
+    broker.pass({'function':'Controller.assertChecked()'});
+  } else {
+    throw new Error("assert failed for checked element " + el.getInfo());
+  }
+
+  return true;
+};
+
+/**
+ * Assert that a provided checkbox is not checked
+ */
+MozMillController.prototype.assertNotChecked = function (el) {
+  logDeprecatedAssert("assertNotChecked");
+
+  var element = el.getNode();
+
+  if (!element) {
+    throw new Error("Could not find element" + el.getInfo());
+  }
+
+  if (!element.hasAttribute("checked") || element.checked != true) {
+    broker.pass({'function': 'Controller.assertNotChecked()'});
+  } else {
+    throw new Error("assert failed for not checked element " + el.getInfo());
+  }
+
+  return true;
+};
+
+/**
+ * Assert that an element's javascript property exists or has a particular value
+ *
+ * if val is undefined, will return true if the property exists.
+ * if val is specified, will return true if the property exists and has the correct value
+ */
+MozMillController.prototype.assertJSProperty = function (el, attrib, val) {
+  logDeprecatedAssert("assertJSProperty");
+
+  var element = el.getNode();
+
+  if (!element){
+    throw new Error("could not find element " + el.getInfo());
+  }
+
+  var value = element[attrib];
+  var res = (value !== undefined && (val === undefined ? true :
+                                                         String(value) == String(val)));
+  if (res) {
+    broker.pass({'function':'Controller.assertJSProperty("' + el.getInfo() + '") : ' + val});
+  } else {
+    throw new Error("Controller.assertJSProperty(" + el.getInfo() + ") : " +
+                    (val === undefined ? "property '" + attrib +
+                    "' doesn't exist" : val + " == " + value));
+  }
+
+  return true;
+};
+
+/**
+ * Assert that an element's javascript property doesn't exist or doesn't have a particular value
+ *
+ * if val is undefined, will return true if the property doesn't exist.
+ * if val is specified, will return true if the property doesn't exist or doesn't have the specified value
+ */
+MozMillController.prototype.assertNotJSProperty = function (el, attrib, val) {
+  logDeprecatedAssert("assertNotJSProperty");
+
+  var element = el.getNode();
+
+  if (!element){
+    throw new Error("could not find element " + el.getInfo());
+  }
+
+  var value = element[attrib];
+  var res = (val === undefined ? value === undefined : String(value) != String(val));
+  if (res) {
+    broker.pass({'function':'Controller.assertNotProperty("' + el.getInfo() + '") : ' + val});
+  } else {
+    throw new Error("Controller.assertNotJSProperty(" + el.getInfo() + ") : " +
+                    (val === undefined ? "property '" + attrib +
+                    "' exists" : val + " != " + value));
+  }
+
+  return true;
+};
+
+/**
+ * Assert that an element's dom property exists or has a particular value
+ *
+ * if val is undefined, will return true if the property exists.
+ * if val is specified, will return true if the property exists and has the correct value
+ */
+MozMillController.prototype.assertDOMProperty = function (el, attrib, val) {
+  logDeprecatedAssert("assertDOMProperty");
+
+  var element = el.getNode();
+
+  if (!element){
+    throw new Error("could not find element " + el.getInfo());
+  }
+
+  var value, res = element.hasAttribute(attrib);
+  if (res && val !== undefined) {
+    value = element.getAttribute(attrib);
+    res = (String(value) == String(val));
+  }
+
+  if (res) {
+    broker.pass({'function':'Controller.assertDOMProperty("' + el.getInfo() + '") : ' + val});
+  } else {
+    throw new Error("Controller.assertDOMProperty(" + el.getInfo() + ") : " +
+                    (val === undefined ? "property '" + attrib +
+                    "' doesn't exist" : val + " == " + value));
+  }
+
+  return true;
+};
+
+/**
+ * Assert that an element's dom property doesn't exist or doesn't have a particular value
+ *
+ * if val is undefined, will return true if the property doesn't exist.
+ * if val is specified, will return true if the property doesn't exist or doesn't have the specified value
+ */
+MozMillController.prototype.assertNotDOMProperty = function (el, attrib, val) {
+  logDeprecatedAssert("assertNotDOMProperty");
+
+  var element = el.getNode();
+
+  if (!element) {
+    throw new Error("could not find element " + el.getInfo());
+  }
+
+  var value, res = element.hasAttribute(attrib);
+  if (res && val !== undefined) {
+    value = element.getAttribute(attrib);
+    res = (String(value) == String(val));
+  }
+
+  if (!res) {
+    broker.pass({'function':'Controller.assertNotDOMProperty("' + el.getInfo() + '") : ' + val});
+  } else {
+    throw new Error("Controller.assertNotDOMProperty(" + el.getInfo() + ") : " +
+                    (val == undefined ? "property '" + attrib +
+                    "' exists" : val + " == " + value));
+  }
+
+  return true;
+};
+
+/**
+ * Assert that a specified image has actually loaded. The Safari workaround results
+ * in additional requests for broken images (in Safari only) but works reliably
+ */
+MozMillController.prototype.assertImageLoaded = function (el) {
+  logDeprecatedAssert("assertImageLoaded");
+
+  var img = el.getNode();
+
+  if (!img || img.tagName != 'IMG') {
+    throw new Error('Controller.assertImageLoaded() failed.')
+    return false;
+  }
+
+  var comp = img.complete;
+  var ret = null; // Return value
+
+  // Workaround for Safari -- it only supports the
+  // complete attrib on script-created images
+  if (typeof comp == 'undefined') {
+    test = new Image();
+    // If the original image was successfully loaded,
+    // src for new one should be pulled from cache
+    test.src = img.src;
+    comp = test.complete;
+  }
+
+  // Check the complete attrib. Note the strict
+  // equality check -- we don't want undefined, null, etc.
+  // --------------------------
+  if (comp === false) {
+    // False -- Img failed to load in IE/Safari, or is
+    // still trying to load in FF
+    ret = false;
+  } else if (comp === true && img.naturalWidth == 0) {
+    // True, but image has no size -- image failed to
+    // load in FF
+    ret = false;
+  } else {
+    // Otherwise all we can do is assume everything's
+    // hunky-dory
+   ret = true;
+  }
+
+  if (ret) {
+    broker.pass({'function':'Controller.assertImageLoaded'});
+  } else {
+    throw new Error('Controller.assertImageLoaded() failed.')
+  }
+
+  return true;
+};
+
+/**
+ * Drag one element to the top x,y coords of another specified element
+ */
+MozMillController.prototype.mouseMove = function (doc, start, dest) {
+  // if one of these elements couldn't be looked up
+  if (typeof start != 'object'){
+    throw new Error("received bad coordinates");
+  }
+
+  if (typeof dest != 'object'){
+    throw new Error("received bad coordinates");
+  }
+
+  var triggerMouseEvent = function (element, clientX, clientY) {
+    clientX = clientX ? clientX: 0;
+    clientY = clientY ? clientY: 0;
+
+    // make the mouse understand where it is on the screen
+    var screenX = element.boxObject.screenX ? element.boxObject.screenX : 0;
+    var screenY = element.boxObject.screenY ? element.boxObject.screenY : 0;
+
+    var evt = element.ownerDocument.createEvent('MouseEvents');
+    if (evt.initMouseEvent) {
+      evt.initMouseEvent('mousemove', true, true, element.ownerDocument.defaultView,
+                         1, screenX, screenY, clientX, clientY);
+    } else {
+      evt.initEvent('mousemove', true, true);
+    }
+
+    element.dispatchEvent(evt);
+  };
+
+  // Do the initial move to the drag element position
+  triggerMouseEvent(doc.body, start[0], start[1]);
+  triggerMouseEvent(doc.body, dest[0], dest[1]);
+
+  broker.pass({'function':'Controller.mouseMove()'});
+  return true;
+}
+
+/**
+ * Drag an element to the specified offset on another element, firing mouse and
+ * drag events. Adapted from ChromeUtils.js synthesizeDrop()
+ *
+ * @deprecated Use the MozMillElement object
+ *
+ * @param {MozElement} aSrc
+ *        Source element to be dragged
+ * @param {MozElement} aDest
+ *        Destination element over which the drop occurs
+ * @param {Number} [aOffsetX=element.width/2]
+ *        Relative x offset for dropping on the aDest element
+ * @param {Number} [aOffsetY=element.height/2]
+ *        Relative y offset for dropping on the aDest element
+ * @param {DOMWindow} [aSourceWindow=this.element.ownerDocument.defaultView]
+ *        Custom source Window to be used.
+ * @param {String} [aDropEffect="move"]
+ *        Effect used for the drop event
+ * @param {Object[]} [aDragData]
+ *        An array holding custom drag data to be used during the drag event
+ *        Format: [{ type: "text/plain", "Text to drag"}, ...]
+ *
+ * @returns {String} the captured dropEffect
+ */
+MozMillController.prototype.dragToElement = function (aSrc, aDest, aOffsetX,
+                                                      aOffsetY, aSourceWindow,
+                                                      aDropEffect, aDragData) {
+  logDeprecated("controller.dragToElement", "Use the MozMillElement object.");
+  return aSrc.dragToElement(aDest, aOffsetX, aOffsetY, aSourceWindow, null,
+                            aDropEffect, aDragData);
+};
+
+function Tabs(controller) {
+  this.controller = controller;
+}
+
+Tabs.prototype.getTab = function (index) {
+  return this.controller.browserObject.browsers[index].contentDocument;
+}
+
+Tabs.prototype.__defineGetter__("activeTab", function () {
+  return this.controller.browserObject.selectedBrowser.contentDocument;
+});
+
+Tabs.prototype.selectTab = function (index) {
+  // GO in to tab manager and grab the tab by index and call focus.
+}
+
+Tabs.prototype.findWindow = function (doc) {
+  for (var i = 0; i <= (this.controller.window.frames.length - 1); i++) {
+    if (this.controller.window.frames[i].document == doc) {
+      return this.controller.window.frames[i];
+    }
+  }
+
+  throw new Error("Cannot find window for document. Doc title == " + doc.title);
+}
+
+Tabs.prototype.getTabWindow = function (index) {
+  return this.findWindow(this.getTab(index));
+}
+
+Tabs.prototype.__defineGetter__("activeTabWindow", function () {
+  return this.findWindow(this.activeTab);
+});
+
+Tabs.prototype.__defineGetter__("length", function () {
+  return this.controller.browserObject.browsers.length;
+});
+
+Tabs.prototype.__defineGetter__("activeTabIndex", function () {
+  var browser = this.controller.browserObject;
+
+  switch(this.controller.mozmillModule.Application) {
+    case "MetroFirefox":
+      return browser.tabs.indexOf(browser.selectedTab);
+    case "Firefox":
+    default:
+      return browser.tabContainer.selectedIndex;
+  }
+});
+
+Tabs.prototype.selectTabIndex = function (aIndex) {
+  var browser = this.controller.browserObject;
+
+  switch(this.controller.mozmillModule.Application) {
+    case "MetroFirefox":
+      browser.selectedTab = browser.tabs[aIndex];
+      break;
+    case "Firefox":
+    default:
+      browser.selectTabAtIndex(aIndex);
+  }
+}
+
+function browserAdditions (controller) {
+  controller.tabs = new Tabs(controller);
+
+  controller.waitForPageLoad = function (aDocument, aTimeout, aInterval) {
+    var timeout = aTimeout || 30000;
+    var win = null;
+    var timed_out = false;
+
+    // If a user tries to do waitForPageLoad(2000), this will assign the
+    // interval the first arg which is most likely what they were expecting
+    if (typeof(aDocument) == "number"){
+      timeout = aDocument;
+    }
+
+    // If we have a real document use its default view
+    if (aDocument && (typeof(aDocument) === "object") &&
+        "defaultView" in aDocument)
+      win = aDocument.defaultView;
+
+    // If no document has been specified, fallback to the default view of the
+    // currently selected tab browser
+    win = win || this.browserObject.selectedBrowser.contentWindow;
+
+    // Wait until the content in the tab has been loaded
+    try {
+      this.waitFor(function () {
+        return windows.map.hasPageLoaded(utils.getWindowId(win));
+      }, "Timeout", timeout, aInterval);
+    }
+    catch (ex if ex instanceof errors.TimeoutError) {
+      timed_out = true;
+    }
+    finally {
+      state = 'URI=' + win.document.location.href +
+              ', readyState=' + win.document.readyState;
+      message = "controller.waitForPageLoad(" + state + ")";
+
+      if (timed_out) {
+        throw new errors.AssertionError(message);
+      }
+
+      broker.pass({'function': message});
+    }
+  }
+}
+
+var controllerAdditions = {
+  'navigator:browser'  :browserAdditions
+};
+
+/**
+ *  DEPRECATION WARNING
+ *
+ * The following methods have all been DEPRECATED as of Mozmill 2.0
+ */
+MozMillController.prototype.assertProperty = function (el, attrib, val) {
+  logDeprecatedAssert("assertProperty");
+
+  return this.assertJSProperty(el, attrib, val);
+};
+
+MozMillController.prototype.assertPropertyNotExist = function (el, attrib) {
+  logDeprecatedAssert("assertPropertyNotExist");
+  return this.assertNotJSProperty(el, attrib);
+};
+
+/**
+ *  DEPRECATION WARNING
+ *
+ * The following methods have all been DEPRECATED as of Mozmill 2.0
+ * Use the MozMillElement object instead (https://developer.mozilla.org/en/Mozmill/Mozmill_Element_Object)
+ */
+MozMillController.prototype.select = function (aElement, index, option, value) {
+  logDeprecated("controller.select", "Use the MozMillElement object.");
+
+  return aElement.select(index, option, value);
+};
+
+MozMillController.prototype.keypress = function (aElement, aKey, aModifiers, aExpectedEvent) {
+  logDeprecated("controller.keypress", "Use the MozMillElement object.");
+
+  if (!aElement) {
+    aElement = new mozelement.MozMillElement("Elem", this.window);
+  }
+
+  return aElement.keypress(aKey, aModifiers, aExpectedEvent);
+}
+
+MozMillController.prototype.type = function (aElement, aText, aExpectedEvent) {
+  logDeprecated("controller.type", "Use the MozMillElement object.");
+
+  if (!aElement) {
+    aElement = new mozelement.MozMillElement("Elem", this.window);
+  }
+
+  var that = this;
+  var retval = true;
+  Array.forEach(aText, function (letter) {
+    if (!that.keypress(aElement, letter, {}, aExpectedEvent)) {
+      retval = false; }
+  });
+
+  return retval;
+}
+
+MozMillController.prototype.mouseEvent = function (aElement, aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
+  logDeprecated("controller.mouseEvent", "Use the MozMillElement object.");
+
+  return aElement.mouseEvent(aOffsetX, aOffsetY, aEvent, aExpectedEvent);
+}
+
+MozMillController.prototype.click = function (aElement, left, top, expectedEvent) {
+  logDeprecated("controller.click", "Use the MozMillElement object.");
+
+  return aElement.click(left, top, expectedEvent);
+}
+
+MozMillController.prototype.doubleClick = function (aElement, left, top, expectedEvent) {
+  logDeprecated("controller.doubleClick", "Use the MozMillElement object.");
+
+  return aElement.doubleClick(left, top, expectedEvent);
+}
+
+MozMillController.prototype.mouseDown = function (aElement, button, left, top, expectedEvent) {
+  logDeprecated("controller.mouseDown", "Use the MozMillElement object.");
+
+  return aElement.mouseDown(button, left, top, expectedEvent);
+};
+
+MozMillController.prototype.mouseOut = function (aElement, button, left, top, expectedEvent) {
+  logDeprecated("controller.mouseOut", "Use the MozMillElement object.");
+
+  return aElement.mouseOut(button, left, top, expectedEvent);
+};
+
+MozMillController.prototype.mouseOver = function (aElement, button, left, top, expectedEvent) {
+  logDeprecated("controller.mouseOver", "Use the MozMillElement object.");
+
+  return aElement.mouseOver(button, left, top, expectedEvent);
+};
+
+MozMillController.prototype.mouseUp = function (aElement, button, left, top, expectedEvent) {
+  logDeprecated("controller.mouseUp", "Use the MozMillElement object.");
+
+  return aElement.mouseUp(button, left, top, expectedEvent);
+};
+
+MozMillController.prototype.middleClick = function (aElement, left, top, expectedEvent) {
+  logDeprecated("controller.middleClick", "Use the MozMillElement object.");
+
+  return aElement.middleClick(aElement, left, top, expectedEvent);
+}
+
+MozMillController.prototype.rightClick = function (aElement, left, top, expectedEvent) {
+  logDeprecated("controller.rightClick", "Use the MozMillElement object.");
+
+  return aElement.rightClick(left, top, expectedEvent);
+}
+
+MozMillController.prototype.check = function (aElement, state) {
+  logDeprecated("controller.check", "Use the MozMillElement object.");
+
+  return aElement.check(state);
+}
+
+MozMillController.prototype.radio = function (aElement) {
+  logDeprecated("controller.radio", "Use the MozMillElement object.");
+
+  return aElement.select();
+}
+
+MozMillController.prototype.waitThenClick = function (aElement, timeout, interval) {
+  logDeprecated("controller.waitThenClick", "Use the MozMillElement object.");
+
+  return aElement.waitThenClick(timeout, interval);
+}
+
+MozMillController.prototype.waitForElement = function (aElement, timeout, interval) {
+  logDeprecated("controller.waitForElement", "Use the MozMillElement object.");
+
+  return aElement.waitForElement(timeout, interval);
+}
+
+MozMillController.prototype.waitForElementNotPresent = function (aElement, timeout, interval) {
+  logDeprecated("controller.waitForElementNotPresent", "Use the MozMillElement object.");
+
+  return aElement.waitForElementNotPresent(timeout, interval);
+}
new file mode 100644
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/driver/elementslib.js
@@ -0,0 +1,537 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
+                        "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
+                       ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
+var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings);
+var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays);
+var json2 = {}; Cu.import('resource://mozmill/stdlib/json2.js', json2);
+var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs);
+var dom = {}; Cu.import('resource://mozmill/stdlib/dom.js', dom);
+var objects = {}; Cu.import('resource://mozmill/stdlib/objects.js', objects);
+
+var countQuotes = function (str) {
+  var count = 0;
+  var i = 0;
+
+  while (i < str.length) {
+    i = str.indexOf('"', i);
+    if (i != -1) {
+      count++;
+      i++;
+    } else {
+      break;
+    }
+  }
+
+  return count;
+};
+
+/**
+ * smartSplit()
+ *
+ * Takes a lookup string as input and returns
+ * a list of each node in the string
+ */
+var smartSplit = function (str) {
+  // Ensure we have an even number of quotes
+  if (countQuotes(str) % 2 != 0) {
+    throw new Error ("Invalid Lookup Expression");
+  }
+
+  /**
+   * This regex matches a single "node" in a lookup string.
+   * In otherwords, it matches the part between the two '/'s
+   *
+   * Regex Explanation:
+   * \/ - start matching at the first forward slash
+   * ([^\/"]*"[^"]*")* - match as many pairs of quotes as possible until we hit a slash (ignore slashes inside quotes)
+   * [^\/]* - match the remainder of text outside of last quote but before next slash
+   */
+  var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
+  var ret = []
+  var match = re.exec(str);
+
+  while (match != null) {
+    ret.push(match[0].replace(/^\//, ""));
+    match = re.exec(str);
+  }
+
+  return ret;
+};
+
+/**
+ * defaultDocuments()
+ *
+ * Returns a list of default documents in which to search for elements
+ * if no document is provided
+ */
+function defaultDocuments() {
+  var win = Services.wm.getMostRecentWindow("navigator:browser");
+
+  return [
+    win.document,
+    utils.getBrowserObject(win).selectedBrowser.contentWindow.document
+  ];
+};
+
+/**
+ * nodeSearch()
+ *
+ * Takes an optional document, callback and locator string
+ * Returns a handle to the located element or null
+ */
+function nodeSearch(doc, func, string) {
+  if (doc != undefined) {
+    var documents = [doc];
+  } else {
+    var documents = defaultDocuments();
+  }
+
+  var e = null;
+  var element = null;
+
+  //inline function to recursively find the element in the DOM, cross frame.
+  var search = function (win, func, string) {
+    if (win == null) {
+      return;
+    }
+
+    //do the lookup in the current window
+    element = func.call(win, string);
+
+    if (!element || (element.length == 0)) {
+      var frames = win.frames;
+      for (var i = 0; i < frames.length; i++) {
+        search(frames[i], func, string);
+      }
+    } else {
+      e = element;
+    }
+  };
+
+  for (var i = 0; i < documents.length; ++i) {
+    var win = documents[i].defaultView;
+    search(win, func, string);
+    if (e) {
+      break;
+    }
+  }
+
+  return e;
+};
+
+/**
+ * Selector()
+ *
+ * Finds an element by selector string
+ */
+function Selector(_document, selector, index) {
+  if (selector == undefined) {
+    throw new Error('Selector constructor did not recieve enough arguments.');
+  }
+
+  this.selector = selector;
+
+  this.getNodeForDocument = function (s) {
+    return this.document.querySelectorAll(s);
+  };
+
+  var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
+
+  return nodes ? nodes[index || 0] : null;
+};
+
+/**
+ * ID()
+ *
+ * Finds an element by ID
+ */
+function ID(_document, nodeID) {
+  if (nodeID == undefined) {
+    throw new Error('ID constructor did not recieve enough arguments.');
+  }
+
+  this.getNodeForDocument = function (nodeID) {
+    return this.document.getElementById(nodeID);
+  };
+
+  return nodeSearch(_document, this.getNodeForDocument, nodeID);
+};
+
+/**
+ * Link()
+ *
+ * Finds a link by innerHTML
+ */
+function Link(_document, linkName) {
+  if (linkName == undefined) {
+    throw new Error('Link constructor did not recieve enough arguments.');
+  }
+
+  this.getNodeForDocument = function (linkName) {
+    var getText = function (el) {
+      var text = "";
+
+      if (el.nodeType == 3) { //textNode
+        if (el.data != undefined) {
+          text = el.data;
+        } else {
+          text = el.innerHTML;
+        }
+
+        text = text.replace(/n|r|t/g, " ");
+      }
+      else if (el.nodeType == 1) { //elementNode
+        for (var i = 0; i < el.childNodes.length; i++) {
+          var child = el.childNodes.item(i);
+          text += getText(child);
+        }
+
+        if (el.tagName == "P" || el.tagName == "BR" ||
+            el.tagName == "HR" || el.tagName == "DIV") {
+          text += "\n";
+        }
+      }
+
+      return text;
+    };
+
+    //sometimes the windows won't have this function
+    try {
+      var links = this.document.getElementsByTagName('a');
+    } catch (e) {
+      // ADD LOG LINE mresults.write('Error: '+ e, 'lightred');
+    }
+
+    for (var i = 0; i < links.length; i++) {
+      var el = links[i];
+      //if (getText(el).indexOf(this.linkName) != -1) {
+      if (el.innerHTML.indexOf(linkName) != -1) {
+        return el;
+      }
+    }
+
+    return null;
+  };
+
+  return nodeSearch(_document, this.getNodeForDocument, linkName);
+};
+
+/**
+ * XPath()
+ *
+ * Finds an element by XPath
+ */
+function XPath(_document, expr) {
+  if (expr == undefined) {
+    throw new Error('XPath constructor did not recieve enough arguments.');
+  }
+
+  this.getNodeForDocument = function (s) {
+    var aNode = this.document;
+    var aExpr = s;
+    var xpe = null;
+
+    if (this.document.defaultView == null) {
+      xpe = new getMethodInWindows('XPathEvaluator')();
+    } else {
+      xpe = new this.document.defaultView.XPathEvaluator();
+    }
+
+    var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement
+                                                                      : aNode.ownerDocument.documentElement);
+    var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null);
+    var found = [];
+    var res;
+
+    while (res = result.iterateNext()) {
+      found.push(res);
+    }
+
+    return found[0];
+  };
+
+  return nodeSearch(_document, this.getNodeForDocument, expr);
+};
+
+/**
+ * Name()
+ *
+ * Finds an element by Name
+ */
+function Name(_document, nName) {
+  if (nName == undefined) {
+    throw new Error('Name constructor did not recieve enough arguments.');
+  }
+
+  this.getNodeForDocument = function (s) {
+    try{
+      var els = this.document.getElementsByName(s);
+      if (els.length > 0) {
+        return els[0];
+      }
+    } catch (e) {
+    }
+
+    return null;
+  };
+
+  return nodeSearch(_document, this.getNodeForDocument, nName);
+};
+
+
+var _returnResult = function (results) {
+  if (results.length == 0) {
+    return null
+  }
+  else if (results.length == 1) {
+    return results[0];
+  } else {
+    return results;
+  }
+}
+
+var _forChildren = function (element, name, value) {
+  var results = [];
+  var nodes = [e for each (e in element.childNodes) if (e)]
+
+  for (var i in nodes) {
+    var n = nodes[i];
+    if (n[name] == value) {
+      results.push(n);
+    }
+  }
+
+  return results;
+}
+
+var _forAnonChildren = function (_document, element, name, value) {
+  var results = [];
+  var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)];
+
+  for (var i in nodes ) {
+    var n = nodes[i];
+    if (n[name] == value) {
+      results.push(n);
+    }
+  }
+
+  return results;
+}
+
+var _byID = function (_document, parent, value) {
+  return _returnResult(_forChildren(parent, 'id', value));
+}
+
+var _byName = function (_document, parent, value) {
+  return _returnResult(_forChildren(parent, 'tagName', value));
+}
+
+var _byAttrib = function (parent, attributes) {
+  var results = [];
+  var nodes = parent.childNodes;
+
+  for (var i in nodes) {
+    var n = nodes[i];
+    requirementPass = 0;
+    requirementLength = 0;
+
+    for (var a in attributes) {
+      requirementLength++;
+      try {
+        if (n.getAttribute(a) == attributes[a]) {
+          requirementPass++;
+        }
+      } catch (e) {
+        // Workaround any bugs in custom attribute crap in XUL elements
+      }
+    }
+
+    if (requirementPass == requirementLength) {
+      results.push(n);
+    }
+  }
+
+  return _returnResult(results)
+}
+
+var _byAnonAttrib = function (_document, parent, attributes) {
+  var results = [];
+
+  if (objects.getLength(attributes) == 1) {
+    for (var i in attributes) {
+      var k = i;
+      var v = attributes[i];
+    }
+
+    var result = _document.getAnonymousElementByAttribute(parent, k, v);
+    if (result) {
+      return result;
+    }
+  }
+
+  var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)];
+
+  function resultsForNodes (nodes) {
+    for (var i in nodes) {
+      var n = nodes[i];
+      requirementPass = 0;
+      requirementLength = 0;
+
+      for (var a in attributes) {
+        requirementLength++;
+        if (n.getAttribute(a) == attributes[a]) {
+          requirementPass++;
+        }
+      }
+
+      if (requirementPass == requirementLength) {
+        results.push(n);
+      }
+    }
+  }
+
+  resultsForNodes(nodes);
+  if (results.length == 0) {
+    resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)])
+  }
+
+  return _returnResult(results)
+}
+
+var _byIndex = function (_document, parent, i) {
+  if (parent instanceof Array) {
+    return parent[i];
+  }
+
+  return parent.childNodes[i];
+}
+
+var _anonByName = function (_document, parent, value) {
+  return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
+}
+
+var _anonByAttrib = function (_document, parent, value) {
+  return _byAnonAttrib(_document, parent, value);
+}
+
+var _anonByIndex = function (_document, parent, i) {
+  return _document.getAnonymousNodes(parent)[i];
+}
+
+/**
+ * Lookup()
+ *
+ * Finds an element by Lookup expression
+ */
+function Lookup(_document, expression) {
+  if (expression == undefined) {
+    throw new Error('Lookup constructor did not recieve enough arguments.');
+  }
+
+  var expSplit = [e for each (e in smartSplit(expression) ) if (e != '')];
+  expSplit.unshift(_document);
+
+  var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
+  var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
+
+  /**
+   * Reduces the lookup expression
+   * @param {Object} parentNode
+   *        Parent node (previousValue of the formerly executed reduce callback)
+   * @param {String} exp
+   *        Lookup expression for the parents child node
+   *
+   * @returns {Object} Node found by the given expression
+   */
+  var reduceLookup = function (parentNode, exp) {
+    // Abort in case the parent node was not found
+    if (!parentNode) {
+      return false;
+    }
+
+    // Handle case where only index is provided
+    var cases = nCases;
+
+    // Handle ending index before any of the expression gets mangled
+    if (withs.endsWith(exp, ']')) {
+      var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
+    }
+
+    // Handle anon
+    if (withs.startsWith(exp, 'anon')) {
+      exp = strings.vslice(exp, '(', ')');
+      cases = aCases;
+    }
+
+    if (withs.startsWith(exp, '[')) {
+      try {
+        var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
+      } catch (e) {
+        throw new SyntaxError(e + '. String to be parsed was || ' +
+                              strings.vslice(exp, '[', ']') + ' ||');
+      }
+
+      var r = cases['index'](_document, parentNode, obj);
+      if (r == null) {
+        throw new SyntaxError('Expression "' + exp +
+                              '" returned null. Anonymous == ' + (cases == aCases));
+      }
+
+      return r;
+    }
+
+    for (var c in cases) {
+      if (withs.startsWith(exp, c)) {
+        try {
+          var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
+        } catch (e) {
+           throw new SyntaxError(e + '. String to be parsed was || ' +
+                                 strings.vslice(exp, '(', ')') + '  ||');
+        }
+        var result = cases[c](_document, parentNode, obj);
+      }
+    }
+
+    if (!result) {
+      if (withs.startsWith(exp, '{')) {
+        try {
+          var obj = json2.JSON.parse(exp);
+        } catch (e) {
+          throw new SyntaxError(e + '. String to be parsed was || ' + exp + ' ||');
+        }
+
+        if (cases == aCases) {
+          var result = _anonByAttrib(_document, parentNode, obj);
+        } else {
+          var result = _byAttrib(parentNode, obj);
+        }
+      }
+    }
+
+    // Final return
+    if (expIndex) {
+      // TODO: Check length and raise error
+      return result[expIndex];
+    } else {
+      // TODO: Check length and raise error
+      return result;
+    }
+
+    // Maybe we should cause an exception here
+    return false;
+  };
+
+  return expSplit.reduce(reduceLookup);
+};
new file mode 100644
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/driver/mozelement.js
@@ -0,0 +1,1163 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup",
+                        "MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList",
+                        "MozMillTextBox", "subclasses"
+                       ];
+
+const NAMESPACE_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+var EventUtils = {};  Cu.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
+
+var assertions = {};  Cu.import('resource://mozmill/modules/assertions.js', assertions);
+var broker = {};      Cu.import('resource://mozmill/driver/msgbroker.js', broker);
+var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
+var utils = {};       Cu.import('resource://mozmill/stdlib/utils.js', utils);
+
+var assert = new assertions.Assert();
+
+// A list of all the subclasses available.  Shared modules can push their own subclasses onto this list
+var subclasses = [MozMillCheckBox, MozMillRadio, MozMillDropList, MozMillTextBox];
+
+/**
+ * createInstance()
+ *
+ * Returns an new instance of a MozMillElement
+ * The type of the element is automatically determined
+ */
+function createInstance(locatorType, locator, elem, document) {
+  var args = { "document": document, "element": elem };
+
+  // If we already have an element lets determine the best MozMillElement type
+  if (elem) {
+    for (var i = 0; i < subclasses.length; ++i) {
+      if (subclasses[i].isType(elem)) {
+        return new subclasses[i](locatorType, locator, args);
+      }
+    }
+  }
+
+  // By default we create a base MozMillElement
+  if (MozMillElement.isType(elem)) {
+    return new MozMillElement(locatorType, locator, args);
+  }
+
+  throw new Error("Unsupported element type " + locatorType + ": " + locator);
+}
+
+var Elem = function (node) {
+  return createInstance("Elem", node, node);
+};
+
+var Selector = function (document, selector, index) {
+  return createInstance("Selector", selector, elementslib.Selector(document, selector, index), document);
+};
+
+var ID = function (document, nodeID) {
+  return createInstance("ID", nodeID, elementslib.ID(document, nodeID), document);
+};
+
+var Link = function (document, linkName) {
+  return createInstance("Link", linkName, elementslib.Link(document, linkName), document);
+};
+
+var XPath = function (document, expr) {
+  return createInstance("XPath", expr, elementslib.XPath(document, expr), document);
+};
+
+var Name = function (document, nName) {
+  return createInstance("Name", nName, elementslib.Name(document, nName), document);
+};
+
+var Lookup = function (document, expression) {
+  var elem = createInstance("Lookup", expression, elementslib.Lookup(document, expression), document);
+
+  // Bug 864268 - Expose the expression property to maintain backwards compatibility
+  elem.expression = elem._locator;
+
+  return elem;
+};
+
+/**
+ * MozMillElement
+ * The base class for all mozmill elements
+ */
+function MozMillElement(locatorType, locator, args) {
+  args = args || {};
+  this._locatorType = locatorType;
+  this._locator = locator;
+  this._element = args["element"];
+  this._owner = args["owner"];
+
+  this._document = this._element ? this._element.ownerDocument : args["document"];
+  this._defaultView = this._document ? this._document.defaultView : null;
+
+  // Used to maintain backwards compatibility with controller.js
+  this.isElement = true;
+}
+
+// Static method that returns true if node is of this element type
+MozMillElement.isType = function (node) {
+  return true;
+};
+
+// This getter is the magic behind lazy loading (note distinction between _element and element)
+MozMillElement.prototype.__defineGetter__("element", function () {
+  // If the document is invalid (e.g. reload of the page), invalidate the cached
+  // element and update the document cache
+  if (this._defaultView && this._defaultView.document !== this._document) {
+    this._document = this._defaultView.document;
+    this._element = undefined;
+  }
+
+  if (this._element == undefined) {
+    if (elementslib[this._locatorType]) {
+      this._element = elementslib[this._locatorType](this._document, this._locator);
+    } else if (this._locatorType == "Elem") {
+      this._element = this._locator;
+    } else {
+      throw new Error("Unknown locator type: " + this._locatorType);
+    }
+  }
+
+  return this._element;
+});
+
+/**
+ * Drag an element to the specified offset on another element, firing mouse and
+ * drag events. Adapted from ChromeUtils.js synthesizeDrop()
+ *
+ * By default it will drag the source element over the destination's element
+ * center with a "move" dropEffect.
+ *
+ * @param {MozElement} aElement
+ *        Destination element over which the drop occurs
+ * @param {Number} [aOffsetX=aElement.width/2]
+ *        Relative x offset for dropping on aElement
+ * @param {Number} [aOffsetY=aElement.height/2]
+ *        Relative y offset for dropping on aElement
+ * @param {DOMWindow} [aSourceWindow=this.element.ownerDocument.defaultView]
+ *        Custom source Window to be used.
+ * @param {DOMWindow} [aDestWindow=aElement.getNode().ownerDocument.defaultView]
+ *        Custom destination Window to be used.
+ * @param {String} [aDropEffect="move"]
+ *        Possible values: copy, move, link, none
+ * @param {Object[]} [aDragData]
+ *        An array holding custom drag data to be used during the drag event
+ *        Format: [{ type: "text/plain", "Text to drag"}, ...]
+ *
+ * @returns {String} the captured dropEffect
+ */
+MozMillElement.prototype.dragToElement = function(aElement, aOffsetX, aOffsetY,
+                                                  aSourceWindow, aDestWindow,
+                                                  aDropEffect, aDragData) {
+  if (!this.element) {
+    throw new Error("Could not find element " + this.getInfo());
+  }
+  if (!aElement) {
+    throw new Error("Missing destination element");
+  }
+
+  var srcNode = this.element;
+  var destNode = aElement.getNode();
+  var srcWindow = aSourceWindow ||
+                  (srcNode.ownerDocument ? srcNode.ownerDocument.defaultView
+                                         : srcNode);
+  var destWindow = aDestWindow ||
+                  (destNode.ownerDocument ? destNode.ownerDocument.defaultView
+                                          : destNode);
+
+  var srcRect = srcNode.getBoundingClientRect();
+  var srcCoords = {
+    x: srcRect.width / 2,
+    y: srcRect.height / 2
+  };
+  var destRect = destNode.getBoundingClientRect();
+  var destCoords = {
+    x: (!aOffsetX || isNaN(aOffsetX)) ? (destRect.width / 2) : aOffsetX,
+    y: (!aOffsetY || isNaN(aOffsetY)) ? (destRect.height / 2) : aOffsetY
+  };
+
+  var windowUtils = destWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIDOMWindowUtils);
+  var ds = Cc["@mozilla.org/widget/dragservice;1"].getService(Ci.nsIDragService);
+
+  var dataTransfer;
+  var trapDrag = function (event) {
+    srcWindow.removeEventListener("dragstart", trapDrag, true);
+    dataTransfer = event.dataTransfer;
+
+    if (!aDragData) {
+      return;
+    }
+
+    for (var i = 0; i < aDragData.length; i++) {
+      var item = aDragData[i];
+      for (var j = 0; j < item.length; j++) {
+        dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
+      }
+    }
+
+    dataTransfer.dropEffect = aDropEffect || "move";
+    event.preventDefault();
+    event.stopPropagation();
+  }
+
+  ds.startDragSession();
+
+  try {
+    srcWindow.addEventListener("dragstart", trapDrag, true);
+    EventUtils.synthesizeMouse(srcNode, srcCoords.x, srcCoords.y,
+                               { type: "mousedown" }, srcWindow);
+    EventUtils.synthesizeMouse(destNode, destCoords.x, destCoords.y,
+                               { type: "mousemove" }, destWindow);
+
+    var event = destWindow.document.createEvent("DragEvents");
+    event.initDragEvent("dragenter", true, true, destWindow, 0, 0, 0, 0, 0,
+                        false, false, false, false, 0, null, dataTransfer);
+    event.initDragEvent("dragover", true, true, destWindow, 0, 0, 0, 0, 0,
+                        false, false, false, false, 0, null, dataTransfer);
+    event.initDragEvent("drop", true, true, destWindow, 0, 0, 0, 0, 0,
+                        false, false, false, false, 0, null, dataTransfer);
+    windowUtils.dispatchDOMEventViaPresShell(destNode, event, true);
+
+    EventUtils.synthesizeMouse(destNode, destCoords.x, destCoords.y,
+                               { type: "mouseup" }, destWindow);
+
+    return dataTransfer.dropEffect;
+  } finally {
+    ds.endDragSession(true);
+  }
+
+};
+
+// Returns the actual wrapped DOM node
+MozMillElement.prototype.getNode = function () {
+  return this.element;
+};
+
+MozMillElement.prototype.getInfo = function () {
+  return this._locatorType + ": " + this._locator;
+};
+
+/**
+ * Sometimes an element which once existed will no longer exist in the DOM
+ * This function re-searches for the element
+ */
+MozMillElement.prototype.exists = function () {
+  this._element = undefined;
+  if (this.element) {
+    return true;
+  }
+
+  return false;
+};
+
+/**
+ * Synthesize a keypress event on the given element
+ *
+ * @param {string} aKey
+ *        Key to use for synthesizing the keypress event. It can be a simple
+ *        character like "k" or a string like "VK_ESCAPE" for command keys
+ * @param {object} aModifiers
+ *        Information about the modifier keys to send
+ *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
+ *                               [optional - default: false]
+ *                  altKey     - Hold down the alt key
+ *                              [optional - default: false]
+ *                  ctrlKey    - Hold down the ctrl key
+ *                               [optional - default: false]
+ *                  metaKey    - Hold down the meta key (command key on Mac)
+ *                               [optional - default: false]
+ *                  shiftKey   - Hold down the shift key
+ *                               [optional - default: false]
+ * @param {object} aExpectedEvent
+ *        Information about the expected event to occur
+ *        Elements: target     - Element which should receive the event
+ *                               [optional - default: current element]
+ *                  type       - Type of the expected key event
+ */
+MozMillElement.prototype.keypress = function (aKey, aModifiers, aExpectedEvent) {
+  if (!this.element) {
+    throw new Error("Could not find element " + this.getInfo());
+  }
+
+  var win = this.element.ownerDocument ? this.element.ownerDocument.defaultView
+                                       : this.element;
+  this.element.focus();
+
+  if (aExpectedEvent) {
+    if (!aExpectedEvent.type) {
+      throw new Error(arguments.callee.name + ": Expected event type not specified");
+    }
+
+    var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
+                                       : this.element;
+    EventUtils.synthesizeKeyExpectEvent(aKey, aModifiers || {}, target, aExpectedEvent.type,
+                                        "MozMillElement.keypress()", win);
+  } else {
+    EventUtils.synthesizeKey(aKey, aModifiers || {}, win);
+  }
+
+  broker.pass({'function':'MozMillElement.keypress()'});
+
+  return true;
+};
+
+
+/**
+ * Synthesize a general mouse event on the given element
+ *
+ * @param {number} aOffsetX
+ *        Relative x offset in the elements bounds to click on
+ * @param {number} aOffsetY
+ *        Relative y offset in the elements bounds to click on
+ * @param {object} aEvent
+ *        Information about the event to send
+ *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
+ *                               [optional - default: false]
+ *                  altKey     - Hold down the alt key
+ *                               [optional - default: false]
+ *                  button     - Mouse button to use
+ *                               [optional - default: 0]
+ *                  clickCount - Number of counts to click
+ *                               [optional - default: 1]
+ *                  ctrlKey    - Hold down the ctrl key
+ *                               [optional - default: false]
+ *                  metaKey    - Hold down the meta key (command key on Mac)
+ *                               [optional - default: false]
+ *                  shiftKey   - Hold down the shift key
+ *                               [optional - default: false]
+ *                  type       - Type of the mouse event ('click', 'mousedown',
+ *                               'mouseup', 'mouseover', 'mouseout')
+ *                               [optional - default: 'mousedown' + 'mouseup']
+ * @param {object} aExpectedEvent
+ *        Information about the expected event to occur
+ *        Elements: target     - Element which should receive the event
+ *                               [optional - default: current element]
+ *                  type       - Type of the expected mouse event
+ */
+MozMillElement.prototype.mouseEvent = function (aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
+  if (!this.element) {
+    throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
+  }
+
+  if ("document" in this.element) {
+    throw new Error("A window cannot be a target for mouse events.");
+  }
+
+  var rect = this.element.getBoundingClientRect();
+
+  if (!aOffsetX || isNaN(aOffsetX)) {
+    aOffsetX = rect.width / 2;
+  }
+
+  if (!aOffsetY || isNaN(aOffsetY)) {
+    aOffsetY = rect.height / 2;
+  }
+
+  // Scroll element into view otherwise the click will fail
+  if ("scrollIntoView" in this.element)
+    this.element.scrollIntoView();
+
+  if (aExpectedEvent) {
+    // The expected event type has to be set
+    if (!aExpectedEvent.type) {
+      throw new Error(arguments.callee.name + ": Expected event type not specified");
+    }
+
+    // If no target has been specified use the specified element
+    var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
+                                       : this.element;
+    if (!target) {
+      throw new Error(arguments.callee.name + ": could not find element " +
+                      aExpectedEvent.target.getInfo());
+    }
+
+    EventUtils.synthesizeMouseExpectEvent(this.element, aOffsetX, aOffsetY, aEvent,
+                                          target, aExpectedEvent.type,
+                                          "MozMillElement.mouseEvent()",
+                                          this.element.ownerDocument.defaultView);
+  } else {
+    EventUtils.synthesizeMouse(this.element, aOffsetX, aOffsetY, aEvent,
+                               this.element.ownerDocument.defaultView);
+  }
+
+  // Bug 555347
+  // We don't know why this sleep is necessary but more investigation is needed
+  // before it can be removed
+  utils.sleep(0);
+
+  return true;
+};
+
+/**
+ * Synthesize a mouse click event on the given element
+ */
+MozMillElement.prototype.click = function (aOffsetX, aOffsetY, aExpectedEvent) {
+  // Handle menu items differently
+  if (this.element && this.element.tagName == "menuitem") {
+    this.element.click();
+  } else {
+    this.mouseEvent(aOffsetX, aOffsetY, {}, aExpectedEvent);
+  }
+
+  broker.pass({'function':'MozMillElement.click()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a double click on the given element
+ */
+MozMillElement.prototype.doubleClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
+  this.mouseEvent(aOffsetX, aOffsetY, {clickCount: 2}, aExpectedEvent);
+
+  broker.pass({'function':'MozMillElement.doubleClick()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a mouse down event on the given element
+ */
+MozMillElement.prototype.mouseDown = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
+  this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mousedown"}, aExpectedEvent);
+
+  broker.pass({'function':'MozMillElement.mouseDown()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a mouse out event on the given element
+ */
+MozMillElement.prototype.mouseOut = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
+  this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseout"}, aExpectedEvent);
+
+  broker.pass({'function':'MozMillElement.mouseOut()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a mouse over event on the given element
+ */
+MozMillElement.prototype.mouseOver = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
+  this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseover"}, aExpectedEvent);
+
+  broker.pass({'function':'MozMillElement.mouseOver()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a mouse up event on the given element
+ */
+MozMillElement.prototype.mouseUp = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
+  this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseup"}, aExpectedEvent);
+
+  broker.pass({'function':'MozMillElement.mouseUp()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a mouse middle click event on the given element
+ */
+MozMillElement.prototype.middleClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
+  this.mouseEvent(aOffsetX, aOffsetY, {button: 1}, aExpectedEvent);
+
+  broker.pass({'function':'MozMillElement.middleClick()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a mouse right click event on the given element
+ */
+MozMillElement.prototype.rightClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
+  this.mouseEvent(aOffsetX, aOffsetY, {type : "contextmenu", button: 2 }, aExpectedEvent);
+
+  broker.pass({'function':'MozMillElement.rightClick()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a general touch event on the given element
+ *
+ * @param {Number} [aOffsetX=aElement.width / 2]
+ *        Relative x offset in the elements bounds to click on
+ * @param {Number} [aOffsetY=aElement.height / 2]
+ *        Relative y offset in the elements bounds to click on
+ * @param {Object} [aEvent]
+ *        Information about the event to send
+ * @param {Boolean} [aEvent.altKey=false]
+ *        A Boolean value indicating whether or not the alt key was down when
+ *        the touch event was fired
+ * @param {Number} [aEvent.angle=0]
+ *        The angle (in degrees) that the ellipse described by rx and
+ *        ry must be rotated, clockwise, to most accurately cover the area
+ *        of contact between the user and the surface.
+ * @param {Touch[]} [aEvent.changedTouches]
+ *        A TouchList of all the Touch objects representing individual points of
+ *        contact whose states changed between the previous touch event and
+ *        this one
+ * @param {Boolean} [aEvent.ctrlKey]
+ *        A Boolean value indicating whether or not the control key was down
+ *        when the touch event was fired
+ * @param {Number} [aEvent.force=1]
+ *        The amount of pressure being applied to the surface by the user, as a
+ *        float between 0.0 (no pressure) and 1.0 (maximum pressure)
+ * @param {Number} [aEvent.id=0]
+ *        A unique identifier for this Touch object. A given touch (say, by a
+ *        finger) will have the same identifier for the duration of its movement
+ *        around the surface. This lets you ensure that you're tracking the same
+ *        touch all the time
+ * @param {Boolean} [aEvent.metaKey]
+ *        A Boolean value indicating whether or not the meta key was down when
+ *        the touch event was fired.
+ * @param {Number} [aEvent.rx=1]
+ *        The X radius of the ellipse that most closely circumscribes the area
+ *        of contact with the screen.
+ * @param {Number} [aEvent.ry=1]
+ *        The Y radius of the ellipse that most closely circumscribes the area
+ *        of contact with the screen.
+ * @param {Boolean} [aEvent.shiftKey]
+ *        A Boolean value indicating whether or not the shift key was down when
+ *        the touch event was fired
+ * @param {Touch[]} [aEvent.targetTouches]
+ *        A TouchList of all the Touch objects that are both currently in
+ *        contact with the touch surface and were also started on the same
+ *        element that is the target of the event
+ * @param {Touch[]} [aEvent.touches]
+ *        A TouchList of all the Touch objects representing all current points
+ *        of contact with the surface, regardless of target or changed status
+ * @param {Number} [aEvent.type=*|touchstart|touchend|touchmove|touchenter|touchleave|touchcancel]
+ *        The type of touch event that occurred
+ * @param {Element} [aEvent.target]
+ *        The target of the touches associated with this event. This target
+ *        corresponds to the target of all the touches in the targetTouches
+ *        attribute, but note that other touches in this event may have a
+ *        different target. To be careful, you should use the target associated
+ *        with individual touches
+ */
+MozMillElement.prototype.touchEvent = function (aOffsetX, aOffsetY, aEvent) {
+  if (!this.element) {
+    throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
+  }
+
+  if ("document" in this.element) {
+    throw new Error("A window cannot be a target for touch events.");
+  }
+
+  var rect = this.element.getBoundingClientRect();
+
+  if (!aOffsetX || isNaN(aOffsetX)) {
+    aOffsetX = rect.width / 2;
+  }
+
+  if (!aOffsetY || isNaN(aOffsetY)) {
+    aOffsetY = rect.height / 2;
+  }
+
+  // Scroll element into view otherwise the click will fail
+  if ("scrollIntoView" in this.element) {
+    this.element.scrollIntoView();
+  }
+
+  EventUtils.synthesizeTouch(this.element, aOffsetX, aOffsetY, aEvent,
+                             this.element.ownerDocument.defaultView);
+
+  return true;
+};
+
+/**
+ * Synthesize a touch tap event on the given element
+ *
+ * @param {Number} [aOffsetX=aElement.width / 2]
+ *        Left offset in px where the event is triggered
+ * @param {Number} [aOffsetY=aElement.height / 2]
+ *        Top offset in px where the event is triggered
+ * @param {Object} [aExpectedEvent]
+ *        Information about the expected event to occur
+ * @param {MozMillElement} [aExpectedEvent.target=this.element]
+ *        Element which should receive the event
+ * @param {MozMillElement} [aExpectedEvent.type]
+ *        Type of the expected mouse event
+ */
+MozMillElement.prototype.tap = function (aOffsetX, aOffsetY, aExpectedEvent) {
+  this.mouseEvent(aOffsetX, aOffsetY, {
+    clickCount: 1,
+    inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+  }, aExpectedEvent);
+
+  broker.pass({'function':'MozMillElement.tap()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a double tap on the given element
+ *
+ * @param {Number} [aOffsetX=aElement.width / 2]
+ *        Left offset in px where the event is triggered
+ * @param {Number} [aOffsetY=aElement.height / 2]
+ *        Top offset in px where the event is triggered
+ * @param {Object} [aExpectedEvent]
+ *        Information about the expected event to occur
+ * @param {MozMillElement} [aExpectedEvent.target=this.element]
+ *        Element which should receive the event
+ * @param {MozMillElement} [aExpectedEvent.type]
+ *        Type of the expected mouse event
+ */
+MozMillElement.prototype.doubleTap = function (aOffsetX, aOffsetY, aExpectedEvent) {
+  this.mouseEvent(aOffsetX, aOffsetY, {
+    clickCount: 2,
+    inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+  }, aExpectedEvent);
+
+  broker.pass({'function':'MozMillElement.doubleTap()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a long press
+ *
+ * @param {Number} aOffsetX
+ *        Left offset in px where the event is triggered
+ * @param {Number} aOffsetY
+ *        Top offset in px where the event is triggered
+ * @param {Number} [aTime=1000]
+ *        Duration of the "press" event in ms
+ */
+MozMillElement.prototype.longPress = function (aOffsetX, aOffsetY, aTime) {
+  var time = aTime || 1000;
+
+  this.touchStart(aOffsetX, aOffsetY);
+  utils.sleep(time);
+  this.touchEnd(aOffsetX, aOffsetY);
+
+  broker.pass({'function':'MozMillElement.longPress()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a touch & drag event on the given element
+ *
+ * @param {Number} aOffsetX1
+ *        Left offset of the start position
+ * @param {Number} aOffsetY1
+ *        Top offset of the start position
+ * @param {Number} aOffsetX2
+ *        Left offset of the end position
+ * @param {Number} aOffsetY2
+ *        Top offset of the end position
+ */
+MozMillElement.prototype.touchDrag = function (aOffsetX1, aOffsetY1, aOffsetX2, aOffsetY2) {
+  this.touchStart(aOffsetX1, aOffsetY1);
+  this.touchMove(aOffsetX2, aOffsetY2);
+  this.touchEnd(aOffsetX2, aOffsetY2);
+
+  broker.pass({'function':'MozMillElement.move()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a press / touchstart event on the given element
+ *
+ * @param {Number} aOffsetX
+ *        Left offset where the event is triggered
+ * @param {Number} aOffsetY
+ *        Top offset where the event is triggered
+ */
+MozMillElement.prototype.touchStart = function (aOffsetX, aOffsetY) {
+  this.touchEvent(aOffsetX, aOffsetY, { type: "touchstart" });
+
+  broker.pass({'function':'MozMillElement.touchStart()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a release / touchend event on the given element
+ *
+ * @param {Number} aOffsetX
+ *        Left offset where the event is triggered
+ * @param {Number} aOffsetY
+ *        Top offset where the event is triggered
+ */
+MozMillElement.prototype.touchEnd = function (aOffsetX, aOffsetY) {
+  this.touchEvent(aOffsetX, aOffsetY, { type: "touchend" });
+
+  broker.pass({'function':'MozMillElement.touchEnd()'});
+
+  return true;
+};
+
+/**
+ * Synthesize a touchMove event on the given element
+ *
+ * @param {Number} aOffsetX
+ *        Left offset where the event is triggered
+ * @param {Number} aOffsetY
+ *        Top offset where the event is triggered
+ */
+MozMillElement.prototype.touchMove = function (aOffsetX, aOffsetY) {
+  this.touchEvent(aOffsetX, aOffsetY, { type: "touchmove" });
+
+  broker.pass({'function':'MozMillElement.touchMove()'});
+
+  return true;
+};
+
+MozMillElement.prototype.waitForElement = function (timeout, interval) {
+  var elem = this;
+
+  assert.waitFor(function () {
+    return elem.exists();
+  }, "Element.waitForElement(): Element '" + this.getInfo() +
+     "' has been found", timeout, interval);
+
+  broker.pass({'function':'MozMillElement.waitForElement()'});
+};
+
+MozMillElement.prototype.waitForElementNotPresent = function (timeout, interval) {
+  var elem = this;
+
+  assert.waitFor(function () {
+    return !elem.exists();
+  }, "Element.waitForElementNotPresent(): Element '" + this.getInfo() +
+     "' has not been found", timeout, interval);
+
+  broker.pass({'function':'MozMillElement.waitForElementNotPresent()'});
+};
+
+MozMillElement.prototype.waitThenClick = function (timeout, interval,
+                                                   aOffsetX, aOffsetY, aExpectedEvent) {
+  this.waitForElement(timeout, interval);
+  this.click(aOffsetX, aOffsetY, aExpectedEvent);
+};
+
+/**
+ * Waits for the element to be available in the DOM, then trigger a tap event
+ *
+ * @param {Number} [aTimeout=5000]
+ *        Time to wait for the element to be available
+ * @param {Number} [aInterval=100]
+ *        Interval to check for availability
+ * @param {Number} [aOffsetX=aElement.width / 2]
+ *        Left offset where the event is triggered
+ * @param {Number} [aOffsetY=aElement.height / 2]
+ *        Top offset where the event is triggered
+ * @param {Object} [aExpectedEvent]
+ *        Information about the expected event to occur
+ * @param {MozMillElement} [aExpectedEvent.target=this.element]
+ *        Element which should receive the event
+ * @param {MozMillElement} [aExpectedEvent.type]
+ *        Type of the expected mouse event
+ */
+MozMillElement.prototype.waitThenTap = function (aTimeout, aInterval,
+                                                 aOffsetX, aOffsetY, aExpectedEvent) {
+  this.waitForElement(aTimeout, aInterval);
+  this.tap(aOffsetX, aOffsetY, aExpectedEvent);
+};
+
+// Dispatches an HTMLEvent
+MozMillElement.prototype.dispatchEvent = function (eventType, canBubble, modifiers) {
+  canBubble = canBubble || true;
+  modifiers = modifiers || { };
+
+  let document = 'ownerDocument' in this.element ? this.element.ownerDocument
+                                                 : this.element.document;
+
+  let evt = document.createEvent('HTMLEvents');
+  evt.shiftKey = modifiers["shift"];
+  evt.metaKey = modifiers["meta"];
+  evt.altKey = modifiers["alt"];
+  evt.ctrlKey = modifiers["ctrl"];
+  evt.initEvent(eventType, canBubble, true);
+
+  this.element.dispatchEvent(evt);
+};
+
+
+/**
+ * MozMillCheckBox, which inherits from MozMillElement
+ */
+function MozMillCheckBox(locatorType, locator, args) {
+  MozMillElement.call(this, locatorType, locator, args);
+}
+
+
+MozMillCheckBox.prototype = Object.create(MozMillElement.prototype, {
+  check : {
+    /**
+     * Enable/Disable a checkbox depending on the target state
+     *
+     * @param {boolean} state State to set
+     * @return {boolean} Success state
+     */
+    value : function MMCB_check(state) {
+      var result = false;
+
+      if (!this.element) {
+        throw new Error("could not find element " + this.getInfo());
+      }
+
+      // If we have a XUL element, unwrap its XPCNativeWrapper
+      if (this.element.namespaceURI == NAMESPACE_XUL) {
+        this.element = utils.unwrapNode(this.element);
+      }
+
+      state = (typeof(state) == "boolean") ? state : false;
+      if (state != this.element.checked) {
+        this.click();
+        var element = this.element;
+
+        assert.waitFor(function () {
+          return element.checked == state;
+        }, "CheckBox.check(): Checkbox " + this.getInfo() + " could not be checked/unchecked", 500);
+
+        result = true;
+      }
+
+      broker.pass({'function':'MozMillCheckBox.check(' + this.getInfo() +
+                   ', state: ' + state + ')'});
+
+      return result;
+    }
+  }
+});
+
+
+/**
+ * Returns true if node is of type MozMillCheckBox
+ *
+ * @static
+ * @param {DOMNode} node Node to check for its type
+ * @return {boolean} True if node is of type checkbox
+ */
+MozMillCheckBox.isType = function MMCB_isType(node) {
+  return ((node.localName.toLowerCase() == "input" && node.getAttribute("type") == "checkbox") ||
+    (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'checkbox') ||
+    (node.localName.toLowerCase() == 'checkbox'));
+};
+
+
+/**
+ * MozMillRadio, which inherits from MozMillElement
+ */
+function MozMillRadio(locatorType, locator, args) {
+  MozMillElement.call(this, locatorType, locator, args);
+}
+
+
+MozMillRadio.prototype = Object.create(MozMillElement.prototype, {
+  select : {
+    /**
+     * Select the given radio button
+     *
+     * @param {number} [index=0]
+     *        Specifies which radio button in the group to select (only
+     *        applicable to radiogroup elements)
+     * @return {boolean} Success state
+     */
+    value : function MMR_select(index) {
+      if (!this.element) {
+        throw new Error("could not find element " + this.getInfo());
+      }
+
+      if (this.element.localName.toLowerCase() == "radiogroup") {
+        var element = this.element.getElementsByTagName("radio")[index || 0];
+        new MozMillRadio("Elem", element).click();
+      } else {
+        var element = this.element;
+        this.click();
+      }
+
+      assert.waitFor(function () {
+        // If we have a XUL element, unwrap its XPCNativeWrapper
+        if (element.namespaceURI == NAMESPACE_XUL) {
+          element = utils.unwrapNode(element);
+          return element.selected == true;
+        }
+
+        return element.checked == true;
+      }, "Radio.select(): Radio button " + this.getInfo() + " has been selected", 500);
+
+      broker.pass({'function':'MozMillRadio.select(' + this.getInfo() + ')'});
+
+      return true;
+    }
+  }
+});
+
+
+/**
+ * Returns true if node is of type MozMillRadio
+ *
+ * @static
+ * @param {DOMNode} node Node to check for its type
+ * @return {boolean} True if node is of type radio
+ */
+MozMillRadio.isType = function MMR_isType(node) {
+  return ((node.localName.toLowerCase() == 'input' && node.getAttribute('type') == 'radio') ||
+    (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'radio') ||
+    (node.localName.toLowerCase() == 'radio') ||
+    (node.localName.toLowerCase() == 'radiogroup'));
+};
+
+
+/**
+ * MozMillDropList, which inherits from MozMillElement
+ */
+function MozMillDropList(locatorType, locator, args) {
+  MozMillElement.call(this, locatorType, locator, args);
+}
+
+
+MozMillDropList.prototype = Object.create(MozMillElement.prototype, {
+  select : {
+    /**
+     * Select the specified option and trigger the relevant events of the element
+     * @return {boolean}
+     */
+    value : function MMDL_select(index, option, value) {
+      if (!this.element){
+        throw new Error("Could not find element " + this.getInfo());
+      }
+
+      //if we have a select drop down
+      if (this.element.localName.toLowerCase() == "select"){
+        var item = null;
+
+        // The selected item should be set via its index
+        if (index != undefined) {
+          // Resetting a menulist has to be handled separately
+          if (index == -1) {
+            this.dispatchEvent('focus', false);
+            this.element.selectedIndex = index;
+            this.dispatchEvent('change', true);
+
+            broker.pass({'function':'MozMillDropList.select()'});
+
+            return true;
+          } else {
+            item = this.element.options.item(index);
+          }
+        } else {
+          for (var i = 0; i < this.element.options.length; i++) {
+            var entry = this.element.options.item(i);
+            if (option != undefined && entry.innerHTML == option ||
+              value != undefined && entry.value == value) {
+              item = entry;
+              break;
+            }
+          }
+        }
+
+        // Click the item
+        try {
+          // EventUtils.synthesizeMouse doesn't work.
+          this.dispatchEvent('focus', false);
+          item.selected = true;
+          this.dispatchEvent('change', true);
+
+          var self = this;
+          var selected = index || option || value;
+          assert.waitFor(function () {
+            switch (selected) {
+              case index:
+                return selected === self.element.selectedIndex;
+                break;
+              case option:
+                return selected === item.label;
+                break;
+              case value:
+                return selected === item.value;
+                break;
+            }
+          }, "DropList.select(): The correct item has been selected");
+
+          broker.pass({'function':'MozMillDropList.select()'});
+
+          return true;
+        } catch (e) {
+          throw new Error("No item selected for element " + this.getInfo());
+        }
+      }
+      //if we have a xul menupopup select accordingly
+      else if (this.element.namespaceURI.toLowerCase() == NAMESPACE_XUL) {
+        var ownerDoc = this.element.ownerDocument;
+        // Unwrap the XUL element's XPCNativeWrapper
+        this.element = utils.unwrapNode(this.element);
+        // Get the list of menuitems
+        var menuitems = this.element.
+                        getElementsByTagNameNS(NAMESPACE_XUL, "menupopup")[0].
+                        getElementsByTagNameNS(NAMESPACE_XUL, "menuitem");
+
+        var item = null;
+
+        if (index != undefined) {
+          if (index == -1) {
+            this.dispatchEvent('focus', false);
+            this.element.boxObject.QueryInterface(Ci.nsIMenuBoxObject).activeChild = null;
+            this.dispatchEvent('change', true);
+
+            broker.pass({'function':'MozMillDropList.select()'});
+
+            return true;
+          } else {
+            item = menuitems[index];
+          }
+        } else {
+          for (var i = 0; i < menuitems.length; i++) {
+            var entry = menuitems[i];
+            if (option != undefined && entry.label == option ||
+              value != undefined && entry.value == value) {
+              item = entry;
+              break;
+            }
+          }
+        }
+
+        // Click the item
+        try {
+          item.click();
+
+          var self = this;
+          var selected = index || option || value;
+          assert.waitFor(function () {
+            switch (selected) {
+              case index:
+                return selected === self.element.selectedIndex;
+                break;
+              case option:
+                return selected === self.element.label;
+                break;
+              case value:
+                return selected === self.element.value;
+                break;
+            }
+          }, "DropList.select(): The correct item has been selected");
+
+          broker.pass({'function':'MozMillDropList.select()'});
+
+          return true;
+        } catch (e) {
+          throw new Error('No item selected for element ' + this.getInfo());
+        }
+      }
+    }
+  }
+});
+
+
+/**
+ * Returns true if node is of type MozMillDropList
+ *
+ * @static
+ * @param {DOMNode} node Node to check for its type
+ * @return {boolean} True if node is of type dropdown list
+ */
+MozMillDropList.isType = function MMR_isType(node) {
+  return ((node.localName.toLowerCase() == 'toolbarbutton' &&
+    (node.getAttribute('type') == 'menu' || node.getAttribute('type') == 'menu-button')) ||
+    (node.localName.toLowerCase() == 'menu') ||
+    (node.localName.toLowerCase() == 'menulist') ||
+    (node.localName.toLowerCase() == 'select' ));
+};
+
+
+/**
+ * MozMillTextBox, which inherits from MozMillElement
+ */
+function MozMillTextBox(locatorType, locator, args) {
+  MozMillElement.call(this, locatorType, locator, args);
+}
+
+
+MozMillTextBox.prototype = Object.create(MozMillElement.prototype, {
+  sendKeys : {
+    /**
+     * Synthesize keypress events for each character on the given element
+     *
+     * @param {string} aText
+     *        The text to send as single keypress events
+     * @param {object} aModifiers
+     *        Information about the modifier keys to send
+     *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
+     *                               [optional - default: false]
+     *                  altKey     - Hold down the alt key
+     *                              [optional - default: false]
+     *                  ctrlKey    - Hold down the ctrl key
+     *                               [optional - default: false]
+     *                  metaKey    - Hold down the meta key (command key on Mac)
+     *                               [optional - default: false]
+     *                  shiftKey   - Hold down the shift key
+     *                               [optional - default: false]
+     * @param {object} aExpectedEvent
+     *        Information about the expected event to occur
+     *        Elements: target     - Element which should receive the event
+     *                               [optional - default: current element]
+     *                  type       - Type of the expected key event
+     * @return {boolean} Success state
+     */
+    value : function MMTB_sendKeys(aText, aModifiers, aExpectedEvent) {
+      if (!this.element) {
+        throw new Error("could not find element " + this.getInfo());
+      }
+
+      var element = this.element;
+      Array.forEach(aText, function (letter) {
+        var win = element.ownerDocument ? element.ownerDocument.defaultView
+          : element;
+        element.focus();
+
+        if (aExpectedEvent) {
+          if (!aExpectedEvent.type) {
+            throw new Error(arguments.callee.name + ": Expected event type not specified");
+          }
+
+          var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
+            : element;
+          EventUtils.synthesizeKeyExpectEvent(letter, aModifiers || {}, target,
+            aExpectedEvent.type,
+            "MozMillTextBox.sendKeys()", win);
+        } else {
+          EventUtils.synthesizeKey(letter, aModifiers || {}, win);
+        }
+      });
+
+      broker.pass({'function':'MozMillTextBox.type()'});
+
+      return true;
+    }
+  }
+});
+
+
+/**
+ * Returns true if node is of type MozMillTextBox
+ *
+ * @static
+ * @param {DOMNode} node Node to check for its type
+ * @return {boolean} True if node is of type textbox
+ */
+MozMillTextBox.isType = function MMR_isType(node) {
+  return ((node.localName.toLowerCase() == 'input' &&
+    (node.getAttribute('type') == 'text' || node.getAttribute('type') == 'search')) ||
+    (node.localName.toLowerCase() == 'textarea') ||
+    (node.localName.toLowerCase() == 'textbox'));
+};
new file mode 100644
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/driver/mozmill.js
@@ -0,0 +1,285 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
+                        "getBrowserController", "newBrowserController",
+                        "getAddonsController", "getPreferencesController",
+                        "newMail3PaneController", "getMail3PaneController",
+                        "wm", "platform", "getAddrbkController",
+                        "getMsgComposeController", "getDownloadsController",
+                        "Application", "findElement",
+                        "getPlacesController", 'isMac', 'isLinux', 'isWindows',
+                        "firePythonCallback", "getAddons"
+                       ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// imports
+var assertions = {};  Cu.import('resource://mozmill/modules/assertions.js', assertions);
+var broker = {};      Cu.import('resource://mozmill/driver/msgbroker.js', broker);
+var controller = {};  Cu.import('resource://mozmill/driver/controller.js', controller);
+var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
+var findElement = {}; Cu.import('resource://mozmill/driver/mozelement.js', findElement);
+var os = {};          Cu.import('resource://mozmill/stdlib/os.js', os);
+var utils = {};       Cu.import('resource://mozmill/stdlib/utils.js', utils);
+var windows = {};     Cu.import('resource://mozmill/modules/windows.js', windows);
+
+
+const DEBUG = false;
+
+// This is a useful "check" timer. See utils.js, good for debugging
+if (DEBUG) {
+  utils.startTimer();
+}
+
+var assert = new assertions.Assert();
+
+// platform information
+var platform = os.getPlatform();
+var isMac = false;
+var isWindows = false;
+var isLinux = false;
+
+if (platform == "darwin"){
+  isMac = true;
+}
+
+if (platform == "winnt"){
+  isWindows = true;
+}
+
+if (platform == "linux"){
+  isLinux = true;
+}
+
+var wm = Services.wm;
+
+var appInfo = Services.appinfo;
+var Application = utils.applicationName;
+
+
+/**
+ * Retrieves the list with information about installed add-ons.
+ *
+ * @returns {String} JSON data of installed add-ons
+ */
+function getAddons() {
+  var addons = null;
+
+  AddonManager.getAllAddons(function (addonList) {
+    var tmp_list = [ ];
+
+    addonList.forEach(function (addon) {
+      var tmp = { };
+
+      // We have to filter out properties of type 'function' of the addon
+      // object, which will break JSON.stringify() and result in incomplete
+      // addon information.
+      for (var key in addon) {
+        if (typeof(addon[key]) !== "function") {
+          tmp[key] = addon[key];
+        }
+      }
+
+      tmp_list.push(tmp);
+    });
+
+    addons = tmp_list;
+  });
+
+  try {
+    // Sychronize with getAllAddons so we do not return too early
+    assert.waitFor(function () {
+      return !!addons;
+    })
+
+    return addons;
+  } catch (e) {
+    return null;
+  }
+}
+
+/**
+ * Retrieves application details for the Mozmill report
+ *
+ * @return {String} JSON data of application details
+ */
+function getApplicationDetails() {
+  var locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+               .getService(Ci.nsIXULChromeRegistry)
+               .getSelectedLocale("global");
+
+  // Put all our necessary information into JSON and return it:
+  // appinfo, startupinfo, and addons
+  var details = {
+    application_id: appInfo.ID,
+    application_name: Application,
+    application_version: appInfo.version,
+    application_locale: locale,
+    platform_buildid: appInfo.platformBuildID,
+    platform_version: appInfo.platformVersion,
+    addons: getAddons(),
+    startupinfo: getStartupInfo(),
+    paths: {
+      appdata: Services.dirsvc.get('UAppData', Ci.nsIFile).path,
+      profile: Services.dirsvc.get('ProfD', Ci.nsIFile).path
+    }
+  };
+
+  return JSON.stringify(details);
+}
+
+// get startup time if available
+// see http://blog.mozilla.com/tglek/2011/04/26/measuring-startup-speed-correctly/
+function getStartupInfo() {
+  var startupInfo = {};
+
+  try {
+    var _startupInfo = Services.startup.getStartupInfo();
+    for (var time in _startupInfo) {
+      // convert from Date object to ms since epoch
+      startupInfo[time] = _startupInfo[time].getTime();
+    }
+  } catch (e) {
+    startupInfo = null;
+  }
+
+  return startupInfo;
+}
+
+
+
+function newBrowserController () {
+  return new controller.MozMillController(utils.getMethodInWindows('OpenBrowserWindow')());
+}
+
+function getBrowserController () {
+  var browserWindow = wm.getMostRecentWindow("navigator:browser");
+
+  if (browserWindow == null) {
+    return newBrowserController();
+  } else {
+    return new controller.MozMillController(browserWindow);
+  }
+}
+
+function getPlacesController () {
+  utils.getMethodInWindows('PlacesCommandHook').showPlacesOrganizer('AllBookmarks');
+
+  return new controller.MozMillController(wm.getMostRecentWindow(''));
+}
+
+function getAddonsController () {
+  if (Application == 'SeaMonkey') {
+    utils.getMethodInWindows('toEM')();
+  }
+  else if (Application == 'Thunderbird') {
+    utils.getMethodInWindows('openAddonsMgr')();
+  }
+  else if (Application == 'Sunbird') {
+    utils.getMethodInWindows('goOpenAddons')();
+  } else {
+    utils.getMethodInWindows('BrowserOpenAddonsMgr')();
+  }
+
+  return new controller.MozMillController(wm.getMostRecentWindow(''));
+}
+
+function getDownloadsController() {
+  utils.getMethodInWindows('BrowserDownloadsUI')();
+
+  return new controller.MozMillController(wm.getMostRecentWindow(''));
+}
+
+function getPreferencesController() {
+  if (Application == 'Thunderbird') {
+    utils.getMethodInWindows('openOptionsDialog')();
+  } else {
+    utils.getMethodInWindows('openPreferences')();
+  }
+
+  return new controller.MozMillController(wm.getMostRecentWindow(''));
+}
+
+// Thunderbird functions
+function newMail3PaneController () {
+  return new controller.MozMillController(utils.getMethodInWindows('toMessengerWindow')());
+}
+
+function getMail3PaneController () {
+  var mail3PaneWindow = wm.getMostRecentWindow("mail:3pane");
+
+  if (mail3PaneWindow == null) {
+    return newMail3PaneController();
+  } else {
+    return new controller.MozMillController(mail3PaneWindow);
+  }
+}
+
+// Thunderbird - Address book window
+function newAddrbkController () {
+  utils.getMethodInWindows("toAddressBook")();
+  utils.sleep(2000);
+  var addyWin = wm.getMostRecentWindow("mail:addressbook");
+
+  return new controller.MozMillController(addyWin);
+}
+
+function getAddrbkController () {
+  var addrbkWindow = wm.getMostRecentWindow("mail:addressbook");
+  if (addrbkWindow == null) {
+    return newAddrbkController();
+  } else {
+    return new controller.MozMillController(addrbkWindow);
+  }
+}
+
+function firePythonCallback (filename, method, args, kwargs) {
+  obj = {'filename': filename, 'method': method};
+  obj['args'] = args || [];
+  obj['kwargs'] = kwargs || {};
+
+  broker.sendMessage("firePythonCallback", obj);
+}
+
+function timer (name) {
+  this.name = name;
+  this.timers = {};
+  this.actions = [];
+
+  frame.timers.push(this);
+}
+
+timer.prototype.start = function (name) {
+  this.timers[name].startTime = (new Date).getTime();
+}
+
+timer.prototype.stop = function (name) {
+  var t = this.timers[name];
+
+  t.endTime = (new Date).getTime();
+  t.totalTime = (t.endTime - t.startTime);
+}
+
+timer.prototype.end = function () {
+  frame.events.fireEvent("timer", this);
+  frame.timers.remove(this);
+}
+
+// Initialization
+
+/**
+ * Initialize Mozmill
+ */
+function initialize() {
+  windows.init();
+}
+
+initialize();
new file mode 100644
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/driver/msgbroker.js
@@ -0,0 +1,58 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ['addListener', 'addObject',
+                        'removeListener',
+                        'sendMessage', 'log', 'pass', 'fail'];
+
+var listeners = {};
+
+// add a listener for a specific message type
+function addListener(msgType, listener) {
+  if (listeners[msgType] === undefined) {
+    listeners[msgType] = [];
+  }
+
+  listeners[msgType].push(listener);
+}
+
+// add each method in an object as a message listener
+function addObject(object) {
+  for (var msgType in object) {
+    addListener(msgType, object[msgType]);
+  }
+}
+
+// remove a listener for all message types
+function removeListener(listener) {
+  for (var msgType in listeners) {
+    for (let i = 0; i < listeners.length; ++i) {
+      if (listeners[msgType][i] == listener) {
+        listeners[msgType].splice(i, 1); // remove listener from array
+      }
+    }
+  }
+}
+
+function sendMessage(msgType, obj) {
+  if (listeners[msgType] === undefined) {
+    return;
+  }
+
+  for (let i = 0; i < listeners[msgType].length; ++i) {
+    listeners[msgType][i](obj);
+  }
+}
+
+function log(obj) {
+  sendMessage('log', obj);
+}
+
+function pass(obj) {
+  sendMessage('pass', obj);
+}
+
+function fail(obj) {
+  sendMessage('fail', obj);
+}
--- a/services/sync/tps/extensions/mozmill/resource/modules/assertions.js
+++ b/services/sync/tps/extensions/mozmill/resource/modules/assertions.js
@@ -1,202 +1,413 @@
 /* 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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = ['Assert', 'Expect'];
+
+const Cu = Components.utils;
 
-// Use the frame module of Mozmill to raise non-fatal failures
-var mozmillFrame = {};
-Cu.import('resource://mozmill/modules/frame.js', mozmillFrame);
+Cu.import("resource://gre/modules/Services.jsm");
 
+var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
+var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
+var stack = {}; Cu.import('resource://mozmill/modules/stack.js', stack);
 
 /**
  * @name assertions
  * @namespace Defines expect and assert methods to be used for assertions.
  */
-var assertions = exports;
+
+/**
+ * The Assert class implements fatal assertions, and can be used in cases
+ * when a failing test has to directly abort the current test function. All
+ * remaining tasks will not be performed.
+ *
+ */
+var Assert = function () {}
+
+Assert.prototype = {
+
+  // The following deepEquals implementation is from Narwhal under this license:
 
+  // http://wiki.commonjs.org/wiki/Unit_Testing/1.0
+  //
+  // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
+  //
+  // Originally from narwhal.js (http://narwhaljs.org)
+  // Copyright (c) 2009 Thomas Robinson <280north.com>
+  //
+  // Permission is hereby granted, free of charge, to any person obtaining a copy
+  // of this software and associated documentation files (the 'Software'), to
+  // deal in the Software without restriction, including without limitation the
+  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+  // sell copies of the Software, and to permit persons to whom the Software is
+  // furnished to do so, subject to the following conditions:
+  //
+  // The above copyright notice and this permission notice shall be included in
+  // all copies or substantial portions of the Software.
+  //
+  // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+  _deepEqual: function (actual, expected) {
+    // 7.1. All identical values are equivalent, as determined by ===.
+    if (actual === expected) {
+      return true;
+
+    // 7.2. If the expected value is a Date object, the actual value is
+    // equivalent if it is also a Date object that refers to the same time.
+    } else if (actual instanceof Date && expected instanceof Date) {
+      return actual.getTime() === expected.getTime();
+
+    // 7.3. Other pairs that do not both pass typeof value == 'object',
+    // equivalence is determined by ==.
+    } else if (typeof actual != 'object' && typeof expected != 'object') {
+      return actual == expected;
+
+    // 7.4. For all other Object pairs, including Array objects, equivalence is
+    // determined by having the same number of owned properties (as verified
+    // with Object.prototype.hasOwnProperty.call), the same set of keys
+    // (although not necessarily the same order), equivalent values for every
+    // corresponding key, and an identical 'prototype' property. Note: this
+    // accounts for both named and indexed properties on Arrays.
+    } else {
+      return this._objEquiv(actual, expected);
+    }
+  },
 
-/* non-fatal assertions */
-var Expect = function() {}
+  _objEquiv: function (a, b) {
+    if (a == null || a == undefined || b == null || b == undefined)
+      return false;
+    // an identical 'prototype' property.
+    if (a.prototype !== b.prototype) return false;
+
+    function isArguments(object) {
+      return Object.prototype.toString.call(object) == '[object Arguments]';
+    }
 
-Expect.prototype = {
+    //~~~I've managed to break Object.keys through screwy arguments passing.
+    // Converting to array solves the problem.
+    if (isArguments(a)) {
+      if (!isArguments(b)) {
+        return false;
+      }
+      a = pSlice.call(a);
+      b = pSlice.call(b);
+      return _deepEqual(a, b);
+    }
+    try {
+      var ka = Object.keys(a),
+          kb = Object.keys(b),
+          key, i;
+    } catch (e) {//happens when one is a string literal and the other isn't
+      return false;
+    }
+    // having the same number of owned properties (keys incorporates
+    // hasOwnProperty)
+    if (ka.length != kb.length)
+      return false;
+    //the same set of keys (although not necessarily the same order),
+    ka.sort();
+    kb.sort();
+    //~~~cheap key test
+    for (i = ka.length - 1; i >= 0; i--) {
+      if (ka[i] != kb[i])
+        return false;
+    }
+    //equivalent values for every corresponding key, and
+    //~~~possibly expensive deep test
+    for (i = ka.length - 1; i >= 0; i--) {
+      key = ka[i];
+      if (!this._deepEqual(a[key], b[key])) return false;
+    }
+    return true;
+  },
+
+  _expectedException : function Assert__expectedException(actual, expected) {
+    if (!actual || !expected) {
+      return false;
+    }
+
+    if (expected instanceof RegExp) {
+      return expected.test(actual);
+    } else if (actual instanceof expected) {
+      return true;
+    } else if (expected.call({}, actual) === true) {
+      return true;
+    } else if (actual.name === expected.name) {
+      return true;
+    }
+
+    return false;
+  },
 
   /**
-   * Log a test as failing by adding a fail frame.
+   * Log a test as failing by throwing an AssertionException.
    *
    * @param {object} aResult
    *   Test result details used for reporting.
    *   <dl>
    *     <dd>fileName</dd>
    *     <dt>Name of the file in which the assertion failed.</dt>
-   *     <dd>function</dd>
+   *     <dd>functionName</dd>
    *     <dt>Function in which the assertion failed.</dt>
    *     <dd>lineNumber</dd>
    *     <dt>Line number of the file in which the assertion failed.</dt>
    *     <dd>message</dd>
    *     <dt>Message why the assertion failed.</dt>
    *   </dl>
+   * @throws {errors.AssertionError}
+   *
    */
-  _logFail: function Expect__logFail(aResult) {
-    mozmillFrame.events.fail({fail: aResult});
+  _logFail: function Assert__logFail(aResult) {
+    throw new errors.AssertionError(aResult.message,
+                                    aResult.fileName,
+                                    aResult.lineNumber,
+                                    aResult.functionName,
+                                    aResult.name);
   },
 
   /**
    * Log a test as passing by adding a pass frame.
    *
    * @param {object} aResult
    *   Test result details used for reporting.
    *   <dl>
    *     <dd>fileName</dd>
    *     <dt>Name of the file in which the assertion failed.</dt>
-   *     <dd>function</dd>
+   *     <dd>functionName</dd>
    *     <dt>Function in which the assertion failed.</dt>
    *     <dd>lineNumber</dd>
    *     <dt>Line number of the file in which the assertion failed.</dt>
    *     <dd>message</dd>
    *     <dt>Message why the assertion failed.</dt>
    *   </dl>
    */
-  _logPass: function Expect__logPass(aResult) {
-    mozmillFrame.events.pass({pass: aResult});
+  _logPass: function Assert__logPass(aResult) {
+    broker.pass({pass: aResult});
   },
 
   /**
    * Test the condition and mark test as passed or failed
    *
    * @param {boolean} aCondition
    *   Condition to test.
    * @param {string} aMessage
    *   Message to show for the test result
    * @param {string} aDiagnosis
    *   Diagnose message to show for the test result
+   * @throws {errors.AssertionError}
+   *
    * @returns {boolean} Result of the test.
    */
-  _test: function Expect__test(aCondition, aMessage, aDiagnosis) {
+  _test: function Assert__test(aCondition, aMessage, aDiagnosis) {
     let diagnosis = aDiagnosis || "";
     let message = aMessage || "";
 
     if (diagnosis)
       message = aMessage ? message + " - " + diagnosis : diagnosis;
 
     // Build result data
-    let frame = Components.stack;
+    let frame = stack.findCallerFrame(Components.stack);
+
     let result = {
-      'fileName'   : frame.filename.replace(/(.*)-> /, ""),
-      'function'   : frame.name,
-      'lineNumber' : frame.lineNumber,
-      'message'    : message
+      'fileName'     : frame.filename.replace(/(.*)-> /, ""),
+      'functionName' : frame.name,
+      'lineNumber'   : frame.lineNumber,
+      'message'      : message
     };
 
     // Log test result
-    if (aCondition)
+    if (aCondition) {
       this._logPass(result);
-    else
+    }
+    else {
+      result.stack = Components.stack;
       this._logFail(result);
+    }
 
     return aCondition;
   },
 
   /**
    * Perform an always passing test
    *
    * @param {string} aMessage
    *   Message to show for the test result.
    * @returns {boolean} Always returns true.
    */
-  pass: function Expect_pass(aMessage) {
+  pass: function Assert_pass(aMessage) {
     return this._test(true, aMessage, undefined);
   },
 
   /**
    * Perform an always failing test
    *
    * @param {string} aMessage
    *   Message to show for the test result.
+   * @throws {errors.AssertionError}
+   *
    * @returns {boolean} Always returns false.
    */
-  fail: function Expect_fail(aMessage) {
+  fail: function Assert_fail(aMessage) {
     return this._test(false, aMessage, undefined);
   },
 
   /**
    * Test if the value pass
    *
    * @param {boolean|string|number|object} aValue
    *   Value to test.
    * @param {string} aMessage
    *   Message to show for the test result.
+   * @throws {errors.AssertionError}
+   *
    * @returns {boolean} Result of the test.
    */
-  ok: function Expect_ok(aValue, aMessage) {
+  ok: function Assert_ok(aValue, aMessage) {
     let condition = !!aValue;
     let diagnosis = "got '" + aValue + "'";
 
     return this._test(condition, aMessage, diagnosis);
   },
 
-  /**
+ /**
    * Test if both specified values are identical.
    *
    * @param {boolean|string|number|object} aValue
    *   Value to test.
    * @param {boolean|string|number|object} aExpected
    *   Value to strictly compare with.
    * @param {string} aMessage
    *   Message to show for the test result
+   * @throws {errors.AssertionError}
+   *
    * @returns {boolean} Result of the test.
    */
-  equal: function Expect_equal(aValue, aExpected, aMessage) {
+  equal: function Assert_equal(aValue, aExpected, aMessage) {
     let condition = (aValue === aExpected);
-    let diagnosis = "got '" + aValue + "', expected '" + aExpected + "'";
+    let diagnosis = "'" + aValue + "' should equal '" + aExpected + "'";
 
     return this._test(condition, aMessage, diagnosis);
   },
 
-  /**
+ /**
    * Test if both specified values are not identical.
    *
    * @param {boolean|string|number|object} aValue
    *   Value to test.
    * @param {boolean|string|number|object} aExpected
    *   Value to strictly compare with.
    * @param {string} aMessage
    *   Message to show for the test result
+   * @throws {errors.AssertionError}
+   *
+   * @returns {boolean} Result of the test.
+   */
+  notEqual: function Assert_notEqual(aValue, aExpected, aMessage) {
+    let condition = (aValue !== aExpected);
+    let diagnosis = "'" + aValue + "' should not equal '" + aExpected + "'";
+
+    return this._test(condition, aMessage, diagnosis);
+  },
+
+  /**
+   * Test if an object equals another object
+   *
+   * @param {object} aValue
+   *   The object to test.
+   * @param {object} aExpected
+   *   The object to strictly compare with.
+   * @param {string} aMessage
+   *   Message to show for the test result
+   * @throws {errors.AssertionError}
+   *
    * @returns {boolean} Result of the test.
    */
-  notEqual: function Expect_notEqual(aValue, aExpected, aMessage) {
-    let condition = (aValue !== aExpected);
-    let diagnosis = "got '" + aValue + "', not expected '" + aExpected + "'";
+  deepEqual: function equal(aValue, aExpected, aMessage) {
+    let condition = this._deepEqual(aValue, aExpected);
+    try {
+      var aValueString = JSON.stringify(aValue);
+    } catch (e) {
+      var aValueString = String(aValue);
+    }
+    try {
+      var aExpectedString = JSON.stringify(aExpected);
+    } catch (e) {
+      var aExpectedString = String(aExpected);
+    }
+
+    let diagnosis = "'" + aValueString + "' should equal '" +
+                    aExpectedString + "'";
 
     return this._test(condition, aMessage, diagnosis);
   },
 
   /**
+   * Test if an object does not equal another object
+   *
+   * @param {object} aValue
+   *   The object to test.
+   * @param {object} aExpected
+   *   The object to strictly compare with.
+   * @param {string} aMessage
+   *   Message to show for the test result
+   * @throws {errors.AssertionError}
+   *
+   * @returns {boolean} Result of the test.
+   */
+  notDeepEqual: function notEqual(aValue, aExpected, aMessage) {
+     let condition = !this._deepEqual(aValue, aExpected);
+     try {
+       var aValueString = JSON.stringify(aValue);
+     } catch (e) {
+       var aValueString = String(aValue);
+     }
+     try {
+       var aExpectedString = JSON.stringify(aExpected);
+     } catch (e) {
+       var aExpectedString = String(aExpected);
+     }
+
+     let diagnosis = "'" + aValueString + "' should not equal '" +
+                     aExpectedString + "'";
+
+     return this._test(condition, aMessage, diagnosis);
+  },
+
+  /**
    * Test if the regular expression matches the string.
    *
    * @param {string} aString
    *   String to test.
    * @param {RegEx} aRegex
    *   Regular expression to use for testing that a match exists.
    * @param {string} aMessage
    *   Message to show for the test result
+   * @throws {errors.AssertionError}
+   *
    * @returns {boolean} Result of the test.
    */
-  match: function Expect_match(aString, aRegex, aMessage) {
+  match: function Assert_match(aString, aRegex, aMessage) {
     // XXX Bug 634948
     // Regex objects are transformed to strings when evaluated in a sandbox
     // For now lets re-create the regex from its string representation
     let pattern = flags = "";
     try {
       let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
 
       pattern = matches[1];
       flags = matches[2];
-    }
-    catch (ex) {
+    } catch (e) {
     }
 
     let regex = new RegExp(pattern, flags);
     let condition = (aString.match(regex) !== null);
     let diagnosis = "'" + regex + "' matches for '" + aString + "'";
 
     return this._test(condition, aMessage, diagnosis);
   },
@@ -205,30 +416,31 @@ Expect.prototype = {
    * Test if the regular expression does not match the string.
    *
    * @param {string} aString
    *   String to test.
    * @param {RegEx} aRegex
    *   Regular expression to use for testing that a match does not exist.
    * @param {string} aMessage
    *   Message to show for the test result
+   * @throws {errors.AssertionError}
+   *
    * @returns {boolean} Result of the test.
    */
-  notMatch: function Expect_notMatch(aString, aRegex, aMessage) {
+  notMatch: function Assert_notMatch(aString, aRegex, aMessage) {
     // XXX Bug 634948
     // Regex objects are transformed to strings when evaluated in a sandbox
     // For now lets re-create the regex from its string representation
     let pattern = flags = "";
     try {
       let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
 
       pattern = matches[1];
       flags = matches[2];
-    }
-    catch (ex) {
+    } catch (e) {
     }
 
     let regex = new RegExp(pattern, flags);
     let condition = (aString.match(regex) === null);
     let diagnosis = "'" + regex + "' doesn't match for '" + aString + "'";
 
     return this._test(condition, aMessage, diagnosis);
   },
@@ -238,44 +450,48 @@ Expect.prototype = {
    * Test if a code block throws an exception.
    *
    * @param {string} block
    *   function to call to test for exception
    * @param {RegEx} error
    *   the expected error class
    * @param {string} message
    *   message to present if assertion fails
+   * @throws {errors.AssertionError}
+   *
    * @returns {boolean} Result of the test.
    */
-  throws : function Expect_throws(block, /*optional*/error, /*optional*/message) {
+  throws : function Assert_throws(block, /*optional*/error, /*optional*/message) {
     return this._throws.apply(this, [true].concat(Array.prototype.slice.call(arguments)));
   },
 
   /**
    * Test if a code block doesn't throw an exception.
    *
    * @param {string} block
    *   function to call to test for exception
    * @param {RegEx} error
    *   the expected error class
    * @param {string} message
    *   message to present if assertion fails
+   * @throws {errors.AssertionError}
+   *
    * @returns {boolean} Result of the test.
    */
-  doesNotThrow : function Expect_doesNotThrow(block, /*optional*/error, /*optional*/message) {
+  doesNotThrow : function Assert_doesNotThrow(block, /*optional*/error, /*optional*/message) {
     return this._throws.apply(this, [false].concat(Array.prototype.slice.call(arguments)));
   },
 
   /* Tests whether a code block throws the expected exception
      class. helper for throws() and doesNotThrow()
 
      adapted from node.js's assert._throws()
      https://github.com/joyent/node/blob/master/lib/assert.js
   */
-  _throws : function Expect__throws(shouldThrow, block, expected, message) {
+  _throws : function Assert__throws(shouldThrow, block, expected, message) {
     var actual;
 
     if (typeof expected === 'string') {
       message = expected;
       expected = null;
     }
 
     try {
@@ -294,85 +510,158 @@ Expect.prototype = {
     if (!shouldThrow && this._expectedException(actual, expected)) {
       return this._test(false, message, 'Got unwanted exception');
     }
 
     if ((shouldThrow && actual && expected &&
         !this._expectedException(actual, expected)) || (!shouldThrow && actual)) {
       throw actual;
     }
+
     return this._test(true, message);
   },
 
-  _expectedException : function Expect__expectedException(actual, expected) {
-    if (!actual || !expected) {
-      return false;
+  /**
+   * Test if the string contains the pattern.
+   *
+   * @param {String} aString String to test.
+   * @param {String} aPattern Pattern to look for in the string
+   * @param {String} aMessage Message to show for the test result
+   * @throws {errors.AssertionError}
+   *
+   * @returns {Boolean} Result of the test.
+   */
+  contain: function Assert_contain(aString, aPattern, aMessage) {
+    let condition = (aString.indexOf(aPattern) !== -1);
+    let diagnosis = "'" + aString + "' should contain '" + aPattern + "'";
+
+    return this._test(condition, aMessage, diagnosis);
+  },
+
+  /**
+   * Test if the string does not contain the pattern.
+   *
+   * @param {String} aString String to test.
+   * @param {String} aPattern Pattern to look for in the string
+   * @param {String} aMessage Message to show for the test result
+   * @throws {errors.AssertionError}
+   *
+   * @returns {Boolean} Result of the test.
+   */
+  notContain: function Assert_notContain(aString, aPattern, aMessage) {
+    let condition = (aString.indexOf(aPattern) === -1);
+    let diagnosis = "'" + aString + "' should not contain '" + aPattern + "'";
+
+    return this._test(condition, aMessage, diagnosis);
+  },
+
+  /**
+   * Waits for the callback evaluates to true
+   *
+   * @param {Function} aCallback
+   *        Callback for evaluation
+   * @param {String} aMessage
+   *        Message to show for result
+   * @param {Number} aTimeout
+   *        Timeout in waiting for evaluation
+   * @param {Number} aInterval
+   *        Interval between evaluation attempts
+   * @param {Object} aThisObject
+   *        this object
+   * @throws {errors.AssertionError}
+   *
+   * @returns {Boolean} Result of the test.
+   */
+  waitFor: function Assert_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
+    var timeout = aTimeout || 5000;
+    var interval = aInterval || 100;
+
+    var self = {
+      timeIsUp: false,
+      result: aCallback.call(aThisObject)
+    };
+    var deadline = Date.now() + timeout;
+
+    function wait() {
+      if (self.result !== true) {
+        self.result = aCallback.call(aThisObject);
+        self.timeIsUp = Date.now() > deadline;
+      }
     }
 
-    if (expected instanceof RegExp) {
-      return expected.test(actual);
-    } else if (actual instanceof expected) {
-      return true;
-    } else if (expected.call({}, actual) === true) {
-      return true;
+    var hwindow = Services.appShell.hiddenDOMWindow;
+    var timeoutInterval = hwindow.setInterval(wait, interval);
+    var thread = Services.tm.currentThread;
+
+    while (self.result !== true && !self.timeIsUp) {
+      thread.processNextEvent(true);
+
+      let type = typeof(self.result);
+      if (type !== 'boolean')
+        throw TypeError("waitFor() callback has to return a boolean" +
+                        " instead of '" + type + "'");
     }
 
-    return false;
+    hwindow.clearInterval(timeoutInterval);
+
+    if (self.result !== true && self.timeIsUp) {
+      aMessage = aMessage || arguments.callee.name + ": Timeout exceeded for '" + aCallback + "'";
+      throw new errors.TimeoutError(aMessage);
+    }
+
+    broker.pass({'function':'assert.waitFor()'});
+    return true;
   }
 }
 
-/**
-* AssertionError
-*
-* Error object thrown by failing assertions
-*/
-function AssertionError(message, fileName, lineNumber) {
-  var err = new Error();
-  if (err.stack) {
-    this.stack = err.stack;
-  }
-  this.message = message === undefined ? err.message : message;
-  this.fileName = fileName === undefined ? err.fileName : fileName;
-  this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
-};
-AssertionError.prototype = new Error();
-AssertionError.prototype.constructor = AssertionError;
-AssertionError.prototype.name = 'AssertionError';
+/* non-fatal assertions */
+var Expect = function () {}
 
-
-var Assert = function() {}
-
-Assert.prototype = new Expect();
-
-Assert.prototype.AssertionError = AssertionError;
+Expect.prototype = new Assert();
 
 /**
- * The Assert class implements fatal assertions, and can be used in cases
- * when a failing test has to directly abort the current test function. All
- * remaining tasks will not be performed.
- *
- */
-
-/**
- * Log a test as failing by throwing an AssertionException.
+ * Log a test as failing by adding a fail frame.
  *
  * @param {object} aResult
  *   Test result details used for reporting.
  *   <dl>
  *     <dd>fileName</dd>
  *     <dt>Name of the file in which the assertion failed.</dt>
- *     <dd>function</dd>
+ *     <dd>functionName</dd>
  *     <dt>Function in which the assertion failed.</dt>
  *     <dd>lineNumber</dd>
  *     <dt>Line number of the file in which the assertion failed.</dt>
  *     <dd>message</dd>
  *     <dt>Message why the assertion failed.</dt>
  *   </dl>
- * @throws {AssertionError }
  */
-Assert.prototype._logFail = function Assert__logFail(aResult) {
-  throw new AssertionError(aResult);
+Expect.prototype._logFail = function Expect__logFail(aResult) {
+  broker.fail({fail: aResult});
 }
 
+/**
+ * Waits for the callback evaluates to true
+ *
+ * @param {Function} aCallback
+ *        Callback for evaluation
+ * @param {String} aMessage
+ *        Message to show for result
+ * @param {Number} aTimeout
+ *        Timeout in waiting for evaluation
+ * @param {Number} aInterval
+ *        Interval between evaluation attempts
+ * @param {Object} aThisObject
+ *        this object
+ */
+Expect.prototype.waitFor = function Expect_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
+  let condition = true;
+  let message = aMessage;
 
-// Export of variables
-assertions.Expect = Expect;
-assertions.Assert = Assert;
+  try {
+    Assert.prototype.waitFor.apply(this, arguments);
+  }
+  catch (ex if ex instanceof errors.AssertionError) {
+    message = ex.message;
+    condition = false;
+  }
+
+  return this._test(condition, message);
+}
deleted file mode 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/controller.js
+++ /dev/null
@@ -1,1002 +0,0 @@
-/* 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/. */
-
-var EXPORTED_SYMBOLS = ["MozMillController", "globalEventRegistry", "sleep"];
-
-var EventUtils = {}; Components.utils.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
-
-var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
-var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
-var mozelement = {}; Components.utils.import('resource://mozmill/modules/mozelement.js', mozelement);
-var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
-
-var hwindow = Components.classes["@mozilla.org/appshell/appShellService;1"]
-                .getService(Components.interfaces.nsIAppShellService)
-                .hiddenDOMWindow;
-var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"].
-     getService(Components.interfaces.nsIConsoleService);
-
-// Declare most used utils functions in the controller namespace
-var sleep = utils.sleep;
-var assert = utils.assert;
-var waitFor = utils.waitFor;
-
-waitForEvents = function() {}
-
-waitForEvents.prototype = {
-  /**
-   * Initialize list of events for given node
-   */
-  init : function waitForEvents_init(node, events) {
-    if (node.getNode != undefined)
-      node = node.getNode();
-
-    this.events = events;
-    this.node = node;
-    node.firedEvents = {};
-    this.registry = {};
-
-    for each(e in events) {
-      var listener = function(event) {
-        this.firedEvents[event.type] = true;
-      }
-      this.registry[e] = listener;
-      this.registry[e].result = false;
-      this.node.addEventListener(e, this.registry[e], true);
-    }
-  },
-
-  /**
-   * Wait until all assigned events have been fired
-   */
-  wait : function waitForEvents_wait(timeout, interval)
-  {
-    for (var e in this.registry) {
-      utils.waitFor(function() {
-        return this.node.firedEvents[e] == true;
-      }, "Timeout happened before event '" + ex +"' was fired.", timeout, interval);
-
-      this.node.removeEventListener(e, this.registry[e], true);
-    }
-  }
-}
-
-/**
- * Class to handle menus and context menus
- *
- * @constructor
- * @param {MozMillController} controller
- *        Mozmill controller of the window under test
- * @param {string} menuSelector
- *        jQuery like selector string of the element
- * @param {object} document
- *        Document to use for finding the menu
- *        [optional - default: aController.window.document]
- */
-var Menu = function(controller, menuSelector, document) {
-  this._controller = controller;
-  this._menu = null;
-
-  document = document || controller.window.document;
-  var node = document.querySelector(menuSelector);
-  if (node) {
-    // We don't unwrap nodes automatically yet (Bug 573185)
-    node = node.wrappedJSObject || node;
-    this._menu = new mozelement.Elem(node);
-  }
-  else {
-    throw new Error("Menu element '" + menuSelector + "' not found.");
-  }
-}
-
-Menu.prototype = {
-
-  /**
-   * Open and populate the menu
-   *
-   * @param {ElemBase} contextElement
-   *        Element whose context menu has to be opened
-   * @returns {Menu} The Menu instance
-   */
-  open : function(contextElement) {
-    // We have to open the context menu
-    var menu = this._menu.getNode();
-    if ((menu.localName == "popup" || menu.localName == "menupopup") &&
-        contextElement && contextElement.exists()) {
-      this._controller.rightClick(contextElement);
-      this._controller.waitFor(function() {
-        return menu.state == "open";
-      }, "Context menu has been opened.");
-    }
-
-    // Run through the entire menu and populate with dynamic entries
-    this._buildMenu(menu);
-
-    return this;
-  },
-
-  /**
-   * Close the menu
-   *
-   * @returns {Menu} The Menu instance
-   */
-  close : function() {
-    var menu = this._menu.getNode();
-
-    this._controller.keypress(this._menu, "VK_ESCAPE", {});
-    this._controller.waitFor(function() {
-      return menu.state == "closed";
-    }, "Context menu has been closed.");
-
-    return this;
-  },
-
-  /**
-   * Retrieve the specified menu entry
-   *
-   * @param {string} itemSelector
-   *        jQuery like selector string of the menu item
-   * @returns {ElemBase} Menu element
-   * @throws Error If menu element has not been found
-   */
-  getItem : function(itemSelector) {
-    var node = this._menu.getNode().querySelector(itemSelector);
-
-    if (!node) {
-      throw new Error("Menu entry '" + itemSelector + "' not found.");
-    }
-
-    return new mozelement.Elem(node);
-  },
-
-  /**
-   * Click the specified menu entry
-   *
-   * @param {string} itemSelector
-   *        jQuery like selector string of the menu item
-   *
-   * @returns {Menu} The Menu instance
-   */
-  click : function(itemSelector) {
-    this._controller.click(this.getItem(itemSelector));
-
-    return this;
-  },
-
-  /**
-   * Synthesize a keypress against the menu
-   *
-   * @param {string} key
-   *        Key to press
-   * @param {object} modifier
-   *        Key modifiers
-   * @see MozMillController#keypress
-   *
-   * @returns {Menu} The Menu instance
-   */
-  keypress : function(key, modifier) {
-    this._controller.keypress(this._menu, key, modifier);
-
-    return this;
-  },
-
-  /**
-   * Opens the context menu, click the specified entry and
-   * make sure that the menu has been closed.
-   *
-   * @param {string} itemSelector
-   *        jQuery like selector string of the element
-   * @param {ElemBase} contextElement
-   *        Element whose context menu has to be opened
-   *
-   * @returns {Menu} The Menu instance
-   */
-  select : function(itemSelector, contextElement) {
-    this.open(contextElement);
-    this.click(itemSelector);
-    this.close();
-  },
-
-  /**
-   * Recursive function which iterates through all menu elements and
-   * populates the menus with dynamic menu entries.
-   *
-   * @param {node} menu
-   *        Top menu node whose elements have to be populated
-   */
-  _buildMenu : function(menu) {
-    var items = menu ? menu.childNodes : null;
-
-    Array.forEach(items, function(item) {
-      // When we have a menu node, fake a click onto it to populate
-      // the sub menu with dynamic entries
-      if (item.tagName == "menu") {
-        var popup = item.querySelector("menupopup");
-        if (popup) {
-          if (popup.allowevents) {
-            var popupEvent = this._controller.window.document.createEvent("MouseEvent");
-            popupEvent.initMouseEvent("popupshowing", true, true, this._controller.window, 0,
-                                             0, 0, 0, 0, false, false, false, false, 0, null);
-            popup.dispatchEvent(popupEvent);
-          }
-          this._buildMenu(popup);
-        }
-      }
-    }, this);
-  }
-};
-
-var MozMillController = function (window) {
-  this.window = window;
-
-  this.mozmillModule = {};
-  Components.utils.import('resource://mozmill/modules/mozmill.js', this.mozmillModule);
-
-  utils.waitFor(function() {
-    return window != null && this.isLoaded();
-  }, "controller(): Window could not be initialized.", undefined, undefined, this);
-
-  if ( controllerAdditions[window.document.documentElement.getAttribute('windowtype')] != undefined ) {
-    this.prototype = new utils.Copy(this.prototype);
-    controllerAdditions[window.document.documentElement.getAttribute('windowtype')](this);
-    this.windowtype = window.document.documentElement.getAttribute('windowtype');
-  }
-}
-
-MozMillController.prototype.sleep = utils.sleep;
-
-// Open the specified url in the current tab
-MozMillController.prototype.open = function(url)
-{
-  switch(this.mozmillModule.Application) {
-    case "Firefox":
-      this.window.gBrowser.loadURI(url);
-      break;
-    case "SeaMonkey":
-      this.window.getBrowser().loadURI(url);
-      break;
-    default:
-      throw new Error("MozMillController.open not supported.");
-  }
-
-  frame.events.pass({'function':'Controller.open()'});
-}
-
-/**
- * Take a screenshot of specified node
- *
- * @param {element} node
- *   the window or DOM element to capture
- * @param {string} name
- *   the name of the screenshot used in reporting and as filename
- * @param {boolean} save
- *   if true saves the screenshot as 'name.png' in tempdir, otherwise returns a dataURL
- * @param {element list} highlights
- *   a list of DOM elements to highlight by drawing a red rectangle around them
- */
-MozMillController.prototype.screenShot = function _screenShot(node, name, save, highlights) {
-  if (!node) {
-    throw new Error("node is undefined");
-  }
-
-  // Unwrap the node and highlights
-  if ("getNode" in node) node = node.getNode();
-  if (highlights) {
-    for (var i = 0; i < highlights.length; ++i) {
-      if ("getNode" in highlights[i]) {
-        highlights[i] = highlights[i].getNode();
-      }
-    }
-  }
-
-  // If save is false, a dataURL is used
-  // Include both in the report anyway to avoid confusion and make the report easier to parse
-  var filepath, dataURL;
-  try {
-    if (save) {
-      filepath = utils.takeScreenshot(node, name, highlights);
-    } else {
-      dataURL = utils.takeScreenshot(node, undefined, highlights);
-    }
-  } catch (e) {
-    throw new Error("controller.screenShot() failed: " + e);
-  }
-
-  // Find the name of the test function
-  for (var attr in frame.events.currentModule) {
-    if (frame.events.currentModule[attr] == frame.events.currentTest) {
-      var testName = attr;
-      break;
-    }
-  }
-
-  // Create a timestamp
-  var d = new Date();
-  // Report object
-  var obj = { "filepath": filepath,
-              "dataURL": dataURL,
-              "name": name,
-              "timestamp": d.toLocaleString(),
-              "test_file": frame.events.currentModule.__file__,
-              "test_name": testName,
-            }
-  // Send the screenshot object to python over jsbridge
-  this.fireEvent("screenShot", obj);
-
-  frame.events.pass({'function':'controller.screenShot()'});
-}
-
-/**
- * Checks if the specified window has been loaded
- *
- * @param {DOMWindow} [window=this.window] Window object to check for loaded state
- */
-MozMillController.prototype.isLoaded = function(window) {
-  var win = window || this.window;
-
-  return ("mozmillDocumentLoaded" in win) && win.mozmillDocumentLoaded;
-};
-
-MozMillController.prototype.waitFor = function(callback, message, timeout,
-                                               interval, thisObject) {
-  utils.waitFor(callback, message, timeout, interval, thisObject);
-
-  frame.events.pass({'function':'controller.waitFor()'});
-}
-
-MozMillController.prototype.__defineGetter__("waitForEvents", function() {
-  if (this._waitForEvents == undefined)
-    this._waitForEvents = new waitForEvents();
-  return this._waitForEvents;
-});
-
-/**
- * Wrapper function to create a new instance of a menu
- * @see Menu
- */
-MozMillController.prototype.getMenu = function (menuSelector, document) {
-  return new Menu(this, menuSelector, document);
-};
-
-MozMillController.prototype.__defineGetter__("mainMenu", function() {
-  return this.getMenu("menubar");
-});
-
-MozMillController.prototype.__defineGetter__("menus", function() {
-        throw('controller.menus - DEPRECATED Use controller.mainMenu instead.');
-
-});
-
-MozMillController.prototype.waitForImage = function (elem, timeout, interval) {
-  this.waitFor(function() {
-    return elem.getNode().complete == true;
-  }, "timeout exceeded for waitForImage " + elem.getInfo(), timeout, interval);
-
-  frame.events.pass({'function':'Controller.waitForImage()'});
-}
-
-MozMillController.prototype.fireEvent = function (name, obj) {
-  if (name == "userShutdown") {
-    frame.events.toggleUserShutdown(obj);
-  }
-  frame.events.fireEvent(name, obj);
-}
-
-MozMillController.prototype.startUserShutdown = function (timeout, restart, next, resetProfile) {
-  if (restart && resetProfile) {
-      throw new Error("You can't have a user-restart and reset the profile; there is a race condition");
-  }
-  this.fireEvent('userShutdown', {'user': true,
-                                  'restart': Boolean(restart),
-                                  'next': next,
-                                  'resetProfile': Boolean(resetProfile)});
-  this.window.setTimeout(this.fireEvent, timeout, 'userShutdown', 0);
-}
-
-MozMillController.prototype.restartApplication = function (next, resetProfile)
-{
-  // restart the application via the python runner
-  // - next : name of the next test function to run after restart
-  // - resetProfile : whether to reset the profile after restart
-  this.fireEvent('userShutdown', {'user': false,
-                                  'restart': true,
-                                  'next': next,
-                                  'resetProfile': Boolean(resetProfile)});
-  utils.getMethodInWindows('goQuitApplication')();
-}
-
-MozMillController.prototype.stopApplication = function (resetProfile)
-{
-  // stop the application via the python runner
-  // - resetProfile : whether to reset the profile after shutdown
-  this.fireEvent('userShutdown', {'user': false,
-                                  'restart': false,
-                                  'resetProfile': Boolean(resetProfile)});
-  utils.getMethodInWindows('goQuitApplication')();
-}
-
-//Browser navigation functions
-MozMillController.prototype.goBack = function(){
-  //this.window.focus();
-  this.window.content.history.back();
-  frame.events.pass({'function':'Controller.goBack()'});
-  return true;
-}
-MozMillController.prototype.goForward = function(){
-  //this.window.focus();
-  this.window.content.history.forward();
-  frame.events.pass({'function':'Controller.goForward()'});
-  return true;
-}
-MozMillController.prototype.refresh = function(){
-  //this.window.focus();
-  this.window.content.location.reload(true);
-  frame.events.pass({'function':'Controller.refresh()'});
-  return true;
-}
-
-function logDeprecated(funcName, message) {
-   frame.log({'function': funcName + '() - DEPRECATED', 'message': funcName + '() is deprecated' + message});
-}
-
-function logDeprecatedAssert(funcName) {
-   logDeprecated('controller.' + funcName, '. use the generic `assert` module instead');
-}
-
-MozMillController.prototype.assertText = function (el, text) {
-  logDeprecatedAssert("assertText");
-  //this.window.focus();
-  var n = el.getNode();
-
-  if (n && n.innerHTML == text){
-    frame.events.pass({'function':'Controller.assertText()'});
-    return true;
-   }
-
-  throw new Error("could not validate element " + el.getInfo()+" with text "+ text);
-  return false;
-
-};
-
-//Assert that a specified node exists
-MozMillController.prototype.assertNode = function (el) {
-  logDeprecatedAssert("assertNode");
-
-  //this.window.focus();
-  var element = el.getNode();
-  if (!element){
-    throw new Error("could not find element " + el.getInfo());
-    return false;
-  }
-  frame.events.pass({'function':'Controller.assertNode()'});
-  return true;
-};
-
-// Assert that a specified node doesn't exist
-MozMillController.prototype.assertNodeNotExist = function (el) {
-  logDeprecatedAssert("assertNodeNotExist");
-
-  //this.window.focus();
-  try {
-    var element = el.getNode();
-  } catch(err){
-    frame.events.pass({'function':'Controller.assertNodeNotExist()'});
-    return true;
-  }
-
-  if (element) {
-    throw new Error("Unexpectedly found element " + el.getInfo());
-    return false;
-  } else {
-    frame.events.pass({'function':'Controller.assertNodeNotExist()'});
-    return true;
-  }
-};
-
-//Assert that a form element contains the expected value
-MozMillController.prototype.assertValue = function (el, value) {
-  logDeprecatedAssert("assertValue");
-
-  //this.window.focus();
-  var n = el.getNode();
-
-  if (n && n.value == value){
-    frame.events.pass({'function':'Controller.assertValue()'});
-    return true;
-  }
-  throw new Error("could not validate element " + el.getInfo()+" with value "+ value);
-  return false;
-};
-
-/**
- * Check if the callback function evaluates to true
- */
-MozMillController.prototype.assert = function(callback, message, thisObject)
-{
-  logDeprecatedAssert("assert");
-  utils.assert(callback, message, thisObject);
-
-  frame.events.pass({'function': ": controller.assert('" + callback + "')"});
-  return true;
-}
-
-//Assert that a provided value is selected in a select element
-MozMillController.prototype.assertSelected = function (el, value) {
-  logDeprecatedAssert("assertSelected");
-
-  //this.window.focus();
-  var n = el.getNode();
-  var validator = value;
-
-  if (n && n.options[n.selectedIndex].value == validator){
-    frame.events.pass({'function':'Controller.assertSelected()'});
-    return true;
-    }
-  throw new Error("could not assert value for element " + el.getInfo()+" with value "+ value);
-  return false;
-};
-
-//Assert that a provided checkbox is checked
-MozMillController.prototype.assertChecked = function (el) {
-  logDeprecatedAssert("assertChecked");
-
-  //this.window.focus();
-  var element = el.getNode();
-
-  if (element && element.checked == true){
-    frame.events.pass({'function':'Controller.assertChecked()'});
-    return true;
-    }
-  throw new Error("assert failed for checked element " + el.getInfo());
-  return false;
-};
-
-// Assert that a provided checkbox is not checked
-MozMillController.prototype.assertNotChecked = function (el) {
-  logDeprecatedAssert("assertNotChecked");
-
-  var element = el.getNode();
-
-  if (!element) {
-    throw new Error("Could not find element" + el.getInfo());
-  }
-
-  if (!element.hasAttribute("checked") || element.checked != true){
-    frame.events.pass({'function':'Controller.assertNotChecked()'});
-    return true;
-    }
-  throw new Error("assert failed for not checked element " + el.getInfo());
-  return false;
-};
-
-/**
- * Assert that an element's javascript property exists or has a particular value
- *
- * if val is undefined, will return true if the property exists.
- * if val is specified, will return true if the property exists and has the correct value
- */
-MozMillController.prototype.assertJSProperty = function(el, attrib, val) {
-  logDeprecatedAssert("assertJSProperty");
-
-  var element = el.getNode();
-  if (!element){
-    throw new Error("could not find element " + el.getInfo());
-    return false;
-  }
-  var value = element[attrib];
-  var res = (value !== undefined && (val === undefined ? true : String(value) == String(val)));
-  if (res) {
-    frame.events.pass({'function':'Controller.assertJSProperty("' + el.getInfo() + '") : ' + val});
-  } else {
-    throw new Error("Controller.assertJSProperty(" + el.getInfo() + ") : " +
-                     (val === undefined ? "property '" + attrib + "' doesn't exist" : val + " == " + value));
-  }
-  return res;
-};
-
-/**
- * Assert that an element's javascript property doesn't exist or doesn't have a particular value
- *
- * if val is undefined, will return true if the property doesn't exist.
- * if val is specified, will return true if the property doesn't exist or doesn't have the specified value
- */
-MozMillController.prototype.assertNotJSProperty = function(el, attrib, val) {
-  logDeprecatedAssert("assertNotJSProperty");
-
-  var element = el.getNode();
-  if (!element){
-    throw new Error("could not find element " + el.getInfo());
-    return false;
-  }
-  var value = element[attrib];
-  var res = (val === undefined ? value === undefined : String(value) != String(val));
-  if (res) {
-    frame.events.pass({'function':'Controller.assertNotProperty("' + el.getInfo() + '") : ' + val});
-  } else {
-    throw new Error("Controller.assertNotJSProperty(" + el.getInfo() + ") : " +
-                     (val === undefined ? "property '" + attrib + "' exists" : val + " != " + value));
-  }
-  return res;
-};
-
-/**
- * Assert that an element's dom property exists or has a particular value
- *
- * if val is undefined, will return true if the property exists.
- * if val is specified, will return true if the property exists and has the correct value
- */
-MozMillController.prototype.assertDOMProperty = function(el, attrib, val) {
-  logDeprecatedAssert("assertDOMProperty");
-
-  var element = el.getNode();
-  if (!element){
-    throw new Error("could not find element " + el.getInfo());
-    return false;
-  }
-  var value, res = element.hasAttribute(attrib);
-  if (res && val !== undefined) {
-    value = element.getAttribute(attrib);
-    res = (String(value) == String(val));
-  }
-
-  if (res) {
-    frame.events.pass({'function':'Controller.assertDOMProperty("' + el.getInfo() + '") : ' + val});
-  } else {
-    throw new Error("Controller.assertDOMProperty(" + el.getInfo() + ") : " +
-                     (val === undefined ? "property '" + attrib + "' doesn't exist" : val + " == " + value));
-  }
-  return res;
-};
-
-/**
- * Assert that an element's dom property doesn't exist or doesn't have a particular value
- *
- * if val is undefined, will return true if the property doesn't exist.
- * if val is specified, will return true if the property doesn't exist or doesn't have the specified value
- */
-MozMillController.prototype.assertNotDOMProperty = function(el, attrib, val) {
-  logDeprecatedAssert("assertNotDOMProperty");
-
-  var element = el.getNode();
-  if (!element){
-    throw new Error("could not find element " + el.getInfo());
-    return false;
-  }
-  var value, res = element.hasAttribute(attrib);
-  if (res && val !== undefined) {
-    value = element.getAttribute(attrib);
-    res = (String(value) == String(val));
-  }
-  if (!res) {
-    frame.events.pass({'function':'Controller.assertNotDOMProperty("' + el.getInfo() + '") : ' + val});
-  } else {
-    throw new Error("Controller.assertNotDOMProperty(" + el.getInfo() + ") : " +
-                     (val == undefined ? "property '" + attrib + "' exists" : val + " == " + value));
-  }
-  return !res;
-};
-
-// deprecated - Use assertNotJSProperty or assertNotDOMProperty instead
-MozMillController.prototype.assertProperty = function(el, attrib, val) {
-  logDeprecatedAssert("assertProperty");
-  return this.assertJSProperty(el, attrib, val);
-};
-
-// deprecated - Use assertNotJSProperty or assertNotDOMProperty instead
-MozMillController.prototype.assertPropertyNotExist = function(el, attrib) {
-  logDeprecatedAssert("assertPropertyNotExist");
-  return this.assertNotJSProperty(el, attrib);
-};
-
-// Assert that a specified image has actually loaded
-// The Safari workaround results in additional requests
-// for broken images (in Safari only) but works reliably
-MozMillController.prototype.assertImageLoaded = function (el) {
-  logDeprecatedAssert("assertImageLoaded");
-
-  //this.window.focus();
-  var img = el.getNode();
-  if (!img || img.tagName != 'IMG') {
-    throw new Error('Controller.assertImageLoaded() failed.')
-    return false;
-  }
-  var comp = img.complete;
-  var ret = null; // Return value
-
-  // Workaround for Safari -- it only supports the
-  // complete attrib on script-created images
-  if (typeof comp == 'undefined') {
-    test = new Image();
-    // If the original image was successfully loaded,
-    // src for new one should be pulled from cache
-    test.src = img.src;
-    comp = test.complete;
-  }
-
-  // Check the complete attrib. Note the strict
-  // equality check -- we don't want undefined, null, etc.
-  // --------------------------
-  // False -- Img failed to load in IE/Safari, or is
-  // still trying to load in FF
-  if (comp === false) {
-    ret = false;
-  }
-  // True, but image has no size -- image failed to
-  // load in FF
-  else if (comp === true && img.naturalWidth == 0) {
-    ret = false;
-  }
-  // Otherwise all we can do is assume everything's
-  // hunky-dory
-  else {
-    ret = true;
-  }
-  if (ret) {
-    frame.events.pass({'function':'Controller.assertImageLoaded'});
-  } else {
-    throw new Error('Controller.assertImageLoaded() failed.')
-  }
-
-  return ret;
-};
-
-// Drag one element to the top x,y coords of another specified element
-MozMillController.prototype.mouseMove = function (doc, start, dest) {
-  // if one of these elements couldn't be looked up
-  if (typeof start != 'object'){
-    throw new Error("received bad coordinates");
-    return false;
-  }
-  if (typeof dest != 'object'){
-    throw new Error("received bad coordinates");
-    return false;
-  }
-
-  var triggerMouseEvent = function(element, clientX, clientY) {
-    clientX = clientX ? clientX: 0;
-    clientY = clientY ? clientY: 0;
-
-    // make the mouse understand where it is on the screen
-    var screenX = element.boxObject.screenX ? element.boxObject.screenX : 0;
-    var screenY = element.boxObject.screenY ? element.boxObject.screenY : 0;
-
-    var evt = element.ownerDocument.createEvent('MouseEvents');
-    if (evt.initMouseEvent) {
-      evt.initMouseEvent('mousemove', true, true, element.ownerDocument.defaultView, 1, screenX, screenY, clientX, clientY)
-    }
-    else {
-      //LOG.warn("element doesn't have initMouseEvent; firing an event which should -- but doesn't -- have other mouse-event related attributes here, as well as controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown");
-      evt.initEvent('mousemove', true, true);
-    }
-    element.dispatchEvent(evt);
-  };
-
-  // Do the initial move to the drag element position
-  triggerMouseEvent(doc.body, start[0], start[1]);
-  triggerMouseEvent(doc.body, dest[0], dest[1]);
-  frame.events.pass({'function':'Controller.mouseMove()'});
-  return true;
-}
-
-// Drag an element to the specified offset on another element, firing mouse and drag events.
-// Returns the captured dropEffect. Adapted from EventUtils' synthesizeDrop()
-MozMillController.prototype.dragToElement = function(src, dest, offsetX,
-    offsetY, aWindow, dropEffect, dragData) {
-  srcElement = src.getNode();
-  destElement = dest.getNode();
-  aWindow = aWindow || srcElement.ownerDocument.defaultView;
-  offsetX = offsetX || 20;
-  offsetY = offsetY || 20;
-
-  var dataTransfer;
-
-  var trapDrag = function(event) {
-    dataTransfer = event.dataTransfer;
-    if(!dragData)
-      return;
-
-    for (var i = 0; i < dragData.length; i++) {
-      var item = dragData[i];
-      for (var j = 0; j < item.length; j++) {
-        dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
-      }
-    }
-    dataTransfer.dropEffect = dropEffect || "move";
-    event.preventDefault();
-    event.stopPropagation();
-  }
-
-  aWindow.addEventListener("dragstart", trapDrag, true);
-  EventUtils.synthesizeMouse(srcElement, 2, 2, { type: "mousedown" }, aWindow); // fire mousedown 2 pixels from corner of element
-  EventUtils.synthesizeMouse(srcElement, 11, 11, { type: "mousemove" }, aWindow);
-  EventUtils.synthesizeMouse(srcElement, offsetX, offsetY, { type: "mousemove" }, aWindow);
-  aWindow.removeEventListener("dragstart", trapDrag, true);
-
-  var event = aWindow.document.createEvent("DragEvents");
-  event.initDragEvent("dragenter", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
-  destElement.dispatchEvent(event);
-
-  var event = aWindow.document.createEvent("DragEvents");
-  event.initDragEvent("dragover", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
-  if (destElement.dispatchEvent(event)) {
-    EventUtils.synthesizeMouse(destElement, offsetX, offsetY, { type: "mouseup" }, aWindow);
-    return "none";
-  }
-
-  event = aWindow.document.createEvent("DragEvents");
-  event.initDragEvent("drop", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
-  destElement.dispatchEvent(event);
-  EventUtils.synthesizeMouse(destElement, offsetX, offsetY, { type: "mouseup" }, aWindow);
-
-  return dataTransfer.dropEffect;
-}
-
-function preferencesAdditions(controller) {
-  var mainTabs = controller.window.document.getAnonymousElementByAttribute(controller.window.document.documentElement, 'anonid', 'selector');
-  controller.tabs = {};
-  for (var i = 0; i < mainTabs.childNodes.length; i++) {
-    var node  = mainTabs.childNodes[i];
-    var obj = {'button':node}
-    controller.tabs[i] = obj;
-    var label = node.attributes.item('label').value.replace('pane', '');
-    controller.tabs[label] = obj;
-  }
-  controller.prototype.__defineGetter__("activeTabButton",
-    function () {return mainTabs.getElementsByAttribute('selected', true)[0];
-  })
-}
-
-function Tabs (controller) {
-  this.controller = controller;
-}
-Tabs.prototype.getTab = function(index) {
-  return this.controller.window.gBrowser.browsers[index].contentDocument;
-}
-Tabs.prototype.__defineGetter__("activeTab", function() {
-  return this.controller.window.gBrowser.selectedBrowser.contentDocument;
-})
-Tabs.prototype.selectTab = function(index) {
-  // GO in to tab manager and grab the tab by index and call focus.
-}
-Tabs.prototype.findWindow = function (doc) {
-  for (var i = 0; i <= (this.controller.window.frames.length - 1); i++) {
-    if (this.controller.window.frames[i].document == doc) {
-      return this.controller.window.frames[i];
-    }
-  }
-  throw new Error("Cannot find window for document. Doc title == " + doc.title);
-}
-Tabs.prototype.getTabWindow = function(index) {
-  return this.findWindow(this.getTab(index));
-}
-Tabs.prototype.__defineGetter__("activeTabWindow", function () {
-  return this.findWindow(this.activeTab);
-})
-Tabs.prototype.__defineGetter__("length", function () {
-  return this.controller.window.gBrowser.browsers.length;
-})
-Tabs.prototype.__defineGetter__("activeTabIndex", function() {
-  return this.controller.window.gBrowser.tabContainer.selectedIndex;
-})
-Tabs.prototype.selectTabIndex = function(i) {
-  this.controller.window.gBrowser.selectTabAtIndex(i);
-}
-
-function browserAdditions (controller) {
-  controller.tabs = new Tabs(controller);
-
-  controller.waitForPageLoad = function(aDocument, aTimeout, aInterval) {
-    var timeout = aTimeout || 30000;
-    var owner;
-
-    // If a user tries to do waitForPageLoad(2000), this will assign the
-    // interval the first arg which is most likely what they were expecting
-    if (typeof(aDocument) == "number"){
-      timeout = aDocument;
-    }
-
-    // If the document is a tab find the corresponding browser element.
-    // Otherwise we have to handle an embedded web page.
-    if (aDocument && typeof(aDocument) == "object") {
-      owner = this.window.gBrowser.getBrowserForDocument(aDocument);
-
-      if (!owner) {
-        // If the document doesn't belong to a tab it will be a
-        // HTML element (e.g. iframe) embedded inside a tab.
-        // In such a case use the default window of the document.
-        owner = aDocument.defaultView;
-       }
-     }
-
-    // If no owner has been specified, fallback to the selected tab browser
-    owner = owner || this.window.gBrowser.selectedBrowser;
-
-    // Wait until the content in the tab has been loaded
-    this.waitFor(function() {
-        return this.isLoaded(owner);
-    }, "controller.waitForPageLoad(): Timeout waiting for page loaded.",
-       timeout, aInterval, this);
-    frame.events.pass({'function':'controller.waitForPageLoad()'});
-  }
-}
-
-controllerAdditions = {
-  'Browser:Preferences':preferencesAdditions,
-  'navigator:browser'  :browserAdditions,
-}
-
-/**
- *  DEPRECATION WARNING
- *
- * The following methods have all been DEPRECATED as of Mozmill 2.0
- * Use the MozMillElement object instead (https://developer.mozilla.org/en/Mozmill/Mozmill_Element_Object)
- */
-MozMillController.prototype.select = function (elem, index, option, value) {
-  return elem.select(index, option, value);
-};
-
-MozMillController.prototype.keypress = function(aTarget, aKey, aModifiers, aExpectedEvent) {
-  return aTarget.keypress(aKey, aModifiers, aExpectedEvent);
-}
-
-MozMillController.prototype.type = function (aTarget, aText, aExpectedEvent) {
-  return aTarget.sendKeys(aText, aExpectedEvent);
-}
-
-MozMillController.prototype.mouseEvent = function(aTarget, aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
-  return aTarget.mouseEvent(aOffsetX, aOffsetY, aEvent, aExpectedEvent);
-}
-
-MozMillController.prototype.click = function(elem, left, top, expectedEvent) {
-  return elem.click(left, top, expectedEvent);
-}
-
-MozMillController.prototype.doubleClick = function(elem, left, top, expectedEvent) {
-  return elem.doubleClick(left, top, expectedEvent);
-}
-
-MozMillController.prototype.mouseDown = function (elem, button, left, top, expectedEvent) {
-  return elem.mouseDown(button, left, top, expectedEvent);
-};
-
-MozMillController.prototype.mouseOut = function (elem, button, left, top, expectedEvent) {
-  return elem.mouseOut(button, left, top, expectedEvent);
-};
-
-MozMillController.prototype.mouseOver = function (elem, button, left, top, expectedEvent) {
-  return elem.mouseOver(button, left, top, expectedEvent);
-};
-
-MozMillController.prototype.mouseUp = function (elem, button, left, top, expectedEvent) {
-  return elem.mouseUp(button, left, top, expectedEvent);
-};
-
-MozMillController.prototype.middleClick = function(elem, left, top, expectedEvent) {
-  return elem.middleClick(elem, left, top, expectedEvent);
-}
-
-MozMillController.prototype.rightClick = function(elem, left, top, expectedEvent) {
-  return elem.rightClick(left, top, expectedEvent);
-}
-
-MozMillController.prototype.check = function(elem, state) {
-  return elem.check(state);
-}
-
-MozMillController.prototype.radio = function(elem) {
-  return elem.select();
-}
-
-MozMillController.prototype.waitThenClick = function (elem, timeout, interval) {
-  return elem.waitThenClick(timeout, interval);
-}
-
-MozMillController.prototype.waitForElement = function(elem, timeout, interval) {
-  return elem.waitForElement(timeout, interval);
-}
-
-MozMillController.prototype.waitForElementNotPresent = function(elem, timeout, interval) {
-  return elem.waitForElementNotPresent(timeout, interval);
-}
-
new file mode 100644
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/modules/driver.js
@@ -0,0 +1,290 @@
+/* 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/. */
+
+/**
+ * @namespace Defines the Mozmill driver for global actions
+ */
+var driver = exports;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+// Temporarily include utils module to re-use sleep
+var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
+var mozmill = {}; Cu.import("resource://mozmill/driver/mozmill.js", mozmill);
+var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
+
+/**
+ * Gets the topmost browser window. If there are none at that time, optionally
+ * opens one. Otherwise will raise an exception if none are found.
+ *
+ * @memberOf driver
+ * @param {Boolean] [aOpenIfNone=true] Open a new browser window if none are found.
+ * @returns {DOMWindow}
+ */
+function getBrowserWindow(aOpenIfNone) {
+  // Set default
+  if (typeof aOpenIfNone === 'undefined') {
+    aOpenIfNone = true;
+  }
+
+  // If implicit open is off, turn on strict checking, and vice versa.
+  let win = getTopmostWindowByType("navigator:browser", !aOpenIfNone);
+
+  // Can just assume automatic open here. If we didn't want it and nothing found,
+  // we already raised above when getTopmostWindow was called.
+  if (!win)
+    win = openBrowserWindow();
+
+  return win;
+}
+
+
+/**
+ * Retrieves the hidden window on OS X
+ *
+ * @memberOf driver
+ * @returns {DOMWindow} The hidden window
+ */
+function getHiddenWindow() {
+  return Services.appShell.hiddenDOMWindow;
+}
+
+
+/**
+ * Opens a new browser window
+ *
+ * @memberOf driver
+ * @returns {DOMWindow}
+ */
+function openBrowserWindow() {
+  // On OS X we have to be able to create a new browser window even with no other
+  // window open. Therefore we have to use the hidden window. On other platforms
+  // at least one remaining browser window has to exist.
+  var win = mozmill.isMac ? getHiddenWindow() :
+                            getTopmostWindowByType("navigator:browser", true);
+  return win.OpenBrowserWindow();
+}
+
+
+/**
+ * Pause the test execution for the given amount of time
+ *
+ * @type utils.sleep
+ * @memberOf driver
+ */
+var sleep = utils.sleep;
+
+/**
+ * Wait until the given condition via the callback returns true.
+ *
+ * @type utils.waitFor
+ * @memberOf driver
+ */
+var waitFor = assertions.Assert.waitFor;
+
+//
+// INTERNAL WINDOW ENUMERATIONS
+//
+
+/**
+ * Internal function to build a list of DOM windows using a given enumerator
+ * and filter.
+ *
+ * @private
+ * @memberOf driver
+ * @param {nsISimpleEnumerator} aEnumerator Window enumerator to use.
+ * @param {Function} [aFilterCallback] Function which is used to filter windows.
+ * @param {Boolean} [aStrict=true] Throw an error if no windows found
+ *
+ * @returns {DOMWindow[]} The windows found, in the same order as the enumerator.
+ */
+function _getWindows(aEnumerator, aFilterCallback, aStrict) {
+  // Set default
+  if (typeof aStrict === 'undefined')
+    aStrict = true;
+
+  let windows = [];
+
+  while (aEnumerator.hasMoreElements()) {
+    let window = aEnumerator.getNext();
+
+    if (!aFilterCallback || aFilterCallback(window)) {
+      windows.push(window);
+    }
+  }
+
+  // If this list is empty and we're strict, throw an error
+  if (windows.length === 0 && aStrict) {
+    var message = 'No windows were found';
+
+    // We'll throw a more detailed error if a filter was used.
+    if (aFilterCallback && aFilterCallback.name)
+      message += ' using filter "' + aFilterCallback.name + '"';
+
+    throw new Error(message);
+  }
+
+  return windows;
+}
+
+//
+// FILTER CALLBACKS
+//
+
+/**
+ * Generator of a closure to filter a window based by a method
+ *
+ * @memberOf driver
+ * @param {String} aName Name of the method in the window object.
+ * @returns {Boolean} True if the condition is met.
+ */
+function windowFilterByMethod(aName) {
+  return function byMethod(aWindow) { return (aName in aWindow); }
+}
+
+
+/**
+ * Generator of a closure to filter a window based by the its title
+ *
+ * @param {String} aTitle Title of the window.
+ * @returns {Boolean} True if the condition is met.
+ */
+function windowFilterByTitle(aTitle) {
+  return function byTitle(aWindow) { return (aWindow.document.title === aTitle); }
+}
+
+
+/**
+ * Generator of a closure to filter a window based by the its type
+ *
+ * @memberOf driver
+ * @param {String} aType Type of the window.
+ * @returns {Boolean} True if the condition is met.
+ */
+function windowFilterByType(aType) {
+  return function byType(aWindow) {
+           var type = aWindow.document.documentElement.getAttribute("windowtype");
+           return (type === aType);
+         }
+}
+
+//
+// WINDOW LIST RETRIEVAL FUNCTIONS
+//
+
+/**
+ * Retrieves a sorted list of open windows based on their age (newest to oldest),
+ * optionally matching filter criteria.
+ *
+ * @memberOf driver
+ * @param {Function} [aFilterCallback] Function which is used to filter windows.
+ * @param {Boolean} [aStrict=true] Throw an error if no windows found
+ *
+ * @returns {DOMWindow[]} List of windows.
+ */
+function getWindowsByAge(aFilterCallback, aStrict) {
+  var windows = _getWindows(Services.wm.getEnumerator(""),
+                            aFilterCallback, aStrict);
+
+  // Reverse the list, since naturally comes back old->new
+  return windows.reverse();
+}
+
+
+/**
+ * Retrieves a sorted list of open windows based on their z order (topmost first),
+ * optionally matching filter criteria.
+ *
+ * @memberOf driver
+ * @param {Function} [aFilterCallback] Function which is used to filter windows.
+ * @param {Boolean} [aStrict=true] Throw an error if no windows found
+ *
+ * @returns {DOMWindow[]} List of windows.
+ */
+function getWindowsByZOrder(aFilterCallback, aStrict) {
+  return _getWindows(Services.wm.getZOrderDOMWindowEnumerator("", true),
+                     aFilterCallback, aStrict);
+}
+
+//
+// SINGLE WINDOW RETRIEVAL FUNCTIONS
+//
+
+/**
+ * Retrieves the last opened window, optionally matching filter criteria.
+ *
+ * @memberOf driver
+ * @param {Function} [aFilterCallback] Function which is used to filter windows.
+ * @param {Boolean} [aStrict=true] If true, throws error if no window found.
+ *
+ * @returns {DOMWindow} The window, or null if none found and aStrict == false
+ */
+function getNewestWindow(aFilterCallback, aStrict) {
+  var windows = getWindowsByAge(aFilterCallback, aStrict);
+  return windows.length ? windows[0] : null;
+}
+
+/**
+ * Retrieves the topmost window, optionally matching filter criteria.
+ *
+ * @memberOf driver
+ * @param {Function} [aFilterCallback] Function which is used to filter windows.
+ * @param {Boolean} [aStrict=true] If true, throws error if no window found.
+ *
+ * @returns {DOMWindow} The window, or null if none found and aStrict == false
+ */
+function getTopmostWindow(aFilterCallback, aStrict) {
+  var windows = getWindowsByZOrder(aFilterCallback, aStrict);
+  return windows.length ? windows[0] : null;
+}
+
+
+/**
+ * Retrieves the topmost window given by the window type
+ *
+ * XXX: Bug 462222
+ *      This function has to be used instead of getTopmostWindow until the
+ *      underlying platform bug has been fixed.
+ *
+ * @memberOf driver
+ * @param {String} [aWindowType=null] Window type to query for
+ * @param {Boolean} [aStrict=true] Throw an error if no windows found
+ *
+ * @returns {DOMWindow} The window, or null if none found and aStrict == false
+ */
+function getTopmostWindowByType(aWindowType, aStrict) {
+  if (typeof aStrict === 'undefined')
+    aStrict = true;
+
+  var win = Services.wm.getMostRecentWindow(aWindowType);
+
+  if (win === null && aStrict) {
+    var message = 'No windows of type "' + aWindowType + '" were found';
+    throw new errors.UnexpectedError(message);
+  }
+
+  return win;
+}
+
+
+// Export of functions
+driver.getBrowserWindow = getBrowserWindow;
+driver.getHiddenWindow = getHiddenWindow;
+driver.openBrowserWindow = openBrowserWindow;
+driver.sleep = sleep;
+driver.waitFor = waitFor;
+
+driver.windowFilterByMethod = windowFilterByMethod;
+driver.windowFilterByTitle = windowFilterByTitle;
+driver.windowFilterByType = windowFilterByType;
+
+driver.getWindowsByAge = getWindowsByAge;
+driver.getNewestWindow = getNewestWindow;
+driver.getTopmostWindowByType = getTopmostWindowByType;
+
+
+// XXX Bug: 462222
+//     Currently those functions cannot be used. So they shouldn't be exported.
+//driver.getWindowsByZOrder = getWindowsByZOrder;
+//driver.getTopmostWindow = getTopmostWindow;
deleted file mode 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/elementslib.js
+++ /dev/null
@@ -1,444 +0,0 @@
-/* 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/. */
-
-var EXPORTED_SYMBOLS = ["Elem", "ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
-                        "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
-                       ];
-
-var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
-var strings = {}; Components.utils.import('resource://mozmill/stdlib/strings.js', strings);
-var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
-var json2 = {}; Components.utils.import('resource://mozmill/stdlib/json2.js', json2);
-var withs = {}; Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
-var dom = {}; Components.utils.import('resource://mozmill/stdlib/dom.js', dom);
-var objects = {}; Components.utils.import('resource://mozmill/stdlib/objects.js', objects);
-
-var countQuotes = function(str){
-  var count = 0;
-  var i = 0;
-  while(i < str.length) {
-    i = str.indexOf('"', i);
-    if (i != -1) {
-      count++;
-      i++;
-    } else {
-      break;
-    }
-  }
-  return count;
-};
-
-/**
- * smartSplit()
- *
- * Takes a lookup string as input and returns
- * a list of each node in the string
- */
-var smartSplit = function (str) {
-  // Ensure we have an even number of quotes
-  if (countQuotes(str) % 2 != 0) {
-    throw new Error ("Invalid Lookup Expression");
-  }
-
-  /**
-   * This regex matches a single "node" in a lookup string.
-   * In otherwords, it matches the part between the two '/'s
-   *
-   * Regex Explanation:
-   * \/ - start matching at the first forward slash
-   * ([^\/"]*"[^"]*")* - match as many pairs of quotes as possible until we hit a slash (ignore slashes inside quotes)
-   * [^\/]* - match the remainder of text outside of last quote but before next slash
-   */
-  var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
-  var ret = []
-  var match = re.exec(str);
-  while (match != null) {
-    ret.push(match[0].replace(/^\//, ""));
-    match = re.exec(str);
-  }
-  return ret;
-};
-
-/**
- * defaultDocuments()
- *
- * Returns a list of default documents in which to search for elements
- * if no document is provided
- */
-function defaultDocuments() {
-  var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService(Components.interfaces.nsIWindowMediator);
-  win = windowManager.getMostRecentWindow("navigator:browser");
-  return [win.gBrowser.selectedBrowser.contentDocument, win.document];
-};
-
-/**
- * nodeSearch()
- *
- * Takes an optional document, callback and locator string
- * Returns a handle to the located element or null
- */
-function nodeSearch(doc, func, string) {
-  if (doc != undefined) {
-    var documents = [doc];
-  } else {
-    var documents = defaultDocuments();
-  }
-  var e = null;
-  var element = null;
-  //inline function to recursively find the element in the DOM, cross frame.
-  var search = function(win, func, string) {
-    if (win == null)
-      return;
-
-    //do the lookup in the current window
-    element = func.call(win, string);
-
-    if (!element || (element.length == 0)) {
-      var frames = win.frames;
-      for (var i=0; i < frames.length; i++) {
-        search(frames[i], func, string);
-      }
-    }
-    else { e = element; }
-  };
-
-  for (var i = 0; i < documents.length; ++i) {
-    var win = documents[i].defaultView;
-    search(win, func, string);
-    if (e) break;
-  }
-  return e;
-};
-
-/**
- * Selector()
- *
- * Finds an element by selector string
- */
-function Selector(_document, selector, index) {
-  if (selector == undefined) {
-    throw new Error('Selector constructor did not recieve enough arguments.');
-  }
-  this.selector = selector;
-  this.getNodeForDocument = function (s) {
-    return this.document.querySelectorAll(s);
-  };
-  var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
-  return nodes ? nodes[index || 0] : null;
-};
-
-/**
- * ID()
- *
- * Finds an element by ID
- */
-function ID(_document, nodeID) {
-  if (nodeID == undefined) {
-    throw new Error('ID constructor did not recieve enough arguments.');
-  }
-  this.getNodeForDocument = function (nodeID) {
-    return this.document.getElementById(nodeID);
-  };
-  return nodeSearch(_document, this.getNodeForDocument, nodeID);
-};
-
-/**
- * Link()
- *
- * Finds a link by innerHTML
- */
-function Link(_document, linkName) {
-  if (linkName == undefined) {
-    throw new Error('Link constructor did not recieve enough arguments.');
-  }
-
-  this.getNodeForDocument = function (linkName) {
-    var getText = function(el){
-      var text = "";
-      if (el.nodeType == 3){ //textNode
-        if (el.data != undefined){
-          text = el.data;
-        } else {
-          text = el.innerHTML;
-        }
-      text = text.replace(/n|r|t/g, " ");
-      }
-      if (el.nodeType == 1){ //elementNode
-        for (var i = 0; i < el.childNodes.length; i++) {
-          var child = el.childNodes.item(i);
-          text += getText(child);
-        }
-        if (el.tagName == "P" || el.tagName == "BR" || el.tagName == "HR" || el.tagName == "DIV") {
-          text += "n";
-        }
-      }
-      return text;
-    };
-
-    //sometimes the windows won't have this function
-    try {
-      var links = this.document.getElementsByTagName('a'); }
-    catch(err){ // ADD LOG LINE mresults.write('Error: '+ err, 'lightred');
-    }
-    for (var i = 0; i < links.length; i++) {
-      var el = links[i];
-      //if (getText(el).indexOf(this.linkName) != -1) {
-      if (el.innerHTML.indexOf(linkName) != -1){
-        return el;
-      }
-    }
-    return null;
-  };
-
-  return nodeSearch(_document, this.getNodeForDocument, linkName);
-};
-
-/**
- * XPath()
- *
- * Finds an element by XPath
- */
-function XPath(_document, expr) {
-  if (expr == undefined) {
-    throw new Error('XPath constructor did not recieve enough arguments.');
-  }
-
-  this.getNodeForDocument = function (s) {
-    var aNode = this.document;
-    var aExpr = s;
-    var xpe = null;
-
-    if (this.document.defaultView == null) {
-      xpe = new getMethodInWindows('XPathEvaluator')();
-    } else {
-      xpe = new this.document.defaultView.XPathEvaluator();
-    }
-    var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement : aNode.ownerDocument.documentElement);
-    var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null);
-    var found = [];
-    var res;
-    while (res = result.iterateNext())
-      found.push(res);
-    return found[0];
-  };
-  return nodeSearch(_document, this.getNodeForDocument, expr);
-};
-
-/**
- * Name()
- *
- * Finds an element by Name
- */
-function Name(_document, nName) {
-  if (nName == undefined) {
-    throw new Error('Name constructor did not recieve enough arguments.');
-  }
-  this.getNodeForDocument = function (s) {
-    try{
-      var els = this.document.getElementsByName(s);
-      if (els.length > 0) { return els[0]; }
-    }
-    catch(err){};
-    return null;
-  };
-  return nodeSearch(_document, this.getNodeForDocument, nName);
-};
-
-
-var _returnResult = function (results) {
-  if (results.length == 0) {
-    return null
-  } else if (results.length == 1) {
-    return results[0];
-  } else {
-    return results;
-  }
-}
-var _forChildren = function (element, name, value) {
-  var results = [];
-  var nodes = [e for each (e in element.childNodes) if (e)]
-  for (var i in nodes) {
-    var n = nodes[i];
-    if (n[name] == value) {
-      results.push(n);
-    }
-  }
-  return results;
-}
-var _forAnonChildren = function (_document, element, name, value) {
-  var results = [];
-  var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)];
-  for (var i in nodes ) {
-    var n = nodes[i];
-    if (n[name] == value) {
-      results.push(n);
-    }
-  }
-  return results;
-}
-var _byID = function (_document, parent, value) {
-  return _returnResult(_forChildren(parent, 'id', value));
-}
-var _byName = function (_document, parent, value) {
-  return _returnResult(_forChildren(parent, 'tagName', value));
-}
-var _byAttrib = function (parent, attributes) {
-  var results = [];
-
-  var nodes = parent.childNodes;
-  for (var i in nodes) {
-    var n = nodes[i];
-    requirementPass = 0;
-    requirementLength = 0;
-    for (var a in attributes) {
-      requirementLength++;
-      try {
-        if (n.getAttribute(a) == attributes[a]) {
-          requirementPass++;
-        }
-      } catch (err) {
-        // Workaround any bugs in custom attribute crap in XUL elements
-      }
-    }
-    if (requirementPass == requirementLength) {
-      results.push(n);
-    }
-  }
-  return _returnResult(results)
-}
-var _byAnonAttrib = function (_document, parent, attributes) {
-  var results = [];
-
-  if (objects.getLength(attributes) == 1) {
-    for (var i in attributes) {var k = i; var v = attributes[i]; }
-    var result = _document.getAnonymousElementByAttribute(parent, k, v)
-    if (result) {
-      return result;
-
-    }
-  }
-  var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)];
-  function resultsForNodes (nodes) {
-    for (var i in nodes) {
-      var n = nodes[i];
-      requirementPass = 0;
-      requirementLength = 0;
-      for (var a in attributes) {
-        requirementLength++;
-        if (n.getAttribute(a) == attributes[a]) {
-          requirementPass++;
-        }
-      }
-      if (requirementPass == requirementLength) {
-        results.push(n);
-      }
-    }
-  }
-  resultsForNodes(nodes)
-  if (results.length == 0) {
-    resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)])
-  }
-  return _returnResult(results)
-}
-var _byIndex = function (_document, parent, i) {
-  if (parent instanceof Array) {
-    return parent[i];
-  }
-  return parent.childNodes[i];
-}
-var _anonByName = function (_document, parent, value) {
-  return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
-}
-var _anonByAttrib = function (_document, parent, value) {
-  return _byAnonAttrib(_document, parent, value);
-}
-var _anonByIndex = function (_document, parent, i) {
-  return _document.getAnonymousNodes(parent)[i];
-}
-
-/**
- * Lookup()
- *
- * Finds an element by Lookup expression
- */
-function Lookup (_document, expression) {
-  if (expression == undefined) {
-    throw new Error('Lookup constructor did not recieve enough arguments.');
-  }
-
-  var expSplit = [e for each (e in smartSplit(expression) ) if (e != '')];
-  expSplit.unshift(_document)
-  var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
-  var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
-
-
-  var reduceLookup = function (parent, exp) {
-    // Handle case where only index is provided
-    var cases = nCases;
-
-    // Handle ending index before any of the expression gets mangled
-    if (withs.endsWith(exp, ']')) {
-      var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
-    }
-    // Handle anon
-    if (withs.startsWith(exp, 'anon')) {
-      var exp = strings.vslice(exp, '(', ')');
-      var cases = aCases;
-    }
-    if (withs.startsWith(exp, '[')) {
-      try {
-        var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
-      } catch (err) {
-        throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '[', ']')+' ||');
-      }
-      var r = cases['index'](_document, parent, obj);
-      if (r == null) {
-        throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
-      }
-      return r;
-    }
-
-    for (var c in cases) {
-      if (withs.startsWith(exp, c)) {
-        try {
-          var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
-        } catch(err) {
-           throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '(', ')')+'  ||');
-        }
-        var result = cases[c](_document, parent, obj);
-      }
-    }
-
-    if (!result) {
-      if ( withs.startsWith(exp, '{') ) {
-        try {
-          var obj = json2.JSON.parse(exp)
-        } catch(err) {
-          throw new Error(err+'. String to be parsed was || '+exp+' ||');
-        }
-
-        if (cases == aCases) {
-          var result = _anonByAttrib(_document, parent, obj)
-        } else {
-          var result = _byAttrib(parent, obj)
-        }
-      }
-      if (!result) {
-        throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
-      }
-    }
-
-    // Final return
-    if (expIndex) {
-      // TODO: Check length and raise error
-      return result[expIndex];
-    } else {
-      // TODO: Check length and raise error
-      return result;
-    }
-    // Maybe we should cause an exception here
-    return false;
-  };
-  return expSplit.reduce(reduceLookup);
-};
new file mode 100644
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/modules/errors.js
@@ -0,0 +1,102 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ['BaseError',
+                        'ApplicationQuitError',
+                        'AssertionError',
+                        'TimeoutError'];
+
+
+/**
+ * Creates a new instance of a base error
+ *
+ * @class Represents the base for custom errors
+ * @param {string} [aMessage=Error().message]
+ *        The error message to show
+ * @param {string} [aFileName=Error().fileName]
+ *        The file name where the error has been raised
+ * @param {string} [aLineNumber=Error().lineNumber]
+ *        The line number of the file where the error has been raised
+ * @param {string} [aFunctionName=undefined]
+ *        The function name in which the error has been raised
+ */
+function BaseError(aMessage, aFileName, aLineNumber, aFunctionName) {
+  this.name = this.constructor.name;
+
+  var err = new Error();
+  if (err.stack) {
+    this.stack = err.stack;
+  }
+
+  this.message = aMessage || err.message;
+  this.fileName = aFileName || err.fileName;
+  this.lineNumber = aLineNumber || err.lineNumber;
+  this.functionName = aFunctionName;
+}
+
+
+/**
+ * Creates a new instance of an application quit error used by Mozmill to
+ * indicate that the application is going to shutdown
+ *
+ * @class Represents an error object thrown when the application is going to shutdown
+ * @param {string} [aMessage=Error().message]
+ *        The error message to show
+ * @param {string} [aFileName=Error().fileName]
+ *        The file name where the error has been raised
+ * @param {string} [aLineNumber=Error().lineNumber]
+ *        The line number of the file where the error has been raised
+ * @param {string} [aFunctionName=undefined]
+ *        The function name in which the error has been raised
+ */
+function ApplicationQuitError(aMessage, aFileName, aLineNumber, aFunctionName) {
+  BaseError.apply(this, arguments);
+}
+
+ApplicationQuitError.prototype = Object.create(BaseError.prototype, {
+  constructor : { value : ApplicationQuitError }
+});
+
+
+/**
+ * Creates a new instance of an assertion error
+ *
+ * @class Represents an error object thrown by failing assertions
+ * @param {string} [aMessage=Error().message]
+ *        The error message to show
+ * @param {string} [aFileName=Error().fileName]
+ *        The file name where the error has been raised
+ * @param {string} [aLineNumber=Error().lineNumber]
+ *        The line number of the file where the error has been raised
+ * @param {string} [aFunctionName=undefined]
+ *        The function name in which the error has been raised
+ */
+function AssertionError(aMessage, aFileName, aLineNumber, aFunctionName) {
+  BaseError.apply(this, arguments);
+}
+
+AssertionError.prototype = Object.create(BaseError.prototype, {
+  constructor : { value : AssertionError }
+});
+
+/**
+ * Creates a new instance of a timeout error
+ *
+ * @class Represents an error object thrown by failing assertions
+ * @param {string} [aMessage=Error().message]
+ *        The error message to show
+ * @param {string} [aFileName=Error().fileName]
+ *        The file name where the error has been raised
+ * @param {string} [aLineNumber=Error().lineNumber]
+ *        The line number of the file where the error has been raised
+ * @param {string} [aFunctionName=undefined]
+ *        The function name in which the error has been raised
+ */
+function TimeoutError(aMessage, aFileName, aLineNumber, aFunctionName) {
+  AssertionError.apply(this, arguments);
+}
+
+TimeoutError.prototype = Object.create(AssertionError.prototype, {
+  constructor : { value : TimeoutError }
+});
--- a/services/sync/tps/extensions/mozmill/resource/modules/frame.js
+++ b/services/sync/tps/extensions/mozmill/resource/modules/frame.js
@@ -1,562 +1,785 @@
 /* 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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = ['Collector','Runner','events', 'runTestFile', 'log',
+                        'timers', 'persisted', 'shutdownApplication'];
 
-var EXPORTED_SYMBOLS = ['loadFile','Collector','Runner','events',
-                        'jsbridge', 'runTestFile', 'log', 'getThread',
-                        'timers', 'persisted'];
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+const TIMEOUT_SHUTDOWN_HTTPD = 15000;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+Cu.import('resource://mozmill/stdlib/httpd.js');
 
-var httpd = {};   Components.utils.import('resource://mozmill/stdlib/httpd.js', httpd);
-var os = {};      Components.utils.import('resource://mozmill/stdlib/os.js', os);
-var strings = {}; Components.utils.import('resource://mozmill/stdlib/strings.js', strings);
-var arrays = {};  Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
-var withs = {};   Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
-var utils = {};   Components.utils.import('resource://mozmill/modules/utils.js', utils);
-var securableModule = {};  Components.utils.import('resource://mozmill/stdlib/securable-module.js', securableModule);
+var broker = {};  Cu.import('resource://mozmill/driver/msgbroker.js', broker);
+var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
+var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
+var os = {};      Cu.import('resource://mozmill/stdlib/os.js', os);
+var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings);
+var arrays = {};  Cu.import('resource://mozmill/stdlib/arrays.js', arrays);
+var withs = {};   Cu.import('resource://mozmill/stdlib/withs.js', withs);
+var utils = {};   Cu.import('resource://mozmill/stdlib/utils.js', utils);
 
-var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"].
-     getService(Components.interfaces.nsIConsoleService);
-var ios = Components.classes["@mozilla.org/network/io-service;1"]
-                    .getService(Components.interfaces.nsIIOService);
-var subscriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
-                    .getService(Components.interfaces.mozIJSSubScriptLoader);
-var uuidgen = Components.classes["@mozilla.org/uuid-generator;1"]
-                    .getService(Components.interfaces.nsIUUIDGenerator);
+var securableModule = {};
+Cu.import('resource://mozmill/stdlib/securable-module.js', securableModule);
 
+var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+
+var httpd = null;
 var persisted = {};
 
-var moduleLoader = new securableModule.Loader({
-  rootPaths: ["resource://mozmill/modules/"],
-  defaultPrincipal: "system",
-  globals : { Cc: Components.classes,
-              Ci: Components.interfaces,
-              Cu: Components.utils,
-              Cr: Components.results}
-});
+var assert = new assertions.Assert();
+
+var mozmill = undefined;
+var mozelement = undefined;
+var modules = undefined;
+
+var timers = [];
+
+
+/**
+ * Shutdown or restart the application
+ *
+ * @param {boolean} [aFlags=undefined]
+ *        Additional flags how to handle the shutdown or restart. The attributes
+ *        eRestarti386 and eRestartx86_64 have not been documented yet.
+ * @see https://developer.mozilla.org/nsIAppStartup#Attributes
+ */
+function shutdownApplication(aFlags) {
+  var flags = Ci.nsIAppStartup.eForceQuit;
 
-arrayRemove = function(array, from, to) {
-  var rest = array.slice((to || from) + 1 || array.length);
-  array.length = from < 0 ? array.length + from : from;
-  return array.push.apply(array, rest);
-};
-
-mozmill = undefined; mozelement = undefined;
+  if (aFlags) {
+    flags |= aFlags;
+  }
 
-var loadTestResources = function () {
-  // load resources we want in our tests
-  if (mozmill == undefined) {
-    mozmill = {};
-    Components.utils.import("resource://mozmill/modules/mozmill.js", mozmill);
+  // Send a request to shutdown the application. That will allow us and other
+  // components to finish up with any shutdown code. Please note that we don't
+  // care if other components or add-ons want to prevent this via cancelQuit,
+  // we really force the shutdown.
+  let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
+                   createInstance(Components.interfaces.nsISupportsPRBool);
+  Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null);
+
+  // Use a timer to trigger the application restart, which will allow us to
+  // send an ACK packet via jsbridge if the method has been called via Python.
+  var event = {
+    notify: function(timer) {
+      Services.startup.quit(flags);
+    }
   }
-  if (mozelement == undefined) {
-    mozelement = {};
-    Components.utils.import("resource://mozmill/modules/mozelement.js", mozelement);
-  }
+
+  var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+  timer.initWithCallback(event, 100, Ci.nsITimer.TYPE_ONE_SHOT);
 }
 
-var loadFile = function(path, collector) {
-  // load a test module from a file and add some candy
-  var file = Components.classes["@mozilla.org/file/local;1"]
-                       .createInstance(Components.interfaces.nsILocalFile);
-  file.initWithPath(path);
-  var uri = ios.newFileURI(file).spec;
-
-  loadTestResources();
-  var assertions = moduleLoader.require("./assertions");
-  var module = {
-    collector:  collector,
-    mozmill: mozmill,
-    elementslib: mozelement,
-    findElement: mozelement,
-    persisted: persisted,
-    Cc: Components.classes,
-    Ci: Components.interfaces,
-    Cu: Components.utils,
-    Cr: Components.results,
-    log: log,
-    assert: new assertions.Assert(),
-    expect: new assertions.Expect()
-  }
-
-  module.require = function (mod) {
-    var loader = new securableModule.Loader({
-      rootPaths: [ios.newFileURI(file.parent).spec,
-                  "resource://mozmill/modules/"],
-      defaultPrincipal: "system",
-      globals : { mozmill: mozmill,
-                  elementslib: mozelement,      // This a quick hack to maintain backwards compatibility with 1.5.x
-                  findElement: mozelement,
-                  persisted: persisted,
-                  Cc: Components.classes,
-                  Ci: Components.interfaces,
-                  Cu: Components.utils,
-                  log: log }
-    });
-    return loader.require(mod);
-  }
-
-  if (collector != undefined) {
-    collector.current_file = file;
-    collector.current_path = path;
-  }
-  try {
-    subscriptLoader.loadSubScript(uri, module, "UTF-8");
-  } catch(e) {
-    events.fail(e);
-    var obj = {
-      'filename':path,
-      'passed':false,
-      'failed':true,
-      'passes':0,
-      'fails' :1,
-      'name'  :'Unknown Test',
-    };
-    events.fireEvent('endTest', obj);
-    Components.utils.reportError(e);
-  }
-
-  module.__file__ = path;
-  module.__uri__ = uri;
-  return module;
-}
-
-function stateChangeBase (possibilties, restrictions, target, cmeta, v) {
+function stateChangeBase(possibilties, restrictions, target, cmeta, v) {
   if (possibilties) {
     if (!arrays.inArray(possibilties, v)) {
       // TODO Error value not in this.poss
       return;
     }
   }
+
   if (restrictions) {
     for (var i in restrictions) {
       var r = restrictions[i];
       if (!r(v)) {
         // TODO error value did not pass restriction
         return;
       }
     }
   }
+
   // Fire jsbridge notification, logging notification, listener notifications
   events[target] = v;
   events.fireEvent(cmeta, target);
 }
 
-timers = [];
 
 var events = {
-  'currentState' : null,
-  'currentModule': null,
-  'currentTest'  : null,
-  'userShutdown' : false,
-  'appQuit'      : false,
-  'listeners'    : {},
+  appQuit           : false,
+  currentModule     : null,
+  currentState      : null,
+  currentTest       : null,
+  shutdownRequested : false,
+  userShutdown      : null,
+  userShutdownTimer : null,
+
+  listeners       : {},
+  globalListeners : []
 }
+
 events.setState = function (v) {
-   return stateChangeBase(['dependencies', 'setupModule', 'teardownModule',
-                           'setupTest', 'teardownTest', 'test', 'collection'],
-                           null, 'currentState', 'setState', v);
+  return stateChangeBase(['dependencies', 'setupModule', 'teardownModule',
+                          'test', 'setupTest', 'teardownTest', 'collection'],
+                          null, 'currentState', 'setState', v);
 }
+
 events.toggleUserShutdown = function (obj){
-  if (this.userShutdown) {
-      this.fail({'function':'frame.events.toggleUserShutdown', 'message':'Shutdown expected but none detected before timeout', 'userShutdown': obj});
+  if (!this.userShutdown) {
+    this.userShutdown = obj;
+
+    var event = {
+      notify: function(timer) {
+       events.toggleUserShutdown(obj);
+      }
+    }
+
+    this.userShutdownTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    this.userShutdownTimer.initWithCallback(event, obj.timeout, Ci.nsITimer.TYPE_ONE_SHOT);
+
+  } else {
+    this.userShutdownTimer.cancel();
+
+    // If the application is not going to shutdown, the user shutdown failed and
+    // we have to force a shutdown.
+    if (!events.appQuit) {
+      this.fail({'function':'events.toggleUserShutdown',
+                 'message':'Shutdown expected but none detected before timeout',
+                 'userShutdown': obj});
+
+      var flags = Ci.nsIAppStartup.eAttemptQuit;
+      if (events.isRestartShutdown()) {
+        flags |= Ci.nsIAppStartup.eRestart;
+      }
+
+      shutdownApplication(flags);
+    }
   }
-  this.userShutdown = obj;
 }
+
 events.isUserShutdown = function () {
-  return Boolean(this.userShutdown);
+  return this.userShutdown ? this.userShutdown["user"] : false;
+}
+
+events.isRestartShutdown = function () {
+  return this.userShutdown.restart;
 }
-events.setTest = function (test, invokedFromIDE) {
+
+events.startShutdown = function (obj) {
+  events.fireEvent('shutdown', obj);
+
+  if (obj["user"]) {
+    events.toggleUserShutdown(obj);
+  } else {
+    shutdownApplication(obj.flags);
+  }
+}
+
+events.setTest = function (test) {
+  test.__start__ = Date.now();
   test.__passes__ = [];
   test.__fails__ = [];
-  test.__invokedFromIDE__ = invokedFromIDE;
+
   events.currentTest = test;
-  test.__start__ = Date.now();
-  var obj = {'filename':events.currentModule.__file__,
-             'name':test.__name__,
-            }
+
+  var obj = {'filename': events.currentModule.__file__,
+             'name': test.__name__}
   events.fireEvent('setTest', obj);
 }
+
 events.endTest = function (test) {
+  // use the current test unless specified
+  if (test === undefined) {
+    test = events.currentTest;
+  }
+
+  // If no test is set it has already been reported. Beside that we don't want
+  // to report it a second time.
+  if (!test || test.status === 'done')
+    return;
+
   // report the end of a test
-  test.status = 'done';
-  events.currentTest = null;
   test.__end__ = Date.now();
-  var obj = {'filename':events.currentModule.__file__,
-         'passed':test.__passes__.length,
-         'failed':test.__fails__.length,
-         'passes':test.__passes__,
-         'fails' :test.__fails__,
-         'name'  :test.__name__,
-         'time_start':test.__start__,
-         'time_end':test.__end__
-         }
+  test.status = 'done';
+
+  var obj = {'filename': events.currentModule.__file__,
+             'passed': test.__passes__.length,
+             'failed': test.__fails__.length,
+             'passes': test.__passes__,
+             'fails' : test.__fails__,
+             'name'  : test.__name__,
+             'time_start': test.__start__,
+             'time_end': test.__end__}
+
   if (test.skipped) {
     obj['skipped'] = true;
     obj.skipped_reason = test.skipped_reason;
   }
+
   if (test.meta) {
     obj.meta = test.meta;
   }
 
-  // Report the test result only if the test is a true test or if it is a
-  // failing setup/teardown
-  var shouldSkipReporting = false;
-  if (test.__passes__ &&
-      (test.__name__ == 'setupModule' ||
-       test.__name__ == 'setupTest' ||
-       test.__name__ == 'teardownTest' ||
-       test.__name__ == 'teardownModule')) {
-    shouldSkipReporting = true;
-  }
-
-  if (!shouldSkipReporting) {
+  // Report the test result only if the test is a true test or if it is failing
+  if (withs.startsWith(test.__name__, "test") || test.__fails__.length > 0) {
     events.fireEvent('endTest', obj);
   }
 }
 
-events.setModule = function (v) {
-  return stateChangeBase( null, [function (v) {return (v.__file__ != undefined)}],
-                          'currentModule', 'setModule', v);
+events.setModule = function (aModule) {
+  aModule.__start__ = Date.now();
+  aModule.__status__ = 'running';
+
+  var result = stateChangeBase(null,
+                               [function (aModule) {return (aModule.__file__ != undefined)}],
+                               'currentModule', 'setModule', aModule);
+
+  return result;
+}
+
+events.endModule = function (aModule) {
+  // It should only reported once, so check if it already has been done
+  if (aModule.__status__ === 'done')
+    return;
+
+  aModule.__end__ = Date.now();
+  aModule.__status__ = 'done';
+
+  var obj = {
+    'filename': aModule.__file__,
+    'time_start': aModule.__start__,
+    'time_end': aModule.__end__
+  }
+
+  events.fireEvent('endModule', obj);
 }
 
 events.pass = function (obj) {
   // a low level event, such as a keystroke, succeeds
   if (events.currentTest) {
     events.currentTest.__passes__.push(obj);
   }
-  for each(var timer in timers) {
+
+  for each (var timer in timers) {
     timer.actions.push(
-      {"currentTest":events.currentModule.__file__+"::"+events.currentTest.__name__, "obj":obj,
-       "result":"pass"}
+      {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__,
+       "obj": obj,
+       "result": "pass"}
     );
   }
+
   events.fireEvent('pass', obj);
 }
+
 events.fail = function (obj) {
   var error = obj.exception;
-  if(error) {
+
+  if (error) {
     // Error objects aren't enumerable https://bugzilla.mozilla.org/show_bug.cgi?id=637207
     obj.exception = {
       name: error.name,
       message: error.message,
       lineNumber: error.lineNumber,
       fileName: error.fileName,
       stack: error.stack
     };
   }
+
   // a low level event, such as a keystroke, fails
   if (events.currentTest) {
     events.currentTest.__fails__.push(obj);
   }
-  for each(var time in timers) {
+
+  for each (var time in timers) {
     timer.actions.push(
-      {"currentTest":events.currentModule.__file__+"::"+events.currentTest.__name__, "obj":obj,
-       "result":"fail"}
+      {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__,
+       "obj": obj,
+       "result": "fail"}
     );
   }
+
   events.fireEvent('fail', obj);
 }
+
 events.skip = function (reason) {
-  // this is used to report skips associated with setupModule and setupTest
-  // and nothing else
+  // this is used to report skips associated with setupModule and nothing else
   events.currentTest.skipped = true;
   events.currentTest.skipped_reason = reason;
-  for each(var timer in timers) {
+
+  for (var timer of timers) {
     timer.actions.push(
-      {"currentTest":events.currentModule.__file__+"::"+events.currentTest.__name__, "obj":reason,
-       "result":"skip"}
+      {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__,
+       "obj": reason,
+       "result": "skip"}
     );
   }
+
   events.fireEvent('skip', reason);
 }
+
 events.fireEvent = function (name, obj) {
+  if (events.appQuit) {
+    // dump('* Event discarded: ' + name + ' ' + JSON.stringify(obj) + '\n');
+    return;
+  }
+
   if (this.listeners[name]) {
     for (var i in this.listeners[name]) {
       this.listeners[name][i](obj);
     }
   }
+
   for each(var listener in this.globalListeners) {
     listener(name, obj);
   }
 }
-events.globalListeners = [];
+
 events.addListener = function (name, listener) {
   if (this.listeners[name]) {
     this.listeners[name].push(listener);
-  } else if (name =='') {
+  } else if (name == '') {
     this.globalListeners.push(listener)
   } else {
     this.listeners[name] = [listener];
   }
 }
-events.removeListener = function(listener) {
+
+events.removeListener = function (listener) {
   for (var listenerIndex in this.listeners) {
     var e = this.listeners[listenerIndex];
+
     for (var i in e){
       if (e[i] == listener) {
-        this.listeners[listenerIndex] = arrayRemove(e, i);
+        this.listeners[listenerIndex] = arrays.remove(e, i);
       }
     }
   }
+
   for (var i in this.globalListeners) {
     if (this.globalListeners[i] == listener) {
-      this.globalListeners = arrayRemove(this.globalListeners, i);
+      this.globalListeners = arrays.remove(this.globalListeners, i);
     }
   }
 }
 
+events.persist = function () {
+  try {
+    events.fireEvent('persist', persisted);
+  } catch (e) {
+    events.fireEvent('error', "persist serialization failed.")
+  }
+}
+
+events.firePythonCallback = function (obj) {
+  obj['test'] = events.currentModule.__file__;
+  events.fireEvent('firePythonCallback', obj);
+}
+
+events.screenshot = function (obj) {
+  // Find the name of the test function
+  for (var attr in events.currentModule) {
+    if (events.currentModule[attr] == events.currentTest) {
+      var testName = attr;
+      break;
+    }
+  }
+
+  obj['test_file'] = events.currentModule.__file__;
+  obj['test_name'] = testName;
+  events.fireEvent('screenshot', obj);
+}
+
 var log = function (obj) {
   events.fireEvent('log', obj);
 }
 
+// Register the listeners
+broker.addObject({'endTest': events.endTest,
+                  'fail': events.fail,
+                  'firePythonCallback': events.firePythonCallback,
+                  'log': log,
+                  'pass': events.pass,
+                  'persist': events.persist,
+                  'screenshot': events.screenshot,
+                  'shutdown': events.startShutdown,
+                 });
+
 try {
-  var jsbridge = {}; Components.utils.import('resource://jsbridge/modules/events.js', jsbridge);
-} catch(err) {
-  var jsbridge = null;
+  Cu.import('resource://jsbridge/modules/Events.jsm');
 
-  aConsoleService.logStringMessage("jsbridge not available.");
+  events.addListener('', function (name, obj) {
+    Events.fireEvent('mozmill.' + name, obj);
+  });
+} catch (e) {
+  Services.console.logStringMessage("Event module of JSBridge not available.");
 }
 
-if (jsbridge) {
-  events.addListener('', function (name, obj) {jsbridge.fireEvent('mozmill.'+name, obj)} );
-}
 
-function Collector () {
-  // the collector handles HTTPD and initilizing the module
-  this.test_modules_by_filename = {};
-  this.testing = [];
-  this.httpd_started = false;
-  this.http_port = 43336;
-  this.http_server = httpd.getServer(this.http_port);
+/**
+ * Observer for notifications when the application is going to shutdown
+ */
+function AppQuitObserver() {
+  this.runner = null;
+
+  Services.obs.addObserver(this, "quit-application-requested", false);
 }
 
-Collector.prototype.startHttpd = function () {
-  while (this.httpd == undefined) {
-    try {
-      this.http_server.start(this.http_port);
-      this.httpd = this.http_server;
-    } catch(e) { // Failure most likely due to port conflict
-      this.http_port++;
-      this.http_server = httpd.getServer(this.http_port);
-    };
-  }
-}
-Collector.prototype.stopHttpd = function () {
-  if (this.httpd) {
-    this.httpd.stop(function(){});  // Callback needed to pause execution until the server has been properly shutdown
-    this.httpd = null;
+AppQuitObserver.prototype = {
+  observe: function (aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "quit-application-requested":
+        Services.obs.removeObserver(this, "quit-application-requested");
+
+        // If we observe a quit notification make sure to send the
+        // results of the current test. In those cases we don't reach
+        // the equivalent code in runTestModule()
+        events.pass({'message': 'AppQuitObserver: ' + JSON.stringify(aData),
+                     'userShutdown': events.userShutdown});
+
+        if (this.runner) {
+          this.runner.end();
+        }
+
+        if (httpd) {
+          httpd.stop();
+        }
+
+        events.appQuit = true;
+
+        break;
+    }
   }
 }
-Collector.prototype.addHttpResource = function (directory, ns) {
-  if (!this.httpd) {
-    this.startHttpd();
-  }
+
+var appQuitObserver = new AppQuitObserver();
 
-  if (!ns) {
-    ns = '/';
-  } else {
-    ns = '/' + ns + '/';
-  }
-
-  var lp = Components.classes["@mozilla.org/file/local;1"].
-           createInstance(Components.interfaces.nsILocalFile);
-  lp.initWithPath(os.abspath(directory, this.current_file));
-  this.httpd.registerDirectory(ns, lp);
-
-  return 'http://localhost:' + this.http_port + ns
+/**
+ * The collector handles HTTPd.js and initilizing the module
+ */
+function Collector() {
+  this.test_modules_by_filename = {};
+  this.testing = [];
 }
 
-Collector.prototype.initTestModule = function (filename, name) {
-  var test_module = loadFile(filename, this);
+Collector.prototype.addHttpResource = function (aDirectory, aPath) {
+  var fp = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+  fp.initWithPath(os.abspath(aDirectory, this.current_file));
+
+  return httpd.addHttpResource(fp, aPath);
+}
+
+Collector.prototype.initTestModule = function (filename, testname) {
+  var test_module = this.loadFile(filename, this);
+  var has_restarted = !(testname == null);
   test_module.__tests__ = [];
+
   for (var i in test_module) {
     if (typeof(test_module[i]) == "function") {
       test_module[i].__name__ = i;
-      if (i == "setupTest") {
+
+      // Only run setupModule if we are a single test OR if we are the first
+      // test of a restart chain (don't run it prior to members in a restart
+      // chain)
+      if (i == "setupModule" && !has_restarted) {
+        test_module.__setupModule__ = test_module[i];
+      } else if (i == "setupTest") {
         test_module.__setupTest__ = test_module[i];
-      } else if (i == "setupModule") {
-        test_module.__setupModule__ = test_module[i];
       } else if (i == "teardownTest") {
         test_module.__teardownTest__ = test_module[i];
       } else if (i == "teardownModule") {
         test_module.__teardownModule__ = test_module[i];
       } else if (withs.startsWith(i, "test")) {
-        if (name && (i != name)) {
-            continue;
+        if (testname && (i != testname)) {
+          continue;
         }
-        name = null;
+
+        testname = null;
         test_module.__tests__.push(test_module[i]);
       }
     }
   }
 
   test_module.collector = this;
   test_module.status = 'loaded';
+
   this.test_modules_by_filename[filename] = test_module;
+
   return test_module;
 }
 
-// Observer which gets notified when the application quits
-function AppQuitObserver() {
-  this.register();
+Collector.prototype.loadFile = function (path, collector) {
+  var moduleLoader = new securableModule.Loader({
+    rootPaths: ["resource://mozmill/modules/"],
+    defaultPrincipal: "system",
+    globals : { Cc: Cc,
+                Ci: Ci,
+                Cu: Cu,
+                Cr: Components.results}
+  });
+
+  // load a test module from a file and add some candy
+  var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+  file.initWithPath(path);
+  var uri = Services.io.newFileURI(file).spec;
+
+  this.loadTestResources();
+
+  var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+  var module = new Components.utils.Sandbox(systemPrincipal);
+  module.assert = new assertions.Assert();
+  module.Cc = Cc;
+  module.Ci = Ci;
+  module.Cr = Components.results;
+  module.Cu = Cu;
+  module.collector = collector;
+  module.driver = moduleLoader.require("driver");
+  module.elementslib = mozelement;
+  module.errors = errors;
+  module.expect = new assertions.Expect();
+  module.findElement = mozelement;
+  module.log = log;
+  module.mozmill = mozmill;
+  module.persisted = persisted;
+
+  module.require = function (mod) {
+    var loader = new securableModule.Loader({
+      rootPaths: [Services.io.newFileURI(file.parent).spec,
+                  "resource://mozmill/modules/"],
+      defaultPrincipal: "system",
+      globals : { mozmill: mozmill,
+                  elementslib: mozelement,      // This a quick hack to maintain backwards compatibility with 1.5.x
+                  findElement: mozelement,
+                  persisted: persisted,
+                  Cc: Cc,
+                  Ci: Ci,
+                  Cu: Cu,
+                  log: log }
+    });
+
+    if (modules != undefined) {
+      loader.modules = modules;
+    }
+
+    var retval = loader.require(mod);
+    modules = loader.modules;
+
+    return retval;
+  }
+
+  if (collector != undefined) {
+    collector.current_file = file;
+    collector.current_path = path;
+  }
+
+  try {
+    Services.scriptloader.loadSubScript(uri, module, "UTF-8");
+  } catch (e) {
+    var obj = {
+      'filename': path,
+      'passed': 0,
+      'failed': 1,
+      'passes': [],
+      'fails' : [{'exception' : {
+                    message: e.message,
+                    filename: e.filename,
+                    lineNumber: e.lineNumber}}],
+      'name'  :'<TOP_LEVEL>'
+    };
+
+    events.fail({'exception': e});
+    events.fireEvent('endTest', obj);
+  }
+
+  module.__file__ = path;
+  module.__uri__ = uri;
+
+  return module;
 }
-AppQuitObserver.prototype = {
-  observe: function(subject, topic, data) {
-    events.appQuit = true;
-  },
-  register: function() {
-    var obsService = Components.classes["@mozilla.org/observer-service;1"]
-                     .getService(Components.interfaces.nsIObserverService);
-    obsService.addObserver(this, "quit-application", false);
-  },
-  unregister: function() {
-    var obsService = Components.classes["@mozilla.org/observer-service;1"]
-                     .getService(Components.interfaces.nsIObserverService);
-    obsService.removeObserver(this, "quit-application");
+
+Collector.prototype.loadTestResources = function () {
+  // load resources we want in our tests
+  if (mozmill === undefined) {
+    mozmill = {};
+    Cu.import("resource://mozmill/driver/mozmill.js", mozmill);
+  }
+  if (mozelement === undefined) {
+    mozelement = {};
+    Cu.import("resource://mozmill/driver/mozelement.js", mozelement);
   }
 }
 
 
-function Runner (collector, invokedFromIDE) {
-  this.collector = collector;
-  this.invokedFromIDE = invokedFromIDE
+/**
+ *
+ */
+function Httpd(aPort) {
+  this.http_port = aPort;
+
+  while (true) {
+    try {
+      var srv = new HttpServer();
+      srv.registerContentType("sjs", "sjs");
+      srv.identity.setPrimary("http", "localhost", this.http_port);
+      srv.start(this.http_port);
+
+      this._httpd = srv;
+      break;
+    }
+    catch (e) {
+      // Failure most likely due to port conflict
+      this.http_port++;
+    }
+  }
+}
+
+Httpd.prototype.addHttpResource = function (aDir, aPath) {
+  var path = aPath ? ("/" + aPath + "/") : "/";
+
+  try {
+    this._httpd.registerDirectory(path, aDir);
+    return 'http://localhost:' + this.http_port + path;
+  }
+  catch (e) {
+    throw Error("Failure to register directory: " + aDir.path);
+  }
+};
+
+Httpd.prototype.stop = function () {
+  if (!this._httpd) {
+    return;
+  }
+
+  var shutdown = false;
+  this._httpd.stop(function () { shutdown = true; });
+
+  assert.waitFor(function () {
+    return shutdown;
+  }, "Local HTTP server has been stopped", TIMEOUT_SHUTDOWN_HTTPD);
+
+  this._httpd = null;
+};
+
+function startHTTPd() {
+  if (!httpd) {
+    // Ensure that we start the HTTP server only once during a session
+    httpd = new Httpd(43336);
+  }
+}
+
+
+function Runner() {
+  this.collector = new Collector();
+  this.ended = false;
+
+  var m = {}; Cu.import('resource://mozmill/driver/mozmill.js', m);
+  this.platform = m.platform;
+
   events.fireEvent('startRunner', true);
-  var m = {}; Components.utils.import('resource://mozmill/modules/mozmill.js', m);
-  this.platform = m.platform;
 }
 
+Runner.prototype.end = function () {
+  if (!this.ended) {
+    this.ended = true;
+
+    appQuitObserver.runner = null;
+
+    events.endTest();
+    events.endModule(events.currentModule);
+    events.fireEvent('endRunner', true);
+    events.persist();
+  }
+};
+
 Runner.prototype.runTestFile = function (filename, name) {
-  this.collector.initTestModule(filename, name);
-  this.runTestModule(this.collector.test_modules_by_filename[filename]);
-}
-Runner.prototype.end = function () {
-  try {
-    events.fireEvent('persist', persisted);
-  } catch(e) {
-    events.fireEvent('error', "persist serialization failed.");
+  var module = this.collector.initTestModule(filename, name);
+  this.runTestModule(module);
+};
+
+Runner.prototype.runTestModule = function (module) {
+  appQuitObserver.runner = this;
+  events.setModule(module);
+
+  // If setupModule passes, run all the tests. Otherwise mark them as skipped.
+  if (this.execFunction(module.__setupModule__, module)) {
+    for (var test of module.__tests__) {
+      if (events.shutdownRequested) {
+        break;
+      }
+
+      // If setupTest passes, run the test. Otherwise mark it as skipped.
+      if (this.execFunction(module.__setupTest__, module)) {
+        this.execFunction(test);
+      } else {
+        this.skipFunction(test, module.__setupTest__.__name__ + " failed");
+      }
+
+      this.execFunction(module.__teardownTest__, module);
+    }
+
+  } else {
+    for (var test of module.__tests__) {
+      this.skipFunction(test, module.__setupModule__.__name__ + " failed");
+    }
   }
-  this.collector.stopHttpd();
-  events.fireEvent('endRunner', true);
-}
+
+  this.execFunction(module.__teardownModule__, module);
+  events.endModule(module);
+};
 
-Runner.prototype.wrapper = function (func, arg) {
-  thread = Components.classes["@mozilla.org/thread-manager;1"]
-                     .getService(Components.interfaces.nsIThreadManager)
-                     .currentThread;
+Runner.prototype.execFunction = function (func, arg) {
+  if (typeof func !== "function" || events.shutdownRequested) {
+    return true;
+  }
+
+  var isTest = withs.startsWith(func.__name__, "test");
+
+  events.setState(isTest ? "test" : func.__name);
+  events.setTest(func);
 
   // skip excluded platforms
   if (func.EXCLUDED_PLATFORMS != undefined) {
     if (arrays.inArray(func.EXCLUDED_PLATFORMS, this.platform)) {
       events.skip("Platform exclusion");
-      return;
+      events.endTest(func);
+      return false;
     }
   }
 
   // skip function if requested
   if (func.__force_skip__ != undefined) {
     events.skip(func.__force_skip__);
-    return;
+    events.endTest(func);
+    return false;
   }
 
   // execute the test function
   try {
-    if (arg) {
-        func(arg);
+    func(arg);
+  } catch (e) {
+    if (e instanceof errors.ApplicationQuitError) {
+      events.shutdownRequested = true;
     } else {
-        func();
-    }
-
-    // If a user shutdown was expected but the application hasn't quit, throw a failure
-    if (events.isUserShutdown()) {
-      utils.sleep(500);  // Prevents race condition between mozrunner hard process kill and normal FFx shutdown
-      if (events.userShutdown['user'] && !events.appQuit) {
-          events.fail({'function':'Runner.wrapper',
-                       'message':'Shutdown expected but none detected before end of test',
-                       'userShutdown': events.userShutdown});
-      }
-    }
-  } catch (e) {
-    // Allow the exception if a user shutdown was expected
-    if (!events.isUserShutdown()) {
-      events.fail({'exception': e, 'test':func})
-      Components.utils.reportError(e);
+      events.fail({'exception': e, 'test': func})
     }
   }
-}
-
-Runner.prototype.runTestModule = function (module) {
-  events.setModule(module);
-  module.__status__ = 'running';
-  if (module.__setupModule__) {
-    events.setState('setupModule');
-    events.setTest(module.__setupModule__);
-    this.wrapper(module.__setupModule__, module);
-    var setupModulePassed = (events.currentTest.__fails__.length == 0 && !events.currentTest.skipped);
-    events.endTest(module.__setupModule__);
-  } else {
-    var setupModulePassed = true;
-  }
-  if (setupModulePassed) {
-    var observer = new AppQuitObserver();
-    for (var i in module.__tests__) {
-      events.appQuit = false;
-      var test = module.__tests__[i];
-
-      // TODO: introduce per-test timeout:
-      // https://bugzilla.mozilla.org/show_bug.cgi?id=574871
 
-      if (module.__setupTest__) {
-        events.setState('setupTest');
-        events.setTest(module.__setupTest__);
-        this.wrapper(module.__setupTest__, test);
-        var setupTestPassed = (events.currentTest.__fails__.length == 0 && !events.currentTest.skipped);
-        events.endTest(module.__setupTest__);
-      } else {
-        var setupTestPassed = true;
-      }
-      events.setState('test');
-      events.setTest(test, this.invokedFromIDE);
-      if (setupTestPassed) {
-        this.wrapper(test);
-        if (events.userShutdown && !events.userShutdown['user']) {
-            events.endTest(test);
-            break;
-        }
-      } else {
-        events.skip("setupTest failed.");
-      }
-      if (module.__teardownTest__) {
-        events.setState('teardownTest');
-        events.setTest(module.__teardownTest__);
-        this.wrapper(module.__teardownTest__, test);
-        events.endTest(module.__teardownTest__);
-      }
-      events.endTest(test)
-    }
-    observer.unregister();
-  } else {
-    for each(var test in module.__tests__) {
-      events.setTest(test);
-      events.skip("setupModule failed.");
-      events.endTest(test);
-    }
+  // If a user shutdown has been requested and the function already returned,
+  // we can assume that a shutdown will not happen anymore. We should force a
+  // shutdown then, to prevent the next test from being executed.
+  if (events.isUserShutdown()) {
+    events.shutdownRequested = true;
+    events.toggleUserShutdown(events.userShutdown);
   }
-  if (module.__teardownModule__) {
-    events.setState('teardownModule');
-    events.setTest(module.__teardownModule__);
-    this.wrapper(module.__teardownModule__, module);
-    events.endTest(module.__teardownModule__);
-  }
-  module.__status__ = 'done';
-}
 
-var runTestFile = function (filename, invokedFromIDE, name) {
-  var runner = new Runner(new Collector(), invokedFromIDE);
+  events.endTest(func);
+  return events.currentTest.__fails__.length == 0;
+};
+
+function runTestFile(filename, name) {
+  var runner = new Runner();
   runner.runTestFile(filename, name);
   runner.end();
+
   return true;
 }
 
-var getThread = function () {
-  return thread;
-}
+Runner.prototype.skipFunction = function (func, message) {
+  events.setTest(func);
+  events.skip(message);
+  events.endTest(func);
+};
deleted file mode 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/init.js
+++ /dev/null
@@ -1,177 +0,0 @@
-/* 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/. */
-
-var frame = {};   Components.utils.import('resource://mozmill/modules/frame.js', frame);
-
-/**
-* Console listener which listens for error messages in the console and forwards
-* them to the Mozmill reporting system for output.
-*/
-function ConsoleListener() {
- this.register();
-}
-ConsoleListener.prototype = {
- observe: function(aMessage) {
-   var msg = aMessage.message;
-   var re = /^\[.*Error:.*(chrome|resource):\/\/.*/i;
-   if (msg.match(re)) {
-     frame.events.fail(aMessage);
-   }
- },
- QueryInterface: function (iid) {
-	if (!iid.equals(Components.interfaces.nsIConsoleListener) && !iid.equals(Components.interfaces.nsISupports)) {
-		throw Components.results.NS_ERROR_NO_INTERFACE;
-   }
-   return this;
- },
- register: function() {
-   var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"]
-                              .getService(Components.interfaces.nsIConsoleService);
-   aConsoleService.registerListener(this);
- },
- unregister: function() {
-   var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"]
-                              .getService(Components.interfaces.nsIConsoleService);
-   aConsoleService.unregisterListener(this);
- }
-}
-
-// start listening
-var consoleListener = new ConsoleListener();
-
-var EXPORTED_SYMBOLS = ["mozmill"];
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-var mozmill = Cu.import('resource://mozmill/modules/mozmill.js');
-
-// Observer for new top level windows
-var windowObserver = {
-  observe: function(subject, topic, data) {
-    attachEventListeners(subject);
-  }
-};
-
-/**
- * Attach event listeners
- */
-function attachEventListeners(window) {
-  // These are the event handlers
-  function pageShowHandler(event) {
-    var doc = event.originalTarget;
-    var tab = window.gBrowser.getBrowserForDocument(doc);
-
-    if (tab) {
-      //log("*** Loaded tab: location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
-      tab.mozmillDocumentLoaded = true;
-    } else {
-      //log("*** Loaded HTML location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
-      doc.defaultView.mozmillDocumentLoaded = true;
-    }
-
-    // We need to add/remove the unload/pagehide event listeners to preserve caching.
-    window.gBrowser.addEventListener("beforeunload", beforeUnloadHandler, true);
-    window.gBrowser.addEventListener("pagehide", pageHideHandler, true);
-  };
-
-  var DOMContentLoadedHandler = function(event) {
-    var errorRegex = /about:.+(error)|(blocked)\?/;
-    if (errorRegex.exec(event.target.baseURI)) {
-      // Wait about 1s to be sure the DOM is ready
-      mozmill.utils.sleep(1000);
-
-      var tab = window.gBrowser.getBrowserForDocument(event.target);
-      if (tab)
-        tab.mozmillDocumentLoaded = true;
-
-      // We need to add/remove the unload event listener to preserve caching.
-      window.gBrowser.addEventListener("beforeunload", beforeUnloadHandler, true);
-    }
-  };
-
-  // beforeunload is still needed because pagehide doesn't fire before the page is unloaded.
-  // still use pagehide for cases when beforeunload doesn't get fired
-  function beforeUnloadHandler(event) {
-    var doc = event.originalTarget;
-    var tab = window.gBrowser.getBrowserForDocument(event.target);
-
-    if (tab) {
-      //log("*** Unload tab: location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
-      tab.mozmillDocumentLoaded = false;
-    } else {
-      //log("*** Unload HTML location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
-      doc.defaultView.mozmillDocumentLoaded = false;
-    }
-
-    window.gBrowser.removeEventListener("beforeunload", beforeUnloadHandler, true);
-  };
-
-  var pageHideHandler = function(event) {
-    // If event.persisted is false, the beforeUnloadHandler should fire
-    // and there is no need for this event handler.
-    if (event.persisted) {
-      var doc = event.originalTarget;
-      var tab = window.gBrowser.getBrowserForDocument(event.target);
-
-      if (tab) {
-        //log("*** Unload tab: location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
-        tab.mozmillDocumentLoaded = false;
-      } else {
-        //log("*** Unload HTML location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
-        doc.defaultView.mozmillDocumentLoaded = false;
-      }
-
-      window.gBrowser.removeEventListener("beforeunload", beforeUnloadHandler, true);
-    }
-
-  };
-
-  // Add the event handlers to the tabbedbrowser once its window has loaded
-  window.addEventListener("load", function(event) {
-    window.mozmillDocumentLoaded = true;
-
-
-    if (window.gBrowser) {
-      // Page is ready
-      window.gBrowser.addEventListener("pageshow", pageShowHandler, true);
-
-      // Note: Error pages will never fire a "load" event. For those we
-      // have to wait for the "DOMContentLoaded" event. That's the final state.
-      // Error pages will always have a baseURI starting with
-      // "about:" followed by "error" or "blocked".
-      window.gBrowser.addEventListener("DOMContentLoaded", DOMContentLoadedHandler, true);
-
-      // Leave page (use caching)
-      window.gBrowser.addEventListener("pagehide", pageHideHandler, true);
-    }
-  }, false);
-}
-
-/**
- * Initialize Mozmill
- */
-function initialize() {
-  // Activate observer for new top level windows
-  var observerService = Cc["@mozilla.org/observer-service;1"].
-                        getService(Ci.nsIObserverService);
-  observerService.addObserver(windowObserver, "toplevel-window-ready", false);
-
-  // Attach event listeners to all open windows
-  var enumerator = Cc["@mozilla.org/appshell/window-mediator;1"].
-                   getService(Ci.nsIWindowMediator).getEnumerator("");
-  while (enumerator.hasMoreElements()) {
-    var win = enumerator.getNext();
-    attachEventListeners(win);
-
-    // For windows or dialogs already open we have to explicitly set the property
-    // otherwise windows which load really quick never gets the property set and
-    // we fail to create the controller
-    win.mozmillDocumentLoaded = true;
-  };
-}
-
-initialize();
-
deleted file mode 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/inspection.js
+++ /dev/null
@@ -1,363 +0,0 @@
-/* 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/. */
-
-var EXPORTED_SYMBOLS = ["inspectElement"]
-
-var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
-var mozmill = {}; Components.utils.import('resource://mozmill/modules/mozmill.js', mozmill);
-var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
-
-var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
-var dom = {}; Components.utils.import('resource://mozmill/stdlib/dom.js', dom);
-var objects = {}; Components.utils.import('resource://mozmill/stdlib/objects.js', objects);
-var json2 = {}; Components.utils.import('resource://mozmill/stdlib/json2.js', json2);
-var withs = {}; Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
-
-var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-           .getService(Components.interfaces.nsIWindowMediator);
-
-var isNotAnonymous = function (elem, result) {
-  if (result == undefined) {
-    var result = true;
-  }
-  if ( elem.parentNode ) {
-    var p = elem.parentNode;
-    return isNotAnonymous(p, result == arrays.inArray(p.childNodes, elem) == true);
-  } else {
-    return result;
-  }
-}
-
-var elemIsAnonymous = function (elem) {
-  if (elem.getAttribute('anonid') || !arrays.inArray(elem.parentNode.childNodes, elem)) {
-    return true;
-  }
-  return false;
-}
-
-var getXPath = function (node, path) {
-  path = path || [];
-
-  if(node.parentNode) {
-    path = getXPath(node.parentNode, path);
-  }
-
-  if(node.previousSibling) {
-    var count = 1;
-    var sibling = node.previousSibling
-    do {
-      if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) {count++;}
-      sibling = sibling.previousSibling;
-    } while(sibling);
-    if(count == 1) {count = null;}
-  } else if(node.nextSibling) {
-    var sibling = node.nextSibling;
-    do {
-      if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) {
-        var count = 1;
-        sibling = null;
-      } else {
-        var count = null;
-        sibling = sibling.previousSibling;
-      }
-    } while(sibling);
-  }
-
-  if(node.nodeType == 1) {
- //   if ($('absXpaths').checked){
-      path.push(node.nodeName.toLowerCase() + (node.id ? "[@id='"+node.id+"']" : count > 0 ? "["+count+"]" : ''));
-  //  }
-  //  else{
-  //    path.push(node.nodeName.toLowerCase() + (node.id ? "" : count > 0 ? "["+count+"]" : ''));
-  //  }
-  }
-  return path;
-};
-
-function getXSPath(node){
-  var xpArray = getXPath(node);
-  var stringXpath = xpArray.join('/');
-  stringXpath = '/'+stringXpath;
-  stringXpath = stringXpath.replace('//','/');
-  return stringXpath;
-}
-function getXULXpath (el, xml) {
-	var xpath = '';
-	var pos, tempitem2;
-
-	while(el !== xml.documentElement) {		
-		pos = 0;
-		tempitem2 = el;
-		while(tempitem2) {
-			if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) {
-			  // If it is ELEMENT_NODE of the same name
-				pos += 1;
-			}
-			tempitem2 = tempitem2.previousSibling;
-		}
-
-		xpath = "*[name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
-
-		el = el.parentNode;
-	}
-	xpath = '/*'+"[name()='"+xml.documentElement.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']"+'/'+xpath;
-	xpath = xpath.replace(/\/$/, '');
-	return xpath;
-}
-
-var getDocument = function (elem) {
-  while (elem.parentNode) {
-    var elem = elem.parentNode;
-  }
-  return elem;
-}
-
-var getTopWindow = function(doc) {
-  return utils.getChromeWindow(doc.defaultView);
-}
-
-var attributeToIgnore = ['focus', 'focused', 'selected', 'select', 'flex', // General Omissions
-                         'linkedpanel', 'last-tab', 'afterselected', // From Tabs UI, thanks Farhad
-                         'style', // Gets set dynamically all the time, also effected by dx display code
-                         ];
-
-var getUniqueAttributesReduction = function (attributes, node) {
-  for (var i in attributes) {
-    if ( node.getAttribute(i) == attributes[i] || arrays.inArray(attributeToIgnore, i) || arrays.inArray(attributeToIgnore, attributes[i]) || i == 'id') {
-      delete attributes[i];
-    }
-  }
-  return attributes;
-}
-
-var getLookupExpression = function (_document, elem) {
-  expArray = [];
-  while ( elem.parentNode ) {
-    var exp = getLookupForElem(_document, elem);
-    expArray.push(exp);
-    var elem = elem.parentNode;
-  }
-  expArray.reverse();
-  return '/' + expArray.join('/');
-}
-
-var getLookupForElem = function (_document, elem) {
-  if ( !elemIsAnonymous(elem) ) {
-    if (elem.id != "" && !withs.startsWith(elem.id, 'panel')) {
-      identifier = {'name':'id', 'value':elem.id};
-    } else if ((elem.name != "") && (typeof(elem.name) != "undefined")) {
-      identifier = {'name':'name', 'value':elem.name};
-    } else {
-      identifier = null;
-    }
-
-    if (identifier) {
-      var result = {'id':elementslib._byID, 'name':elementslib._byName}[identifier.name](_document, elem.parentNode, identifier.value);
-      if ( typeof(result != 'array') ) {
-        return identifier.name+'('+json2.JSON.stringify(identifier.value)+')';
-      }
-    }
-
-    // At this point there is either no identifier or it returns multiple
-    var parse = [n for each (n in elem.parentNode.childNodes) if
-                 (n.getAttribute && n != elem)
-                 ];
-    parse.unshift(dom.getAttributes(elem));
-    var uniqueAttributes = parse.reduce(getUniqueAttributesReduction);
-
-    if (!result) {
-      var result = elementslib._byAttrib(elem.parentNode, uniqueAttributes);
-    }
-
-    if (!identifier && typeof(result) == 'array' ) {
-      return json2.JSON.stringify(uniqueAttributes) + '['+arrays.indexOf(result, elem)+']'
-    } else {
-      var aresult = elementslib._byAttrib(elem.parentNode, uniqueAttributes);
-      if ( typeof(aresult != 'array') ) {
-        if (objects.getLength(uniqueAttributes) == 0) {
-          return '['+arrays.indexOf(elem.parentNode.childNodes, elem)+']'
-        }
-        return json2.JSON.stringify(uniqueAttributes)
-      } else if ( result.length > aresult.length ) {
-        return json2.JSON.stringify(uniqueAttributes) + '['+arrays.indexOf(aresult, elem)+']'
-      } else {
-        return identifier.name+'('+json2.JSON.stringify(identifier.value)+')' + '['+arrays.indexOf(result, elem)+']'
-      }
-    }
-
-  } else {
-    // Handle Anonymous Nodes
-    var parse = [n for each (n in _document.getAnonymousNodes(elem.parentNode)) if
-                 (n.getAttribute && n != elem)
-                 ];
-    parse.unshift(dom.getAttributes(elem));
-    var uniqueAttributes = parse.reduce(getUniqueAttributesReduction);
-    if (uniqueAttributes.anonid && typeof(elementslib._byAnonAttrib(_document,
-        elem.parentNode, {'anonid':uniqueAttributes.anonid})) != 'array') {
-      uniqueAttributes = {'anonid':uniqueAttributes.anonid};
-    }
-
-    if (objects.getLength(uniqueAttributes) == 0) {
-      return 'anon(['+arrays.indexOf(_document.getAnonymousNodes(elem.parentNode), elem)+'])';
-    } else if (arrays.inArray(uniqueAttributes, 'anonid')) {
-      return 'anon({"anonid":"'+uniqueAttributes['anonid']+'"})';
-    } else {
-      return 'anon('+json2.JSON.stringify(uniqueAttributes)+')';
-    }
-
-  }
-  return 'broken '+elemIsAnonymous(elem)
-}
-
-var removeHTMLTags = function(str){
- 	 	str = str.replace(/&(lt|gt);/g, function (strMatch, p1){
- 		 	return (p1 == "lt")? "<" : ">";
- 		});
- 		var strTagStrippedText = str.replace(/<\/?[^>]+(>|$)/g, "");
- 		strTagStrippedText = strTagStrippedText.replace(/&nbsp;/g,"");
-	return strTagStrippedText;
-}
-
-var isMagicAnonymousDiv = function (_document, node) {
-  if (node.getAttribute && node.getAttribute('class') == 'anonymous-div') {
-    if (!arrays.inArray(node.parentNode.childNodes, node) && (_document.getAnonymousNodes(node) == null ||
-        !arrays.inArray(_document.getAnonymousNodes(node), node) ) ) {
-          return true;
-        }
-  }
-  return false;
-}
-
-var copyToClipboard = function(str){
-  const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"] .getService(Components.interfaces.nsIClipboardHelper);
-  gClipboardHelper.copyString(str, _window.document);
-}
-
-var getControllerAndDocument = function (_document, _window) {
-  var windowtype = _window.document.documentElement.getAttribute('windowtype');
-  var controllerString, documentString, activeTab;
-
-  // TODO replace with object based cases
-  switch(windowtype) {
-    case 'navigator:browser':
-      controllerString = 'mozmill.getBrowserController()';
-      activeTab = mozmill.getBrowserController().tabs.activeTab;
-      break;
-    case 'Browser:Preferences':
-      controllerString = 'mozmill.getPreferencesController()';
-      break;
-    case 'Extension:Manager':
-      controllerString = 'mozmill.getAddonsController()';
-      break;
-    default:
-      if(windowtype)
-        controllerString = 'new mozmill.controller.MozMillController(mozmill.utils.getWindowByType("' + windowtype + '"))';
-      else if(_window.document.title)
-        controllerString = 'new mozmill.controller.MozMillController(mozmill.utils.getWindowByTitle("'+_window.document.title+'"))';
-      else
-        controllerString = 'Cannot find window';
-      break;
-  }
-
-  if(activeTab == _document) {
-    documentString = 'controller.tabs.activeTab';
-  } else if(activeTab == _document.defaultView.top.document) {
-    // if this document is from an iframe in the active tab
-    var stub = getDocumentStub(_document, activeTab.defaultView);
-    documentString = 'controller.tabs.activeTab.defaultView' + stub;
-  } else {
-    var stub = getDocumentStub(_document, _window);
-    if(stub)
-      documentString = 'controller.window' + stub;
-    else
-      documentString = 'Cannot find document';
-  }
-  return {'controllerString':controllerString, 'documentString':documentString}
-}
-
-getDocumentStub = function( _document, _window) {
-  if(_window.document == _document)
-    return '.document';
-  for(var i = 0; i < _window.frames.length; i++) {
-    var stub = getDocumentStub(_document, _window.frames[i]);
-    if (stub)
-      return '.frames['+i+']' + stub;
-  }
-  return '';
-}
-
-var inspectElement = function(e){
-  if (e.originalTarget != undefined) {
-    target = e.originalTarget;
-  } else {
-    target = e.target;
-  }
-
-  //Element highlighting
-  try {
-    if (this.lastEvent)
-      this.lastEvent.target.style.outline = "";
-  } catch(err) {}
-
-  this.lastEvent = e;
-
-  try {
-     e.target.style.outline = "1px solid darkblue";
-  } catch(err){}
-
-  var _document = getDocument(target);
-
-
-  if (isMagicAnonymousDiv(_document, target)) {
-    target = target.parentNode;
-  }
-
-  var windowtype = _document.documentElement.getAttribute('windowtype');
-  var _window = getTopWindow(_document);
-  r = getControllerAndDocument(_document, _window);
-
-  // displayText = "Controller: " + r.controllerString + '\n\n';
-  if ( isNotAnonymous(target) ) {
-    // Logic for which identifier to use is duplicated above
-    if (target.id != "" && !withs.startsWith(target.id, 'panel')) {
-      elemText = "new elementslib.ID("+ r.documentString + ', "' + target.id + '")';
-      var telem = new elementslib.ID(_document, target.id);
-    } else if ((target.name != "") && (typeof(target.name) != "undefined")) {
-      elemText = "new elementslib.Name("+ r.documentString + ', "' + target.name + '")';
-      var telem = new elementslib.Name(_document, target.name);
-    } else if (target.nodeName == "A") {
-      var linkText = removeHTMLTags(target.innerHTML);
-      elemText = "new elementslib.Link("+ r.documentString + ', "' + linkText + '")';
-      var telem = new elementslib.Link(_document, linkText);
-    }
-  }
-  // Fallback on XPath
-  if (telem == undefined || telem.getNode() != target) {
-    if (windowtype == null) {
-      var stringXpath = getXSPath(target);
-    } else {
-      var stringXpath = getXULXpath(target, _document);
-    }
-    var telem = new elementslib.XPath(_document, stringXpath);
-    if ( telem.getNode() == target ) {
-      elemText = "new elementslib.XPath("+ r.documentString + ', "' + stringXpath + '")';
-    }
-  }
-  // Fallback to Lookup
-  if (telem == undefined || telem.getNode() != target) {
-    var exp = getLookupExpression(_document, target);
-    elemText = "new elementslib.Lookup("+ r.documentString + ", '" + exp + "')";
-    var telem = new elementslib.Lookup(_document, exp);
-  }
-
-  return {'validation':( target == telem.getNode() ),
-          'elementText':elemText,
-          'elementType':telem.constructor.name,
-          'controllerText':r.controllerString,
-          'documentString':r.documentString,
-          }
-}
-
-
-
deleted file mode 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/jum.js
+++ /dev/null
@@ -1,231 +0,0 @@
-/* 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/. */
-
-var EXPORTED_SYMBOLS = ["assert", "assertTrue", "assertFalse", "assertEquals", "assertNotEquals",
-                        "assertNull", "assertNotNull", "assertUndefined", "assertNotUndefined",
-                        "assertNaN", "assertNotNaN", "assertArrayContains", "fail", "pass"];
-
-
-// Array.isArray comes with JavaScript 1.8.5 (Firefox 4)
-// cf. https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
-Array.isArray = Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]'; };
-
-var frame = {}; Components.utils.import("resource://mozmill/modules/frame.js", frame);
-
-var ifJSONable = function (v) {
-  if (typeof(v) == 'function') {
-    return undefined;
-  } else {
-    return v;
-  }
-}
-
-var assert = function (booleanValue, comment) {
-  if (booleanValue) {
-    frame.events.pass({'function':'jum.assert', 'value':ifJSONable(booleanValue), 'comment':comment});
-    return true;
-  } else {
-    frame.events.fail({'function':'jum.assert', 'value':ifJSONable(booleanValue), 'comment':comment});
-    return false;
-  }
-}
-
-var assertTrue = function (booleanValue, comment) {
-  if (typeof(booleanValue) != 'boolean') {
-    frame.events.fail({'function':'jum.assertTrue', 'value':ifJSONable(booleanValue),
-                       'message':'Bad argument, value type '+typeof(booleanValue)+' !=  "boolean"',
-                       'comment':comment});
-    return false;
-  }
-
-  if (booleanValue) {
-    frame.events.pass({'function':'jum.assertTrue', 'value':ifJSONable(booleanValue),
-                       'comment':comment});
-    return true;
-  } else {
-    frame.events.fail({'function':'jum.assertTrue', 'value':ifJSONable(booleanValue),
-                       'comment':comment});
-    return false;
-  }
-}
-
-var assertFalse = function (booleanValue, comment) {
-  if (typeof(booleanValue) != 'boolean') {
-    frame.events.fail({'function':'jum.assertFalse', 'value':ifJSONable(booleanValue),
-                       'message':'Bad argument, value type '+typeof(booleanValue)+' !=  "boolean"',
-                       'comment':comment});
-    return false;
-  }
-
-  if (!booleanValue) {
-    frame.events.pass({'function':'jum.assertFalse', 'value':ifJSONable(booleanValue),
-                       'comment':comment});
-    return true;
-  } else {
-    frame.events.fail({'function':'jum.assertFalse', 'value':ifJSONable(booleanValue),
-                       'comment':comment});
-    return false;
-  }
-}
-
-var assertEquals = function (value1, value2, comment) {
-  // Case where value1 is an array
-  if (Array.isArray(value1)) {
-
-    if (!Array.isArray(value2)) {
-      frame.events.fail({'function':'jum.assertEquals', 'comment':comment,
-                         'message':'Bad argument, value1 is an array and value2 type ' +
-                         typeof(value2)+' != "array"',
-                         'value2':ifJSONable(value2)});
-      return false;
-    }
-
-    if (value1.length != value2.length) {
-      frame.events.fail({'function':'jum.assertEquals', 'comment':comment,
-                         'message':"The arrays do not have the same length",
-                         'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
-      return false;
-    }
-
-    for (var i = 0; i < value1.length; i++) {
-      if (value1[i] !== value2[i]) {
-        frame.events.fail(
-          {'function':'jum.assertEquals', 'comment':comment,
-           'message':"The element of the arrays are different at index " + i,
-           'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
-        return false;
-      }
-    }
-    frame.events.pass({'function':'jum.assertEquals', 'comment':comment,
-                       'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
-    return true;
-  }
-
-  // Case where value1 is not an array
-  if (value1 == value2) {
-    frame.events.pass({'function':'jum.assertEquals', 'comment':comment,
-                       'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
-    return true;
-  } else {
-    frame.events.fail({'function':'jum.assertEquals', 'comment':comment,
-                       'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
-    return false;
-  }
-}
-
-var assertNotEquals = function (value1, value2, comment) {
-  if (value1 != value2) {
-    frame.events.pass({'function':'jum.assertNotEquals', 'comment':comment,
-                       'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
-    return true;
-  } else {
-    frame.events.fail({'function':'jum.assertNotEquals', 'comment':comment,
-                       'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
-    return false;
-  }
-}
-
-var assertNull = function (value, comment) {
-  if (value == null) {
-    frame.events.pass({'function':'jum.assertNull', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return true;
-  } else {
-    frame.events.fail({'function':'jum.assertNull', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return false;
-  }
-}
-
-var assertNotNull = function (value, comment) {
-  if (value != null) {
-    frame.events.pass({'function':'jum.assertNotNull', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return true;
-  } else {
-    frame.events.fail({'function':'jum.assertNotNull', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return false;
-  }
-}
-
-var assertUndefined = function (value, comment) {
-  if (value == undefined) {
-    frame.events.pass({'function':'jum.assertUndefined', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return true;
-  } else {
-    frame.events.fail({'function':'jum.assertUndefined', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return false;
-  }
-}
-
-var assertNotUndefined = function (value, comment) {
-  if (value != undefined) {
-    frame.events.pass({'function':'jum.assertNotUndefined', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return true;
-  } else {
-    frame.events.fail({'function':'jum.assertNotUndefined', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return false;
-  }
-}
-
-var assertNaN = function (value, comment) {
-  if (isNaN(value)) {
-    frame.events.pass({'function':'jum.assertNaN', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return true;
-  } else {
-    frame.events.fail({'function':'jum.assertNaN', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return false;
-  }
-}
-
-var assertNotNaN = function (value, comment) {
-  if (!isNaN(value)) {
-    frame.events.pass({'function':'jum.assertNotNaN', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return true;
-  } else {
-    frame.events.fail({'function':'jum.assertNotNaN', 'comment':comment,
-                       'value':ifJSONable(value)});
-    return false;
-  }
-}
-
-var assertArrayContains = function(array, value, comment) {
-  if (!Array.isArray(array)) {
-    frame.events.fail({'function':'jum.assertArrayContains', 'comment':comment,
-                       'message':'Bad argument, value type '+typeof(array)+' != "array"',
-                       'value':ifJSONable(array)});
-    return false;
-  }
-
-  for (var i = 0; i < array.length; i++) {
-    if (array[i] === value) {
-      frame.events.pass({'function':'jum.assertArrayContains', 'comment':comment,
-                         'value1':ifJSONable(array), 'value2':ifJSONable(value)});
-      return true;
-    }
-  }
-  frame.events.fail({'function':'jum.assertArrayContains', 'comment':comment,
-                     'value1':ifJSONable(array), 'value2':ifJSONable(value)});
-  return false;
-}
-
-var fail = function (comment) {
-  frame.events.fail({'function':'jum.fail', 'comment':comment});
-  return false;
-}
-
-var pass = function (comment) {
-  frame.events.pass({'function':'jum.pass', 'comment':comment});
-  return true;
-}
-
-
--- a/services/sync/tps/extensions/mozmill/resource/modules/l10n.js
+++ b/services/sync/tps/extensions/mozmill/resource/modules/l10n.js
@@ -1,17 +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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * @namespace Defines useful methods to work with localized content
  */
 var l10n = exports;
 
+Cu.import("resource://gre/modules/Services.jsm");
+
 /**
  * Retrieve the localized content for a given DTD entity
  *
  * @memberOf l10n
  * @param {String[]} aDTDs Array of URLs for DTD files.
  * @param {String} aEntityId ID of the entity to get the localized content of.
  *
  * @returns {String} Localized content
@@ -49,24 +51,21 @@ function getEntity(aDTDs, aEntityId) {
  *
  * @memberOf l10n
  * @param {String} aURL URL of the .properties file.
  * @param {String} aProperty The property to get the value of.
  *
  * @returns {String} Value of the requested property
  */
 function getProperty(aURL, aProperty) {
-  var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
-            getService(Ci.nsIStringBundleService);
-  var bundle = sbs.createBundle(aURL);
+  var bundle = Services.strings.createBundle(aURL);
 
   try {
     return bundle.GetStringFromName(aProperty);
-  }
-  catch (ex) {
+  } catch (ex) {
     throw new Error("Unkown property '" + aProperty + "'");
   }
 }
 
 
 // Export of functions
 l10n.getEntity = getEntity;
 l10n.getProperty = getProperty;
deleted file mode 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/mozelement.js
+++ /dev/null
@@ -1,668 +0,0 @@
-/* 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/. */
-
-var EXPORTED_SYMBOLS = ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup",
-                        "MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList",
-                        "MozMillTextBox", "subclasses",
-                       ];
-
-var EventUtils = {}; Components.utils.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
-var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
-var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
-var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
-
-// A list of all the subclasses available.  Shared modules can push their own subclasses onto this list
-var subclasses = [MozMillCheckBox, MozMillRadio, MozMillDropList, MozMillTextBox];
-
-/**
- * createInstance()
- *
- * Returns an new instance of a MozMillElement
- * The type of the element is automatically determined
- */
-function createInstance(locatorType, locator, elem) {
-  if (elem) {
-    var args = {"element":elem};
-    for (var i = 0; i < subclasses.length; ++i) {
-      if (subclasses[i].isType(elem)) {
-        return new subclasses[i](locatorType, locator, args);
-      }
-    }
-    if (MozMillElement.isType(elem)) return new MozMillElement(locatorType, locator, args);
-  }
-  throw new Error("could not find element " + locatorType + ": " + locator);
-};
-
-var Elem = function(node) {
-  return createInstance("Elem", node, node);
-};
-
-var Selector = function(_document, selector, index) {
-  return createInstance("Selector", selector, elementslib.Selector(_document, selector, index));
-};
-
-var ID = function(_document, nodeID) {
-  return createInstance("ID", nodeID, elementslib.ID(_document, nodeID));
-};
-
-var Link = function(_document, linkName) {
-  return createInstance("Link", linkName, elementslib.Link(_document, linkName));
-};
-
-var XPath = function(_document, expr) {
-  return createInstance("XPath", expr, elementslib.XPath(_document, expr));
-};
-
-var Name = function(_document, nName) {
-  return createInstance("Name", nName, elementslib.Name(_document, nName));
-};
-
-var Lookup = function(_document, expression) {
-  return createInstance("Lookup", expression, elementslib.Lookup(_document, expression));
-};
-
-
-/**
- * MozMillElement
- * The base class for all mozmill elements
- */
-function MozMillElement(locatorType, locator, args) {
-  args = args || {};
-  this._locatorType = locatorType;
-  this._locator = locator;
-  this._element = args["element"];
-  this._document = args["document"];
-  this._owner = args["owner"];
-  // Used to maintain backwards compatibility with controller.js
-  this.isElement = true;
-}
-
-// Static method that returns true if node is of this element type
-MozMillElement.isType = function(node) {
-  return true;
-};
-
-// This getter is the magic behind lazy loading (note distinction between _element and element)
-MozMillElement.prototype.__defineGetter__("element", function() {
-  if (this._element == undefined) {
-    if (elementslib[this._locatorType]) {
-      this._element = elementslib[this._locatorType](this._document, this._locator);
-    } else if (this._locatorType == "Elem") {
-      this._element = this._locator;
-    } else {
-      throw new Error("Unknown locator type: " + this._locatorType);
-    }
-  }
-  return this._element;
-});
-
-// Returns the actual wrapped DOM node
-MozMillElement.prototype.getNode = function() {
-  return this.element;
-};
-
-MozMillElement.prototype.getInfo = function() {
-  return this._locatorType + ": " + this._locator;
-};
-
-/**
- * Sometimes an element which once existed will no longer exist in the DOM
- * This function re-searches for the element
- */
-MozMillElement.prototype.exists = function() {
-  this._element = undefined;
-  if (this.element) return true;
-  return false;
-};
-
-/**
- * Synthesize a keypress event on the given element
- *
- * @param {string} aKey
- *        Key to use for synthesizing the keypress event. It can be a simple
- *        character like "k" or a string like "VK_ESCAPE" for command keys
- * @param {object} aModifiers
- *        Information about the modifier keys to send
- *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
- *                               [optional - default: false]
- *                  altKey     - Hold down the alt key
- *                              [optional - default: false]
- *                  ctrlKey    - Hold down the ctrl key
- *                               [optional - default: false]
- *                  metaKey    - Hold down the meta key (command key on Mac)
- *                               [optional - default: false]
- *                  shiftKey   - Hold down the shift key
- *                               [optional - default: false]
- * @param {object} aExpectedEvent
- *        Information about the expected event to occur
- *        Elements: target     - Element which should receive the event
- *                               [optional - default: current element]
- *                  type       - Type of the expected key event
- */
-MozMillElement.prototype.keypress = function(aKey, aModifiers, aExpectedEvent) {
-  if (!this.element) {
-    throw new Error("Could not find element " + this.getInfo());
-  }
-
-  var win = this.element.ownerDocument? this.element.ownerDocument.defaultView : this.element;
-  this.element.focus();
-
-  if (aExpectedEvent) {
-    var target = aExpectedEvent.target? aExpectedEvent.target.getNode() : this.element;
-    EventUtils.synthesizeKeyExpectEvent(aKey, aModifiers || {}, target, aExpectedEvent.type,
-                                                            "MozMillElement.keypress()", win);
-  } else {
-    EventUtils.synthesizeKey(aKey, aModifiers || {}, win);
-  }
-
-  frame.events.pass({'function':'MozMillElement.keypress()'});
-  return true;
-};
-
-
-/**
- * Synthesize a general mouse event on the given element
- *
- * @param {ElemBase} aTarget
- *        Element which will receive the mouse event
- * @param {number} aOffsetX
- *        Relative x offset in the elements bounds to click on
- * @param {number} aOffsetY
- *        Relative y offset in the elements bounds to click on
- * @param {object} aEvent
- *        Information about the event to send
- *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
- *                               [optional - default: false]
- *                  altKey     - Hold down the alt key
- *                               [optional - default: false]
- *                  button     - Mouse button to use
- *                               [optional - default: 0]
- *                  clickCount - Number of counts to click
- *                               [optional - default: 1]
- *                  ctrlKey    - Hold down the ctrl key
- *                               [optional - default: false]
- *                  metaKey    - Hold down the meta key (command key on Mac)
- *                               [optional - default: false]
- *                  shiftKey   - Hold down the shift key
- *                               [optional - default: false]
- *                  type       - Type of the mouse event ('click', 'mousedown',
- *                               'mouseup', 'mouseover', 'mouseout')
- *                               [optional - default: 'mousedown' + 'mouseup']
- * @param {object} aExpectedEvent
- *        Information about the expected event to occur
- *        Elements: target     - Element which should receive the event
- *                               [optional - default: current element]
- *                  type       - Type of the expected mouse event
- */
-MozMillElement.prototype.mouseEvent = function(aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
-  if (!this.element) {
-    throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
-  }
-
-  // If no offset is given we will use the center of the element to click on.
-  var rect = this.element.getBoundingClientRect();
-  if (isNaN(aOffsetX)) {
-    aOffsetX = rect.width / 2;
-  }
-  if (isNaN(aOffsetY)) {
-    aOffsetY = rect.height / 2;
-  }
-
-  // Scroll element into view otherwise the click will fail
-  if (this.element.scrollIntoView) {
-    this.element.scrollIntoView();
-  }
-
-  if (aExpectedEvent) {
-    // The expected event type has to be set
-    if (!aExpectedEvent.type)
-      throw new Error(arguments.callee.name + ": Expected event type not specified");
-
-    // If no target has been specified use the specified element
-    var target = aExpectedEvent.target ? aExpectedEvent.target.getNode() : this.element;
-    if (!target) {
-      throw new Error(arguments.callee.name + ": could not find element " + aExpectedEvent.target.getInfo());
-    }
-
-    EventUtils.synthesizeMouseExpectEvent(this.element, aOffsetX, aOffsetY, aEvent,
-                                          target, aExpectedEvent.event,
-                                          "MozMillElement.mouseEvent()",
-                                          this.element.ownerDocument.defaultView);
-  } else {
-    EventUtils.synthesizeMouse(this.element, aOffsetX, aOffsetY, aEvent,
-                               this.element.ownerDocument.defaultView);
-  }
-};
-
-/**
- * Synthesize a mouse click event on the given element
- */
-MozMillElement.prototype.click = function(left, top, expectedEvent) {
-  // Handle menu items differently
-  if (this.element && this.element.tagName == "menuitem") {
-    this.element.click();
-  } else {
-    this.mouseEvent(left, top, {}, expectedEvent);
-  }
-
-  frame.events.pass({'function':'MozMillElement.click()'});
-};
-
-/**
- * Synthesize a double click on the given element
- */
-MozMillElement.prototype.doubleClick = function(left, top, expectedEvent) {
-  this.mouseEvent(left, top, {clickCount: 2}, expectedEvent);
-
-  frame.events.pass({'function':'MozMillElement.doubleClick()'});
-  return true;
-};
-
-/**
- * Synthesize a mouse down event on the given element
- */
-MozMillElement.prototype.mouseDown = function (button, left, top, expectedEvent) {
-  this.mouseEvent(left, top, {button: button, type: "mousedown"}, expectedEvent);
-
-  frame.events.pass({'function':'MozMillElement.mouseDown()'});
-  return true;
-};
-
-/**
- * Synthesize a mouse out event on the given element
- */
-MozMillElement.prototype.mouseOut = function (button, left, top, expectedEvent) {
-  this.mouseEvent(left, top, {button: button, type: "mouseout"}, expectedEvent);
-
-  frame.events.pass({'function':'MozMillElement.mouseOut()'});
-  return true;
-};
-
-/**
- * Synthesize a mouse over event on the given element
- */
-MozMillElement.prototype.mouseOver = function (button, left, top, expectedEvent) {
-  this.mouseEvent(left, top, {button: button, type: "mouseover"}, expectedEvent);
-
-  frame.events.pass({'function':'MozMillElement.mouseOver()'});
-  return true;
-};
-
-/**
- * Synthesize a mouse up event on the given element
- */
-MozMillElement.prototype.mouseUp = function (button, left, top, expectedEvent) {
-  this.mouseEvent(left, top, {button: button, type: "mouseup"}, expectedEvent);
-
-  frame.events.pass({'function':'MozMillElement.mouseUp()'});
-  return true;
-};
-
-/**
- * Synthesize a mouse middle click event on the given element
- */
-MozMillElement.prototype.middleClick = function(left, top, expectedEvent) {
-  this.mouseEvent(left, top, {button: 1}, expectedEvent);
-
-  frame.events.pass({'function':'MozMillElement.middleClick()'});
-  return true;
-};
-
-/**
- * Synthesize a mouse right click event on the given element
- */
-MozMillElement.prototype.rightClick = function(left, top, expectedEvent) {
-  this.mouseEvent(left, top, {type : "contextmenu", button: 2 }, expectedEvent);
-
-  frame.events.pass({'function':'MozMillElement.rightClick()'});
-  return true;
-};
-
-MozMillElement.prototype.waitForElement = function(timeout, interval) {
-  var elem = this;
-  utils.waitFor(function() {
-    return elem.exists();
-  }, "Timeout exceeded for waitForElement " + this.getInfo(), timeout, interval);
-
-  frame.events.pass({'function':'MozMillElement.waitForElement()'});
-};
-
-MozMillElement.prototype.waitForElementNotPresent = function(timeout, interval) {
-  var elem = this;
-  utils.waitFor(function() {
-    return !elem.exists();
-  }, "Timeout exceeded for waitForElementNotPresent " + this.getInfo(), timeout, interval);
-
-  frame.events.pass({'function':'MozMillElement.waitForElementNotPresent()'});
-};
-
-MozMillElement.prototype.waitThenClick = function (timeout, interval, left, top, expectedEvent) {
-  this.waitForElement(timeout, interval);
-  this.click(left, top, expectedEvent);
-};
-
-// Dispatches an HTMLEvent
-MozMillElement.prototype.dispatchEvent = function (eventType, canBubble, modifiers) {
-  canBubble = canBubble || true;
-  var evt = this.element.ownerDocument.createEvent('HTMLEvents');
-  evt.shiftKey = modifiers["shift"];
-  evt.metaKey = modifiers["meta"];
-  evt.altKey = modifiers["alt"];
-  evt.ctrlKey = modifiers["ctrl"];
-  evt.initEvent(eventType, canBubble, true);
-  this.element.dispatchEvent(evt);
-};
-
-
-//---------------------------------------------------------------------------------------------------------------------------------------
-
-
-/**
- * MozMillCheckBox
- * Checkbox element, inherits from MozMillElement
- */
-MozMillCheckBox.prototype = new MozMillElement();
-MozMillCheckBox.prototype.parent = MozMillElement.prototype;
-MozMillCheckBox.prototype.constructor = MozMillCheckBox;
-function MozMillCheckBox(locatorType, locator, args) {
-  this.parent.constructor.call(this, locatorType, locator, args);
-}
-
-// Static method returns true if node is this type of element
-MozMillCheckBox.isType = function(node) {
-  if ((node.localName.toLowerCase() == "input" && node.getAttribute("type") == "checkbox") ||
-      (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'checkbox') ||
-      (node.localName.toLowerCase() == 'checkbox')) {
-    return true;
-  }
-  return false;
-};
-
-/**
- * Enable/Disable a checkbox depending on the target state
- */
-MozMillCheckBox.prototype.check = function(state) {
-  var result = false;
-
-  if (!this.element) {
-    throw new Error("could not find element " + this.getInfo());
-    return false;
-  }
-
-  // If we have a XUL element, unwrap its XPCNativeWrapper
-  if (this.element.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
-    this.element = utils.unwrapNode(this.element);
-  }
-
-  state = (typeof(state) == "boolean") ? state : false;
-  if (state != this.element.checked) {
-    this.click();
-    var element = this.element;
-    utils.waitFor(function() {
-      return element.checked == state;
-    }, "Checkbox " + this.getInfo() + " could not be checked/unchecked", 500);
-
-    result = true;
-  }
-
-  frame.events.pass({'function':'MozMillCheckBox.check(' + this.getInfo() + ', state: ' + state + ')'});
-  return result;
-};
-
-//----------------------------------------------------------------------------------------------------------------------------------------
-
-
-/**
- * MozMillRadio
- * Radio button inherits from MozMillElement
- */
-MozMillRadio.prototype = new MozMillElement();
-MozMillRadio.prototype.parent = MozMillElement.prototype;
-MozMillRadio.prototype.constructor = MozMillRadio;
-function MozMillRadio(locatorType, locator, args) {
-  this.parent.constructor.call(this, locatorType, locator, args);
-}
-
-// Static method returns true if node is this type of element
-MozMillRadio.isType = function(node) {
-  if ((node.localName.toLowerCase() == 'input' && node.getAttribute('type') == 'radio') ||
-      (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'radio') ||
-      (node.localName.toLowerCase() == 'radio') ||
-      (node.localName.toLowerCase() == 'radiogroup')) {
-    return true;
-  }
-  return false;
-};
-
-/**
- * Select the given radio button
- *
- * index - Specifies which radio button in the group to select (only applicable to radiogroup elements)
- *         Defaults to the first radio button in the group
- */
-MozMillRadio.prototype.select = function(index) {
-  if (!this.element) {
-    throw new Error("could not find element " + this.getInfo());
-  }
-
-  if (this.element.localName.toLowerCase() == "radiogroup") {
-    var element = this.element.getElementsByTagName("radio")[index || 0];
-    new MozMillRadio("Elem", element).click();
-  } else {
-    var element = this.element;
-    this.click();
-  }
-
-  utils.waitFor(function() {
-    // If we have a XUL element, unwrap its XPCNativeWrapper
-    if (element.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
-      element = utils.unwrapNode(element);
-      return element.selected == true;
-    }
-    return element.checked == true;
-  }, "Radio button " + this.getInfo() + " could not be selected", 500);
-
-  frame.events.pass({'function':'MozMillRadio.select(' + this.getInfo() + ')'});
-  return true;
-};
-
-//----------------------------------------------------------------------------------------------------------------------------------------
-
-
-/**
- * MozMillDropList
- * DropList inherits from MozMillElement
- */
-MozMillDropList.prototype = new MozMillElement();
-MozMillDropList.prototype.parent = MozMillElement.prototype;
-MozMillDropList.prototype.constructor = MozMillDropList;
-function MozMillDropList(locatorType, locator, args) {
-  this.parent.constructor.call(this, locatorType, locator, args);
-};
-
-// Static method returns true if node is this type of element
-MozMillDropList.isType = function(node) {
-  if ((node.localName.toLowerCase() == 'toolbarbutton' && (node.getAttribute('type') == 'menu' || node.getAttribute('type') == 'menu-button')) ||
-      (node.localName.toLowerCase() == 'menu') ||
-      (node.localName.toLowerCase() == 'menulist') ||
-      (node.localName.toLowerCase() == 'select' )) {
-    return true;
-  }
-  return false;
-};
-
-/* Select the specified option and trigger the relevant events of the element */
-MozMillDropList.prototype.select = function (indx, option, value) {
-  if (!this.element){
-    throw new Error("Could not find element " + this.getInfo());
-  }
-
-  //if we have a select drop down
-  if (this.element.localName.toLowerCase() == "select"){
-    var item = null;
-
-    // The selected item should be set via its index
-    if (indx != undefined) {
-      // Resetting a menulist has to be handled separately
-      if (indx == -1) {
-        this.dispatchEvent('focus', false);
-        this.element.selectedIndex = indx;
-        this.dispatchEvent('change', true);
-
-        frame.events.pass({'function':'MozMillDropList.select()'});
-        return true;
-      } else {
-        item = this.element.options.item(indx);
-      }
-    } else {
-      for (var i = 0; i < this.element.options.length; i++) {
-        var entry = this.element.options.item(i);
-        if (option != undefined && entry.innerHTML == option ||
-            value != undefined && entry.value == value) {
-          item = entry;
-          break;
-        }
-      }
-    }
-
-    // Click the item
-    try {
-      // EventUtils.synthesizeMouse doesn't work.
-      this.dispatchEvent('focus', false);
-      item.selected = true;
-      this.dispatchEvent('change', true);
-
-      frame.events.pass({'function':'MozMillDropList.select()'});
-      return true;
-    } catch (ex) {
-      throw new Error("No item selected for element " + this.getInfo());
-      return false;
-    }
-  }
-  //if we have a xul menupopup select accordingly
-  else if (this.element.namespaceURI.toLowerCase() == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
-    var ownerDoc = this.element.ownerDocument;
-    // Unwrap the XUL element's XPCNativeWrapper
-    this.element = utils.unwrapNode(this.element);
-    // Get the list of menuitems
-    menuitems = this.element.getElementsByTagName("menupopup")[0].getElementsByTagName("menuitem");
-
-    var item = null;
-
-    if (indx != undefined) {
-      if (indx == -1) {
-        this.dispatchEvent('focus', false);
-        this.element.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject).activeChild = null;
-        this.dispatchEvent('change', true);
-
-        frame.events.pass({'function':'MozMillDropList.select()'});
-        return true;
-      } else {
-        item = menuitems[indx];
-      }
-    } else {
-      for (var i = 0; i < menuitems.length; i++) {
-        var entry = menuitems[i];
-        if (option != undefined && entry.label == option ||
-            value != undefined && entry.value == value) {
-          item = entry;
-          break;
-        }
-      }
-    }
-
-    // Click the item
-    try {
-      EventUtils.synthesizeMouse(this.element, 1, 1, {}, ownerDoc.defaultView);
-
-      // Scroll down until item is visible
-      for (var i = 0; i <= menuitems.length; ++i) {
-        var selected = this.element.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject).activeChild;
-        if (item == selected) {
-          break;
-        }
-        EventUtils.synthesizeKey("VK_DOWN", {}, ownerDoc.defaultView);
-      }
-
-      EventUtils.synthesizeMouse(item, 1, 1, {}, ownerDoc.defaultView);
-
-      frame.events.pass({'function':'MozMillDropList.select()'});
-      return true;
-    } catch (ex) {
-      throw new Error('No item selected for element ' + this.getInfo());
-      return false;
-    }
-  }
-};
-
-
-//----------------------------------------------------------------------------------------------------------------------------------------
-
-
-/**
- * MozMillTextBox
- * TextBox inherits from MozMillElement
- */
-MozMillTextBox.prototype = new MozMillElement();
-MozMillTextBox.prototype.parent = MozMillElement.prototype;
-MozMillTextBox.prototype.constructor = MozMillTextBox;
-function MozMillTextBox(locatorType, locator, args) {
-  this.parent.constructor.call(this, locatorType, locator, args);
-};
-
-// Static method returns true if node is this type of element
-MozMillTextBox.isType = function(node) {
-  if ((node.localName.toLowerCase() == 'input' && (node.getAttribute('type') == 'text' || node.getAttribute('type') == 'search')) ||
-      (node.localName.toLowerCase() == 'textarea') ||
-      (node.localName.toLowerCase() == 'textbox')) {
-    return true;
-  }
-  return false;
-};
-
-/**
- * Synthesize keypress events for each character on the given element
- *
- * @param {string} aText
- *        The text to send as single keypress events
- * @param {object} aModifiers
- *        Information about the modifier keys to send
- *        Elements: accelKey   - Hold down the accelerator key (ctrl/meta)
- *                               [optional - default: false]
- *                  altKey     - Hold down the alt key
- *                              [optional - default: false]
- *                  ctrlKey    - Hold down the ctrl key
- *                               [optional - default: false]
- *                  metaKey    - Hold down the meta key (command key on Mac)
- *                               [optional - default: false]
- *                  shiftKey   - Hold down the shift key
- *                               [optional - default: false]
- * @param {object} aExpectedEvent
- *        Information about the expected event to occur
- *        Elements: target     - Element which should receive the event
- *                               [optional - default: current element]
- *                  type       - Type of the expected key event
- */
-MozMillTextBox.prototype.sendKeys = function (aText, aModifiers, aExpectedEvent) {
-  if (!this.element) {
-    throw new Error("could not find element " + this.getInfo());
-  }
-
-  var element = this.element;
-  Array.forEach(aText, function(letter) {
-    var win = element.ownerDocument? element.ownerDocument.defaultView : element;
-    element.focus();
-
-    if (aExpectedEvent) {
-      var target = aExpectedEvent.target ? aExpectedEvent.target.getNode() : element;
-      EventUtils.synthesizeKeyExpectEvent(letter, aModifiers || {}, target, aExpectedEvent.type,
-                                                              "MozMillTextBox.sendKeys()", win);
-    } else {
-      EventUtils.synthesizeKey(letter, aModifiers || {}, win);
-    }
-  });
-
-  frame.events.pass({'function':'MozMillTextBox.type()'});
-  return true;
-};
deleted file mode 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/mozmill.js
+++ /dev/null
@@ -1,229 +0,0 @@
-/* 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/. */
-
-var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
-                        "getBrowserController", "newBrowserController",
-                        "getAddonsController", "getPreferencesController",
-                        "newMail3PaneController", "getMail3PaneController",
-                        "wm", "platform", "getAddrbkController",
-                        "getMsgComposeController", "getDownloadsController",
-                        "Application", "cleanQuit",
-                        "getPlacesController", 'isMac', 'isLinux', 'isWindows',
-                        "firePythonCallback"
-                       ];
-
-// imports
-var controller = {};  Components.utils.import('resource://mozmill/modules/controller.js', controller);
-var utils = {};       Components.utils.import('resource://mozmill/modules/utils.js', utils);
-var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
-var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
-var os = {}; Components.utils.import('resource://mozmill/stdlib/os.js', os);
-
-try {
-  Components.utils.import("resource://gre/modules/AddonManager.jsm");
-} catch(e) { /* Firefox 4 only */ }
-
-// platform information
-var platform = os.getPlatform();
-var isMac = false;
-var isWindows = false;
-var isLinux = false;
-if (platform == "darwin"){
-  isMac = true;
-}
-if (platform == "winnt"){
-  isWindows = true;
-}
-if (platform == "linux"){
-  isLinux = true;
-}
-
-var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-           .getService(Components.interfaces.nsIWindowMediator);
-
-var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
-               .getService(Components.interfaces.nsIXULAppInfo);
-
-var locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
-               .getService(Components.interfaces.nsIXULChromeRegistry)
-               .getSelectedLocale("global");
-
-var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"].
-    getService(Components.interfaces.nsIConsoleService);
-
-
-applicationDictionary = {
-  "{718e30fb-e89b-41dd-9da7-e25a45638b28}": "Sunbird",
-  "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "SeaMonkey",
-  "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "Firefox",
-  "{3550f703-e582-4d05-9a08-453d09bdfdc6}": 'Thunderbird',
-}
-
-var Application = applicationDictionary[appInfo.ID];
-
-if (Application == undefined) {
-  // Default to Firefox
-  var Application = 'Firefox';
-}
-
-// get startup time if available
-// see http://blog.mozilla.com/tglek/2011/04/26/measuring-startup-speed-correctly/
-var startupInfo = {};
-try {
-    var _startupInfo = Components.classes["@mozilla.org/toolkit/app-startup;1"]
-        .getService(Components.interfaces.nsIAppStartup).getStartupInfo();
-    for (var i in _startupInfo) {
-        startupInfo[i] = _startupInfo[i].getTime(); // convert from Date object to ms since epoch
-    }
-} catch(e) {
-    startupInfo = null;
-}
-
-
-// keep list of installed addons to send to jsbridge for test run report
-var addons = "null"; // this will be JSON parsed
-if(typeof AddonManager != "undefined") {
-  AddonManager.getAllAddons(function(addonList) {
-      var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
-          .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
-      converter.charset = 'utf-8';
-
-      function replacer(key, value) {
-          if (typeof(value) == "string") {
-              try {
-                  return converter.ConvertToUnicode(value);
-              } catch(e) {
-                  var newstring = '';
-                  for (var i=0; i < value.length; i++) {
-                      replacement = '';
-                      if ((32 <= value.charCodeAt(i)) && (value.charCodeAt(i) < 127)) {
-                          // eliminate non-convertable characters;
-                          newstring += value.charAt(i);
-                      } else {
-                          newstring += replacement;
-                      }
-                  }
-                  return newstring;
-              }
-          }
-          return value;
-      }
-
-      addons = converter.ConvertToUnicode(JSON.stringify(addonList, replacer))
-  });
-}
-
-function cleanQuit () {
-  utils.getMethodInWindows('goQuitApplication')();
-}
-
-function addHttpResource (directory, namespace) {
-  return 'http://localhost:4545/'+namespace;
-}
-
-function newBrowserController () {
-  return new controller.MozMillController(utils.getMethodInWindows('OpenBrowserWindow')());
-}
-
-function getBrowserController () {
-  var browserWindow = wm.getMostRecentWindow("navigator:browser");
-  if (browserWindow == null) {
-    return newBrowserController();
-  }
-  else {
-    return new controller.MozMillController(browserWindow);
-  }
-}
-
-function getPlacesController () {
-  utils.getMethodInWindows('PlacesCommandHook').showPlacesOrganizer('AllBookmarks');
-  return new controller.MozMillController(wm.getMostRecentWindow(''));
-}
-
-function getAddonsController () {
-  if (Application == 'SeaMonkey') {
-    utils.getMethodInWindows('toEM')();
-  } else if (Application == 'Thunderbird') {
-    utils.getMethodInWindows('openAddonsMgr')();
-  } else if (Application == 'Sunbird') {
-    utils.getMethodInWindows('goOpenAddons')();
-  } else {
-    utils.getMethodInWindows('BrowserOpenAddonsMgr')();
-  }
-  return new controller.MozMillController(wm.getMostRecentWindow(''));
-}
-
-function getDownloadsController() {
-  utils.getMethodInWindows('BrowserDownloadsUI')();
-  return new controller.MozMillController(wm.getMostRecentWindow(''));
-}
-
-function getPreferencesController() {
-  if (Application == 'Thunderbird') {
-    utils.getMethodInWindows('openOptionsDialog')();
-  } else {
-    utils.getMethodInWindows('openPreferences')();
-  }
-  return new controller.MozMillController(wm.getMostRecentWindow(''));
-}
-
-// Thunderbird functions
-function newMail3PaneController () {
-  return new controller.MozMillController(utils.getMethodInWindows('toMessengerWindow')());
-}
-
-function getMail3PaneController () {
-  var mail3PaneWindow = wm.getMostRecentWindow("mail:3pane");
-  if (mail3PaneWindow == null) {
-    return newMail3PaneController();
-  }
-  else {
-    return new controller.MozMillController(mail3PaneWindow);
-  }
-}
-
-// Thunderbird - Address book window
-function newAddrbkController () {
-  utils.getMethodInWindows("toAddressBook")();
-  utils.sleep(2000);
-  var addyWin = wm.getMostRecentWindow("mail:addressbook");
-  return new controller.MozMillController(addyWin);
-}
-
-function getAddrbkController () {
-  var addrbkWindow = wm.getMostRecentWindow("mail:addressbook");
-  if (addrbkWindow == null) {
-    return newAddrbkController();
-  }
-  else {
-    return new controller.MozMillController(addrbkWindow);
-  }
-}
-
-function firePythonCallback (filename, method, args, kwargs) {
-  obj = {'filename': filename, 'method': method};
-  obj['test'] = frame.events.currentModule.__file__;
-  obj['args'] = args || [];
-  obj['kwargs'] = kwargs || {};
-  frame.events.fireEvent("firePythonCallback", obj);
-}
-
-function timer (name) {
-  this.name = name;
-  this.timers = {};
-  frame.timers.push(this);
-  this.actions = [];
-}
-timer.prototype.start = function (name) {
-  this.timers[name].startTime = (new Date).getTime();
-}
-timer.prototype.stop = function (name) {
-  var t = this.timers[name];
-  t.endTime = (new Date).getTime();
-  t.totalTime = (t.endTime - t.startTime);
-}
-timer.prototype.end = function () {
-  frame.events.fireEvent("timer", this);
-  frame.timers.remove(this);
-}
new file mode 100644
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/modules/stack.js
@@ -0,0 +1,43 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ['findCallerFrame'];
+
+
+/**
+ * @namespace Defines utility methods for handling stack frames
+ */
+
+/**
+ * Find the frame to use for logging the test result. If a start frame has
+ * been specified, we walk down the stack until a frame with the same filename
+ * as the start frame has been found. The next file in the stack will be the
+ * frame to use for logging the result.
+ *
+ * @memberOf stack
+ * @param {Object} [aStartFrame=Components.stack] Frame to start from walking up the stack.
+ * @returns {Object} Frame of the stack to use for logging the result.
+ */
+function findCallerFrame(aStartFrame) {
+  let frame = Components.stack;
+  let filename = frame.filename.replace(/(.*)-> /, "");
+
+  // If a start frame has been specified, walk up the stack until we have
+  // found the corresponding file
+  if (aStartFrame) {
+    filename = aStartFrame.filename.replace(/(.*)-> /, "");
+
+    while (frame.caller &&
+           frame.filename && (frame.filename.indexOf(filename) == -1)) {
+      frame = frame.caller;
+    }
+  }
+
+  // Walk even up more until the next file has been found
+  while (frame.caller &&
+         (!frame.filename || (frame.filename.indexOf(filename) != -1)))
+    frame = frame.caller;
+
+  return frame;
+}
deleted file mode 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/utils.js
+++ /dev/null
@@ -1,522 +0,0 @@
-/* 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/. */
-
-var EXPORTED_SYMBOLS = ["openFile", "saveFile", "saveAsFile", "genBoiler",
-                        "getFile", "Copy", "getChromeWindow", "getWindows", "runEditor",
-                        "runFile", "getWindowByTitle", "getWindowByType", "tempfile",
-                        "getMethodInWindows", "getPreference", "setPreference",
-                        "sleep", "assert", "unwrapNode", "TimeoutError", "waitFor",
-                        "takeScreenshot",
-                       ];
-
-var hwindow = Components.classes["@mozilla.org/appshell/appShellService;1"]
-              .getService(Components.interfaces.nsIAppShellService)
-              .hiddenDOMWindow;
-
-var uuidgen = Components.classes["@mozilla.org/uuid-generator;1"]
-    .getService(Components.interfaces.nsIUUIDGenerator);
-
-function Copy (obj) {
-  for (var n in obj) {
-    this[n] = obj[n];
-  }
-}
-
-function getChromeWindow(aWindow) {
-  var chromeWin = aWindow
-           .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-           .getInterface(Components.interfaces.nsIWebNavigation)
-           .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
-           .rootTreeItem
-           .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-           .getInterface(Components.interfaces.nsIDOMWindow)
-           .QueryInterface(Components.interfaces.nsIDOMChromeWindow);
-  return chromeWin;
-}
-
-function getWindows(type) {
-  if (type == undefined) {
-      type = "";
-  }
-  var windows = []
-  var enumerator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-                     .getService(Components.interfaces.nsIWindowMediator)
-                     .getEnumerator(type);
-  while(enumerator.hasMoreElements()) {
-    windows.push(enumerator.getNext());
-  }
-  if (type == "") {
-    windows.push(hwindow);
-  }
-  return windows;
-}
-
-function getMethodInWindows (methodName) {
-  for each(w in getWindows()) {
-    if (w[methodName] != undefined) {
-      return w[methodName];
-    }
-  }
-  throw new Error("Method with name: '" + methodName + "' is not in any open window.");
-}
-
-function getWindowByTitle(title) {
-  for each(w in getWindows()) {
-    if (w.document.title && w.document.title == title) {
-      return w;
-    }
-  }
-}
-
-function getWindowByType(type) {
-  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-           .getService(Components.interfaces.nsIWindowMediator);
-  return wm.getMostRecentWindow(type);
-}
-
-function tempfile(appention) {
-  if (appention == undefined) {
-    var appention = "mozmill.utils.tempfile"
-  }
-	var tempfile = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties).get("TmpD", Components.interfaces.nsIFile);
-	tempfile.append(uuidgen.generateUUID().toString().replace('-', '').replace('{', '').replace('}',''))
-	tempfile.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0777);
-	tempfile.append(appention);
-	tempfile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
-	// do whatever you need to the created file
-	return tempfile.clone()
-}
-
-var checkChrome = function() {
-   var loc = window.document.location.href;
-   try {
-       loc = window.top.document.location.href;
-   } catch (e) {}
-
-   if (/^chrome:\/\//.test(loc)) { return true; }
-   else { return false; }
-}
-
-
- var runFile = function(w){
-   //define the interface
-   var nsIFilePicker = Components.interfaces.nsIFilePicker;
-   var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
-   //define the file picker window
-   fp.init(w, "Select a File", nsIFilePicker.modeOpen);
-   fp.appendFilter("JavaScript Files","*.js");
-   //show the window
-   var res = fp.show();
-   //if we got a file
-   if (res == nsIFilePicker.returnOK){
-     var thefile = fp.file;
-     //create the paramObj with a files array attrib
-     var paramObj = {};
-     paramObj.files = [];
-     paramObj.files.push(thefile.path);
-   }
- };
-
- var saveFile = function(w, content, filename){
-   //define the file interface
-   var file = Components.classes["@mozilla.org/file/local;1"]
-                        .createInstance(Components.interfaces.nsILocalFile);
-   //point it at the file we want to get at
-   file.initWithPath(filename);
-
-   // file is nsIFile, data is a string
-   var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
-                            .createInstance(Components.interfaces.nsIFileOutputStream);
-
-   // use 0x02 | 0x10 to open file for appending.
-   foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
-   // write, create, truncate
-   // In a c file operation, we have no need to set file mode with or operation,
-   // directly using "r" or "w" usually.
-
-   foStream.write(content, content.length);
-   foStream.close();
- };
-
-  var saveAsFile = function(w, content){
-     //define the interface
-     var nsIFilePicker = Components.interfaces.nsIFilePicker;
-     var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
-     //define the file picker window
-     fp.init(w, "Select a File", nsIFilePicker.modeSave);
-     fp.appendFilter("JavaScript Files","*.js");
-     //show the window
-     var res = fp.show();
-     //if we got a file
-     if ((res == nsIFilePicker.returnOK) || (res == nsIFilePicker.returnReplace)){
-       var thefile = fp.file;
-
-       //forcing the user to save as a .js file
-       if (thefile.path.indexOf(".js") == -1){
-         //define the file interface
-         var file = Components.classes["@mozilla.org/file/local;1"]
-                              .createInstance(Components.interfaces.nsILocalFile);
-         //point it at the file we want to get at
-         file.initWithPath(thefile.path+".js");
-         var thefile = file;
-       }
-
-       // file is nsIFile, data is a string
-       var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
-                               .createInstance(Components.interfaces.nsIFileOutputStream);
-
-       // use 0x02 | 0x10 to open file for appending.
-       foStream.init(thefile, 0x02 | 0x08 | 0x20, 0666, 0);
-       // write, create, truncate
-       // In a c file operation, we have no need to set file mode with or operation,
-       // directly using "r" or "w" usually.
-       foStream.write(content, content.length);
-       foStream.close();
-       return thefile.path;
-     }
-  };
-
- var openFile = function(w){
-    //define the interface
-    var nsIFilePicker = Components.interfaces.nsIFilePicker;
-    var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
-    //define the file picker window
-    fp.init(w, "Select a File", nsIFilePicker.modeOpen);
-    fp.appendFilter("JavaScript Files","*.js");
-    //show the window
-    var res = fp.show();
-    //if we got a file
-    if (res == nsIFilePicker.returnOK){
-      var thefile = fp.file;
-      //create the paramObj with a files array attrib
-      var data = getFile(thefile.path);
-
-      return {path:thefile.path, data:data};
-    }
-  };
-
- var getFile = function(path){
-   //define the file interface
-   var file = Components.classes["@mozilla.org/file/local;1"]
-                        .createInstance(Components.interfaces.nsILocalFile);
-   //point it at the file we want to get at
-   file.initWithPath(path);
-   // define file stream interfaces
-   var data = "";
-   var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
-                           .createInstance(Components.interfaces.nsIFileInputStream);
-   var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
-                           .createInstance(Components.interfaces.nsIScriptableInputStream);
-   fstream.init(file, -1, 0, 0);
-   sstream.init(fstream);
-
-   //pull the contents of the file out
-   var str = sstream.read(4096);
-   while (str.length > 0) {
-     data += str;
-     str = sstream.read(4096);
-   }
-
-   sstream.close();
-   fstream.close();
-
-   //data = data.replace(/\r|\n|\r\n/g, "");
-   return data;
- };
-
-/**
- * Called to get the state of an individual preference.
- *
- * @param aPrefName     string The preference to get the state of.
- * @param aDefaultValue any    The default value if preference was not found.
- *
- * @returns any The value of the requested preference
- *
- * @see setPref
- * Code by Henrik Skupin: <hskupin@gmail.com>
- */
-function getPreference(aPrefName, aDefaultValue) {
-  try {
-    var branch = Components.classes["@mozilla.org/preferences-service;1"].
-                 getService(Components.interfaces.nsIPrefBranch);
-    switch (typeof aDefaultValue) {
-      case ('boolean'):
-        return branch.getBoolPref(aPrefName);
-      case ('string'):
-        return branch.getCharPref(aPrefName);
-      case ('number'):
-        return branch.getIntPref(aPrefName);
-      default:
-        return branch.getComplexValue(aPrefName);
-    }
-  } catch(e) {
-    return aDefaultValue;
-  }
-}
-
-/**
- * Called to set the state of an individual preference.
- *
- * @param aPrefName string The preference to set the state of.
- * @param aValue    any    The value to set the preference to.
- *
- * @returns boolean Returns true if value was successfully set.
- *
- * @see getPref
- * Code by Henrik Skupin: <hskupin@gmail.com>
- */
-function setPreference(aName, aValue) {
-  try {
-    var branch = Components.classes["@mozilla.org/preferences-service;1"].
-                 getService(Components.interfaces.nsIPrefBranch);
-    switch (typeof aValue) {
-      case ('boolean'):
-        branch.setBoolPref(aName, aValue);
-        break;
-      case ('string'):
-        branch.setCharPref(aName, aValue);
-        break;
-      case ('number'):
-        branch.setIntPref(aName, aValue);
-        break;
-      default:
-        branch.setComplexValue(aName, aValue);
-    }
-  } catch(e) {
-    return false;
-  }
-
-  return true;
-}
-
-/**
- * Sleep for the given amount of milliseconds
- *
- * @param {number} milliseconds
- *        Sleeps the given number of milliseconds
- */
-function sleep(milliseconds) {
-  // We basically just call this once after the specified number of milliseconds
-  var timeup = false;
-  function wait() { timeup = true; }
-  hwindow.setTimeout(wait, milliseconds);
-
-  var thread = Components.classes["@mozilla.org/thread-manager;1"].
-               getService().currentThread;
-  while(!timeup) {
-    thread.processNextEvent(true);
-  }
-}
-
-/**
- * Check if the callback function evaluates to true
- */
-function assert(callback, message, thisObject) {
-  var result = callback.call(thisObject);
-
-  if (!result) {
-    throw new Error(message || arguments.callee.name + ": Failed for '" + callback + "'");
-  }
-
-  return true;
-}
-	
-/**
- * Unwraps a node which is wrapped into a XPCNativeWrapper or XrayWrapper
- *
- * @param {DOMnode} Wrapped DOM node
- * @returns {DOMNode} Unwrapped DOM node
- */
-function unwrapNode(aNode) {
-  var node = aNode;
-  if (node) {
-    // unwrap is not available on older branches (3.5 and 3.6) - Bug 533596
-    if ("unwrap" in XPCNativeWrapper) {	
-      node = XPCNativeWrapper.unwrap(node);
-    }
-    else if (node.wrappedJSObject != null) {
-      node = node.wrappedJSObject;
-    }
-  }
-  return node;
-}
-
-/**
- * TimeoutError
- *
- * Error object used for timeouts
- */
-function TimeoutError(message, fileName, lineNumber) {
-  var err = new Error();
-  if (err.stack) {
-    this.stack = err.stack;
-  }
-  this.message = message === undefined ? err.message : message;
-  this.fileName = fileName === undefined ? err.fileName : fileName;
-  this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
-};
-TimeoutError.prototype = new Error();
-TimeoutError.prototype.constructor = TimeoutError;
-TimeoutError.prototype.name = 'TimeoutError';
-
-/**
- * Waits for the callback evaluates to true
- */
-function waitFor(callback, message, timeout, interval, thisObject) {
-  timeout = timeout || 5000;
-  interval = interval || 100;
-
-  var self = {counter: 0, result: callback.call(thisObject)};
-
-  function wait() {
-    self.counter += interval;
-    self.result = callback.call(thisObject);
-  }
-
-  var timeoutInterval = hwindow.setInterval(wait, interval);
-  var thread = Components.classes["@mozilla.org/thread-manager;1"].
-               getService().currentThread;
-
-  while((self.result != true) && (self.counter < timeout))  {
-    thread.processNextEvent(true);
-  }
-
-  hwindow.clearInterval(timeoutInterval);
-
-  if (self.counter >= timeout) {
-    message = message || arguments.callee.name + ": Timeout exceeded for '" + callback + "'";
-    throw new TimeoutError(message);
-  }
-
-  return true;
-}
-
-/**
- * Calculates the x and y chrome offset for an element
- * See https://developer.mozilla.org/en/DOM/window.innerHeight
- *
- * Note this function will not work if the user has custom toolbars (via extension) at the bottom or left/right of the screen
- */
-function getChromeOffset(elem) {
-  var win = elem.ownerDocument.defaultView;
-  // Calculate x offset
-  var chromeWidth = 0;
-  if (win["name"] != "sidebar") {
-    chromeWidth = win.outerWidth - win.innerWidth;
-  }
-
-  // Calculate y offset
-  var chromeHeight = win.outerHeight - win.innerHeight;
-  // chromeHeight == 0 means elem is already in the chrome and doesn't need the addonbar offset
-  if (chromeHeight > 0) {
-    // window.innerHeight doesn't include the addon or find bar, so account for these if present
-    var addonbar = win.document.getElementById("addon-bar");
-    if (addonbar) {
-      chromeHeight -= addonbar.scrollHeight;
-    }
-    var findbar = win.document.getElementById("FindToolbar");
-    if (findbar) {
-      chromeHeight -= findbar.scrollHeight;
-    }
-  }
-
-  return {'x':chromeWidth, 'y':chromeHeight};
-}
-
-/**
- * Takes a screenshot of the specified DOM node
- */
-function takeScreenshot(node, name, highlights) {
-  var rect, win, width, height, left, top, needsOffset;
-  // node can be either a window or an arbitrary DOM node
-  try {
-    win = node.ownerDocument.defaultView;   // node is an arbitrary DOM node
-    rect = node.getBoundingClientRect();
-    width = rect.width;
-    height = rect.height;
-    top = rect.top;
-    left = rect.left;
-    // offset for highlights not needed as they will be relative to this node
-    needsOffset = false;
-  } catch (e) {
-    win = node;                             // node is a window
-    width = win.innerWidth;
-    height = win.innerHeight;
-    top = 0;
-    left = 0;
-    // offset needed for highlights to take 'outerHeight' of window into account
-    needsOffset = true;
-  }
-
-  var canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
-  canvas.width = width;
-  canvas.height = height;
-
-  var ctx = canvas.getContext("2d");
-  // Draws the DOM contents of the window to the canvas
-  ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)");
-
-  // This section is for drawing a red rectangle around each element passed in via the highlights array
-  if (highlights) {
-    ctx.lineWidth = "2";
-    ctx.strokeStyle = "red";
-    ctx.save();
-
-    for (var i = 0; i < highlights.length; ++i) {
-      var elem = highlights[i];
-      rect = elem.getBoundingClientRect();
-
-      var offsetY = 0, offsetX = 0;
-      if (needsOffset) {
-        var offset = getChromeOffset(elem);
-        offsetX = offset.x;
-        offsetY = offset.y;
-      } else {
-        // Don't need to offset the window chrome, just make relative to containing node
-        offsetY = -top;
-        offsetX = -left;
-      }
-
-      // Draw the rectangle
-      ctx.strokeRect(rect.left + offsetX, rect.top + offsetY, rect.width, rect.height);
-    }
-  } // end highlights
-
-  // if there is a name save the file, else return dataURL
-  if (name) {
-    return saveCanvas(canvas, name);
-  }
-  return canvas.toDataURL("image/png","");
-}
-
-/**
- * Takes a canvas as input and saves it to the file tempdir/name.png
- * Returns the filepath of the saved file
- */
-function saveCanvas(canvas, name) {
-  var file = Components.classes["@mozilla.org/file/directory_service;1"]
-                                .getService(Components.interfaces.nsIProperties)
-                                .get("TmpD", Components.interfaces.nsIFile);
-  file.append("mozmill_screens");
-  file.append(name + ".png");
-  file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
-
-  // create a data url from the canvas and then create URIs of the source and targets
-  var io = Components.classes["@mozilla.org/network/io-service;1"]
-                              .getService(Components.interfaces.nsIIOService);
-  var source = io.newURI(canvas.toDataURL("image/png", ""), "UTF8", null);
-  var target = io.newFileURI(file)
-
-  // prepare to save the canvas data
-  var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
-                                   .createInstance(Components.interfaces.nsIWebBrowserPersist);
-
-  persist.persistFlags = Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
-  persist.persistFlags |= Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
-
-  // save the canvas data to the file
-  persist.saveURI(source, null, null, null, null, file);
-
-  return file.path;
-}
new file mode 100644
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/modules/windows.js
@@ -0,0 +1,292 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["init", "map"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+// imports
+var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
+
+var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+
+/**
+ * The window map is used to store information about the current state of
+ * open windows, e.g. loaded state
+ */
+var map = {
+  _windows : { },
+
+  /**
+   * Check if a given window id is contained in the map of windows
+   *
+   * @param {Number} aWindowId
+   *        Outer ID of the window to check.
+   * @returns {Boolean} True if the window is part of the map, otherwise false.
+   */
+  contains : function (aWindowId) {
+    return (aWindowId in this._windows);
+  },
+
+  /**
+   * Retrieve the value of the specified window's property.
+   *
+   * @param {Number} aWindowId
+   *        Outer ID of the window to check.
+   * @param {String} aProperty
+   *        Property to retrieve the value from
+   * @return {Object} Value of the window's property
+   */
+  getValue : function (aWindowId, aProperty) {
+    if (!this.contains(aWindowId)) {
+      return undefined;
+    } else {
+      var win = this._windows[aWindowId];
+
+      return (aProperty in win) ? win[aProperty]
+                                : undefined;
+    }
+  },
+
+  /**
+   * Remove the entry for a given window
+   *
+   * @param {Number} aWindowId
+   *        Outer ID of the window to check.
+   */
+  remove : function (aWindowId) {
+    if (this.contains(aWindowId)) {
+      delete this._windows[aWindowId];
+    }
+
+    // dump("* current map: " + JSON.stringify(this._windows) + "\n");
+  },
+
+  /**
+   * Update the property value of a given window
+   *
+   * @param {Number} aWindowId
+   *        Outer ID of the window to check.
+   * @param {String} aProperty
+   *        Property to update the value for
+   * @param {Object}
+   *        Value to set
+   */
+  update : function (aWindowId, aProperty, aValue) {
+    if (!this.contains(aWindowId)) {
+      this._windows[aWindowId] = { };
+    }
+
+    this._windows[aWindowId][aProperty] = aValue;
+    // dump("* current map: " + JSON.stringify(this._windows) + "\n");
+  },
+
+  /**
+   * Update the internal loaded state of the given content window. To identify
+   * an active (re)load action we make use of an uuid.
+   *
+   * @param {Window} aId - The outer id of the window to update
+   * @param {Boolean} aIsLoaded - Has the window been loaded
+   */
+  updatePageLoadStatus : function (aId, aIsLoaded) {
+    this.update(aId, "loaded", aIsLoaded);
+
+    var uuid = this.getValue(aId, "id_load_in_transition");
+
+    // If no uuid has been set yet or when the page gets unloaded create a new id
+    if (!uuid || !aIsLoaded) {
+      uuid = uuidgen.generateUUID();
+      this.update(aId, "id_load_in_transition", uuid);
+    }
+
+    // dump("*** Page status updated: id=" + aId + ", loaded=" + aIsLoaded + ", uuid=" + uuid + "\n");
+  },
+
+  /**
+   * This method only applies to content windows, where we have to check if it has
+   * been successfully loaded or reloaded. An uuid allows us to wait for the next
+   * load action triggered by e.g. controller.open().
+   *
+   * @param {Window} aId - The outer id of the content window to check
+   *
+   * @returns {Boolean} True if the content window has been loaded
+   */
+  hasPageLoaded : function (aId) {
+    var load_current = this.getValue(aId, "id_load_in_transition");
+    var load_handled = this.getValue(aId, "id_load_handled");
+
+    var isLoaded = this.contains(aId) && this.getValue(aId, "loaded") &&
+                   (load_current !== load_handled);
+
+    if (isLoaded) {
+      // Backup the current uuid so we can check later if another page load happened.
+      this.update(aId, "id_load_handled", load_current);
+    }
+
+    // dump("** Page has been finished loading: id=" + aId + ", status=" + isLoaded + ", uuid=" + load_current + "\n");
+
+    return isLoaded;
+  }
+};
+
+
+// Observer when a new top-level window is ready
+var windowReadyObserver = {
+  observe: function (aSubject, aTopic, aData) {
+    // Not in all cases we get a ChromeWindow. So ensure we really operate
+    // on such an instance. Otherwise load events will not be handled.
+    var win = utils.getChromeWindow(aSubject);
+
+    // var id = utils.getWindowId(win);
+    // dump("*** 'toplevel-window-ready' observer notification: id=" + id + "\n");
+    attachEventListeners(win);
+  }
+};
+
+
+// Observer when a top-level window is closed
+var windowCloseObserver = {
+  observe: function (aSubject, aTopic, aData) {
+    var id = utils.getWindowId(aSubject);
+    // dump("*** 'outer-window-destroyed' observer notification: id=" + id + "\n");
+
+    map.remove(id);
+  }
+};
+
+// Bug 915554
+// Support for the old Private Browsing Mode (eg. ESR17)
+// TODO: remove once ESR17 is no longer supported
+var enterLeavePrivateBrowsingObserver = {
+  observe: function (aSubject, aTopic, aData) {
+    handleAttachEventListeners();
+  }
+};
+
+/**
+ * Attach event listeners
+ *
+ * @param {ChromeWindow} aWindow
+ *        Window to attach listeners on.
+ */
+function attachEventListeners(aWindow) {
+  // These are the event handlers
+  var pageShowHandler = function (aEvent) {
+    var doc = aEvent.originalTarget;
+
+    // Only update the flag if we have a document as target
+    // see https://bugzilla.mozilla.org/show_bug.cgi?id=690829
+    if ("defaultView" in doc) {
+      var id = utils.getWindowId(doc.defaultView);
+      // dump("*** 'pageshow' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
+      map.updatePageLoadStatus(id, true);
+    }
+
+    // We need to add/remove the unload/pagehide event listeners to preserve caching.
+    aWindow.addEventListener("beforeunload", beforeUnloadHandler, true);
+    aWindow.addEventListener("pagehide", pageHideHandler, true);
+  };
+
+  var DOMContentLoadedHandler = function (aEvent) {
+    var doc = aEvent.originalTarget;
+
+    // Only update the flag if we have a document as target
+    if ("defaultView" in doc) {
+      var id = utils.getWindowId(doc.defaultView);
+      // dump("*** 'DOMContentLoaded' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
+
+      // We only care about error pages for DOMContentLoaded
+      var errorRegex = /about:.+(error)|(blocked)\?/;
+      if (errorRegex.exec(doc.baseURI)) {
+        // Wait about 1s to be sure the DOM is ready
+        utils.sleep(1000);
+
+        map.updatePageLoadStatus(id, true);
+      }
+
+      // We need to add/remove the unload event listener to preserve caching.
+      aWindow.addEventListener("beforeunload", beforeUnloadHandler, true);
+    }
+  };
+
+  // beforeunload is still needed because pagehide doesn't fire before the page is unloaded.
+  // still use pagehide for cases when beforeunload doesn't get fired
+  var beforeUnloadHandler = function (aEvent) {
+    var doc = aEvent.originalTarget;
+
+    // Only update the flag if we have a document as target
+    if ("defaultView" in doc) {
+      var id = utils.getWindowId(doc.defaultView);
+      // dump("*** 'beforeunload' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
+      map.updatePageLoadStatus(id, false);
+    }
+
+    aWindow.removeEventListener("beforeunload", beforeUnloadHandler, true);
+  };
+
+  var pageHideHandler = function (aEvent) {
+    var doc = aEvent.originalTarget;
+
+    // Only update the flag if we have a document as target
+    if ("defaultView" in doc) {
+      var id = utils.getWindowId(doc.defaultView);
+      // dump("*** 'pagehide' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
+      map.updatePageLoadStatus(id, false);
+    }
+    // If event.persisted is true the beforeUnloadHandler would never fire
+    // and we have to remove the event handler here to avoid memory leaks.
+    if (aEvent.persisted)
+      aWindow.removeEventListener("beforeunload", beforeUnloadHandler, true);
+  };
+
+  var onWindowLoaded = function (aEvent) {
+    var id = utils.getWindowId(aWindow);
+    // dump("*** 'load' event: id=" + id + ", baseURI=" + aWindow.document.baseURI + "\n");
+
+    map.update(id, "loaded", true);
+
+    // Note: Error pages will never fire a "pageshow" event. For those we
+    // have to wait for the "DOMContentLoaded" event. That's the final state.
+    // Error pages will always have a baseURI starting with
+    // "about:" followed by "error" or "blocked".
+    aWindow.addEventListener("DOMContentLoaded", DOMContentLoadedHandler, true);
+
+    // Page is ready
+    aWindow.addEventListener("pageshow", pageShowHandler, true);
+
+    // Leave page (use caching)
+    aWindow.addEventListener("pagehide", pageHideHandler, true);
+  };
+
+  // If the window has already been finished loading, call the load handler
+  // directly. Otherwise attach it to the current window.
+  if (aWindow.document.readyState === 'complete') {
+    onWindowLoaded();
+  } else {
+    aWindow.addEventListener("load", onWindowLoaded, false);
+  }
+}
+
+// Attach event listeners to all already open top-level windows
+function handleAttachEventListeners() {
+  var enumerator = Cc["@mozilla.org/appshell/window-mediator;1"].
+                   getService(Ci.nsIWindowMediator).getEnumerator("");
+  while (enumerator.hasMoreElements()) {
+    var win = enumerator.getNext();
+    attachEventListeners(win);
+  }
+}
+
+function init() {
+  // Activate observer for new top level windows
+  var observerService = Cc["@mozilla.org/observer-service;1"].
+                        getService(Ci.nsIObserverService);
+  observerService.addObserver(windowReadyObserver, "toplevel-window-ready", false);
+  observerService.addObserver(windowCloseObserver, "outer-window-destroyed", false);
+  observerService.addObserver(enterLeavePrivateBrowsingObserver, "private-browsing", false);
+
+  handleAttachEventListeners();
+}
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js
+++ b/services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js
@@ -1,355 +1,588 @@
-/* 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/. */
-
 // Export all available functions for Mozmill
-var EXPORTED_SYMBOLS = ["sendMouseEvent", "sendChar", "sendString", "sendKey",
-                        "synthesizeMouse", "synthesizeMouseScroll", "synthesizeKey",
+var EXPORTED_SYMBOLS = ["disableNonTestMouseEvents","sendMouseEvent", "sendChar",
+                        "sendString", "sendKey", "synthesizeMouse", "synthesizeTouch",
+                        "synthesizeMouseAtPoint", "synthesizeTouchAtPoint",
+                        "synthesizeMouseAtCenter", "synthesizeTouchAtCenter",
+                        "synthesizeWheel", "synthesizeKey",
                         "synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent",
-                        "synthesizeDragStart", "synthesizeDrop", "synthesizeText",
-                        "disableNonTestMouseEvents", "synthesizeComposition",
-                        "synthesizeQuerySelectedText", "synthesizeQueryTextContent",
-                        "synthesizeQueryCaretRect", "synthesizeQueryTextRect",
-                        "synthesizeQueryEditorRect", "synthesizeCharAtPoint",
-                        "synthesizeSelectionSet"];
+                        "synthesizeText",
+                        "synthesizeComposition", "synthesizeQuerySelectedText"];
+
+const Ci = Components.interfaces;