Bug 1153832: New dispatch style framework in Marionette listener
☠☠ backed out by e026141c2ce9 ☠ ☠
authorAndreas Tolfsen <ato@mozilla.com>
Wed, 15 Apr 2015 12:18:00 +0100
changeset 258266 043a824dd7b749192a8c7ec3f1a8d3ba4d2619d0
parent 258211 9ae890059b3c2580a0e3ee31f39c6047798464e4
child 258267 9f2ad189edc93b4f9d8baec7311040b6ca871b5b
push id8007
push userraliiev@mozilla.com
push dateMon, 11 May 2015 19:23:16 +0000
treeherdermozilla-aurora@e2ce1aac996e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1153832, 1107706
milestone40.0a1
Bug 1153832: New dispatch style framework in Marionette listener Takes advantage of the new dispatching technique introduced in bug 1107706 on the content side. The patch introduces the framework to write simpler command handlers in content space, but does not convert all commands in listener.js to use this. This can be done gradually, as both techniques are still compatible. r=dburns
testing/marionette/driver.js
testing/marionette/elements.js
testing/marionette/error.js
testing/marionette/listener.js
testing/marionette/sendkeys.js
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -150,18 +150,17 @@ ListenerProxy.prototype.__noSuchMethod__
       this.mm.addMessageListener(val, removeListeners(val, valListener));
       this.mm.addMessageListener(err, removeListeners(err, errListener));
     };
     listeners.remove = () =>
         listeners.map(l => this.mm.removeMessageListener(l[0], l[1]));
 
     let okListener = () => resolve();
     let valListener = msg => resolve(msg.json.value);
-    let errListener = msg => reject(
-        "error" in msg.objects ? msg.objects.error : msg.json);
+    let errListener = msg => reject(msg.objects.error);
 
     let handleDialog = function(subject, topic) {
       listeners.remove();
       modal.removeHandler(handleDialog);
       this.sendAsync("cancelRequest");
       resolve();
     }.bind(this);
 
@@ -2057,17 +2056,17 @@ GeckoDriver.prototype.clickElement = fun
       break;
 
     case Context.CONTENT:
       // We need to protect against the click causing an OOP frame to close.
       // This fires the mozbrowserclose event when it closes so we need to
       // listen for it and then just send an error back. The person making the
       // call should be aware something isnt right and handle accordingly
       this.addFrameCloseListener("click");
-      yield this.listener.clickElement({id: id});
+      yield this.listener.clickElement(id);
       break;
   }
 };
 
 /**
  * Get a given attribute of an element.
  *
  * @param {string} id
@@ -2081,17 +2080,17 @@ GeckoDriver.prototype.getElementAttribut
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
       let el = this.curBrowser.elementManager.getKnownElement(id, win);
       resp.value = utils.getElementAttribute(el, name);
       break;
 
     case Context.CONTENT:
-      resp.value = yield this.listener.getElementAttribute({id: id, name: name});
+      resp.value = yield this.listener.getElementAttribute(id, name);
       break;
   }
 };
 
 /**
  * Get the text of an element, if any.  Includes the text of all child
  * elements.
  *
@@ -2107,17 +2106,17 @@ GeckoDriver.prototype.getElementText = f
       let win = this.getCurrentWindow();
       let el = this.curBrowser.elementManager.getKnownElement(id, win);
       let lines = [];
       this.getVisibleText(el, lines);
       resp.value = lines.join("\n");
       break;
 
     case Context.CONTENT:
-      resp.value = yield this.listener.getElementText({id: id});
+      resp.value = yield this.listener.getElementText(id);
       break;
   }
 };
 
 /**
  * Get the tag name of the element.
  *
  * @param {string} id
@@ -2129,17 +2128,17 @@ GeckoDriver.prototype.getElementTagName 
   switch (this.context) {
     case Context.CHROME:
       let win = this.getCurrentWindow();
       let el = this.curBrowser.elementManager.getKnownElement(id, win);
       resp.value = el.tagName.toLowerCase();
       break;
 
     case Context.CONTENT:
-      resp.value = yield this.listener.getElementTagName({id: id});
+      resp.value = yield this.listener.getElementTagName(id);
       break;
   }
 };
 
 /**
  * Check if element is displayed.
  *
  * @param {string} id
@@ -2219,17 +2218,17 @@ GeckoDriver.prototype.isElementEnabled =
     case Context.CHROME:
       // Selenium atom doesn't quite work here
       let win = this.getCurrentWindow();
       let el = this.curBrowser.elementManager.getKnownElement(id, win);
       resp.value = !(!!el.disabled);
       break;
 
     case Context.CONTENT:
-      resp.value = yield this.listener.isElementEnabled({id: id});
+      resp.value = yield this.listener.isElementEnabled(id);
       break;
   }
 },
 
 /**
  * Check if element is selected.
  *
  * @param {string} id
@@ -2265,17 +2264,17 @@ GeckoDriver.prototype.getElementSize = f
     case Context.CHROME:
       let win = this.getCurrentWindow();
       let el = this.curBrowser.elementManager.getKnownElement(id, win);
       let rect = el.getBoundingClientRect();
       resp.value = {width: rect.width, height: rect.height};
       break;
 
     case Context.CONTENT:
-      resp.value = yield this.listener.getElementSize({id: id});
+      resp.value = yield this.listener.getElementSize(id);
       break;
   }
 };
 
 GeckoDriver.prototype.getElementRect = function(cmd, resp) {
   let id = cmd.parameters.id;
 
   switch (this.context) {
@@ -2287,17 +2286,17 @@ GeckoDriver.prototype.getElementRect = f
         x: rect.x + win.pageXOffset,
         y: rect.y + win.pageYOffset,
         width: rect.width,
         height: rect.height
       };
       break;
 
     case Context.CONTENT:
-      resp.value = yield this.listener.getElementRect({id: id});
+      resp.value = yield this.listener.getElementRect(id);
       break;
   }
 };
 
 /**
  * Send key presses to element after focusing on it.
  *
  * @param {string} id
--- a/testing/marionette/elements.js
+++ b/testing/marionette/elements.js
@@ -1,13 +1,16 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+let {utils: Cu} = Components;
+
+Cu.import("chrome://marionette/content/error.js");
+
 /**
  * The ElementManager manages DOM references and interactions with elements.
  * According to the WebDriver spec (http://code.google.com/p/selenium/wiki/JsonWireProtocol), the
  * server sends the client an element reference, and maintains the map of reference to element.
  * The client uses this reference when querying/interacting with the element, and the
  * server uses maps this reference to the actual element when it executes the command.
  */
 
@@ -23,36 +26,30 @@ this.EXPORTED_SYMBOLS = [
   "TAG",
   "XPATH",
   "ANON",
   "ANON_ATTRIBUTE"
 ];
 
 const DOCUMENT_POSITION_DISCONNECTED = 1;
 
-let uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
-             .getService(Components.interfaces.nsIUUIDGenerator);
+const uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
+    .getService(Components.interfaces.nsIUUIDGenerator);
 
 this.CLASS_NAME = "class name";
 this.SELECTOR = "css selector";
 this.ID = "id";
 this.NAME = "name";
 this.LINK_TEXT = "link text";
 this.PARTIAL_LINK_TEXT = "partial link text";
 this.TAG = "tag name";
 this.XPATH = "xpath";
 this.ANON= "anon";
 this.ANON_ATTRIBUTE = "anon attribute";
 
-function ElementException(msg, num, stack) {
-  this.message = msg;
-  this.code = num;
-  this.stack = stack;
-}
-
 this.Accessibility = function Accessibility() {
   // A flag indicating whether the accessibility issue should be logged or cause
   // an exception. Default: log to stdout.
   this.strict = false;
   // An interface for in-process accessibility clients
   // Note: we access it lazily to not enable accessibility when it is not needed
   Object.defineProperty(this, 'accessibleRetrieval', {
     configurable: true,
@@ -180,17 +177,17 @@ Accessibility.prototype = {
    * Send an error message or log the error message in the log
    * @param String message
    */
   handleErrorMessage(message) {
     if (!message) {
       return;
     }
     if (this.strict) {
-      throw new ElementException(message, 56, null);
+      throw new ElementNotAccessibleError(message);
     }
     dump(Date.now() + " Marionette: " + message);
   }
 };
 
 this.ElementManager = function ElementManager(notSupported) {
   this.seenItems = {};
   this.timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
@@ -219,29 +216,27 @@ ElementManager.prototype = {
   * @return string
   *        Returns the server-assigned reference ID
   */
   addToKnownElements: function EM_addToKnownElements(element) {
     for (let i in this.seenItems) {
       let foundEl = null;
       try {
         foundEl = this.seenItems[i].get();
-      }
-      catch(e) {}
+      } catch (e) {}
       if (foundEl) {
         if (XPCNativeWrapper(foundEl) == XPCNativeWrapper(element)) {
           return i;
         }
-      }
-      else {
-        //cleanup reference to GC'd element
+      } else {
+        // cleanup reference to GC'd element
         delete this.seenItems[i];
       }
     }
-    var id = uuidGen.generateUUID().toString();
+    let id = uuidGen.generateUUID().toString();
     this.seenItems[id] = Components.utils.getWeakReference(element);
     return id;
   },
 
   /**
    * Retrieve element from its unique ID
    *
    * @param String id
@@ -250,33 +245,34 @@ ElementManager.prototype = {
    *        The window that contains the element
    *
    * @returns nsIDOMElement
    *        Returns the element or throws Exception if not found
    */
   getKnownElement: function EM_getKnownElement(id, win) {
     let el = this.seenItems[id];
     if (!el) {
-      throw new ElementException("Element has not been seen before. Id given was " + id, 17, null);
+      throw new JavaScriptError("Element has not been seen before. Id given was " + id);
     }
     try {
       el = el.get();
     }
     catch(e) {
       el = null;
       delete this.seenItems[id];
     }
     // use XPCNativeWrapper to compare elements; see bug 834266
     let wrappedWin = XPCNativeWrapper(win);
     if (!el ||
         !(XPCNativeWrapper(el).ownerDocument == wrappedWin.document) ||
         (XPCNativeWrapper(el).compareDocumentPosition(wrappedWin.document.documentElement) &
          DOCUMENT_POSITION_DISCONNECTED)) {
-      throw new ElementException("The element reference is stale. Either the element " +
-                                 "is no longer attached to the DOM or the page has been refreshed.", 10, null);
+      throw new StaleElementReferenceError(
+          "The element reference is stale. Either the element " +
+          "is no longer attached to the DOM or the page has been refreshed.");
     }
     return el;
   },
 
   /**
    * Convert values to primitives that can be transported over the
    * Marionette protocol.
    *
@@ -364,18 +360,19 @@ ElementManager.prototype = {
             converted.push(this.convertWrappedArguments(args[i], win));
           }
         }
         else if (((typeof(args[this.elementKey]) === 'string') && args.hasOwnProperty(this.elementKey)) ||
                  ((typeof(args[this.w3cElementKey]) === 'string') &&
                      args.hasOwnProperty(this.w3cElementKey))) {
           let elementUniqueIdentifier = args[this.w3cElementKey] ? args[this.w3cElementKey] : args[this.elementKey];
           converted = this.getKnownElement(elementUniqueIdentifier,  win);
-          if (converted == null)
-            throw new ElementException("Unknown element: " + elementUniqueIdentifier, 500, null);
+          if (converted == null) {
+            throw new WebDriverError(`Unknown element: ${elementUniqueIdentifier}`);
+          }
         }
         else {
           converted = {};
           for (let prop in args) {
             converted[prop] = this.convertWrappedArguments(args[prop], win);
           }
         }
         break;
@@ -438,17 +435,17 @@ ElementManager.prototype = {
    * @return nsIDOMElement or list of nsIDOMElements
    *        Returns the element(s) by calling the on_success function.
    */
   find: function EM_find(win, values, searchTimeout, all, on_success, on_error, command_id) {
     let startTime = values.time ? values.time : new Date().getTime();
     let startNode = (values.element != undefined) ?
                     this.getKnownElement(values.element, win) : win.document;
     if (this.elementStrategies.indexOf(values.using) < 0) {
-      throw new ElementException("No such strategy.", 32, null);
+      throw new InvalidSelectorError(`No such strategy: ${values.using}`);
     }
     let found = all ? this.findElements(values.using, values.value, win.document, startNode) :
                       this.findElement(values.using, values.value, win.document, startNode);
     let type = Object.prototype.toString.call(found);
     let isArrayLike = ((type == '[object Array]') || (type == '[object HTMLCollection]') || (type == '[object NodeList]'));
     if (found == null || (isArrayLike && found.length <= 0)) {
       if (!searchTimeout || new Date().getTime() - startTime > searchTimeout) {
         if (all) {
@@ -456,17 +453,17 @@ ElementManager.prototype = {
         } else {
           // Format message depending on strategy if necessary
           let message = "Unable to locate element: " + values.value;
           if (values.using == ANON) {
             message = "Unable to locate anonymous children";
           } else if (values.using == ANON_ATTRIBUTE) {
             message = "Unable to locate anonymous element: " + JSON.stringify(values.value);
           }
-          on_error({message: message, code: 7}, command_id);
+          on_error(new NoSuchElementError(message), command_id);
         }
       } else {
         values.time = startTime;
         this.timer.initWithCallback(this.find.bind(this, win, values,
                                                    searchTimeout, all,
                                                    on_success, on_error,
                                                    command_id),
                                     100,
@@ -589,17 +586,17 @@ ElementManager.prototype = {
           element = element[0];
         }
         break;
       case ANON_ATTRIBUTE:
         let attr = Object.keys(value)[0];
         element = rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]);
         break;
       default:
-        throw new ElementException("No such strategy", 500, null);
+        throw new WebDriverError("No such strategy");
     }
     return element;
   },
 
   /**
    * Helper method to find. Finds all element using find's criteria
    *
    * @param string using
@@ -656,13 +653,13 @@ ElementManager.prototype = {
       case ANON_ATTRIBUTE:
         let attr = Object.keys(value)[0];
         let el = rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]);
         if (el != null) {
           elements = [el];
         }
         break;
       default:
-        throw new ElementException("No such strategy", 500, null);
+        throw new WebDriverError("No such strategy");
     }
     return elements;
   },
 }
--- a/testing/marionette/error.js
+++ b/testing/marionette/error.js
@@ -2,28 +2,31 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {utils: Cu} = Components;
 
 const errors = [
+  "ElementNotAccessibleError",
   "ElementNotVisibleError",
   "FrameSendFailureError",
   "FrameSendNotInitializedError",
   "IllegalArgumentError",
   "InvalidElementStateError",
+  "InvalidSelectorError",
   "JavaScriptError",
   "NoAlertOpenError",
   "NoSuchElementError",
   "NoSuchFrameError",
   "NoSuchWindowError",
   "ScriptTimeoutError",
   "SessionNotCreatedError",
+  "StaleElementReferenceError",
   "TimeoutError",
   "UnknownCommandError",
   "UnknownError",
   "UnsupportedOperationError",
   "WebDriverError",
 ];
 
 this.EXPORTED_SYMBOLS = ["error"].concat(errors);
@@ -34,21 +37,16 @@ error.toJSON = function(err) {
   return {
     message: err.message,
     stacktrace: err.stack || null,
     status: err.code
   };
 };
 
 /**
- * Gets WebDriver error by its Selenium status code number.
- */
-error.byCode = n => lookup.get(n);
-
-/**
  * Determines if the given status code is successful.
  */
 error.isSuccess = code => code === 0;
 
 /**
  * Old-style errors are objects that has all of the properties
  * "message", "code", and "stack".
  *
@@ -125,16 +123,24 @@ this.WebDriverError = function(msg) {
   Error.call(this, msg);
   this.name = "WebDriverError";
   this.message = msg;
   this.status = "webdriver error";
   this.code = 500;  // overridden
 };
 WebDriverError.prototype = Object.create(Error.prototype);
 
+this.ElementNotAccessibleError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "ElementNotAccessibleError";
+  this.status = "element not accessible";
+  this.code = 56;
+};
+ElementNotAccessibleError.prototype = Object.create(WebDriverError.prototype);
+
 this.ElementNotVisibleError = function(msg) {
   WebDriverError.call(this, msg);
   this.name = "ElementNotVisibleError";
   this.status = "element not visible";
   this.code = 11;
 };
 ElementNotVisibleError.prototype = Object.create(WebDriverError.prototype);
 
@@ -171,16 +177,24 @@ IllegalArgumentError.prototype = Object.
 this.InvalidElementStateError = function(msg) {
   WebDriverError.call(this, msg);
   this.name = "InvalidElementStateError";
   this.status = "invalid element state";
   this.code = 12;
 };
 InvalidElementStateError.prototype = Object.create(WebDriverError.prototype);
 
+this.InvalidSelectorError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "InvalidSelectorError";
+  this.status = "invalid selector";
+  this.code = 32;
+};
+InvalidSelectorError.prototype = Object.create(WebDriverError.prototype);
+
 /**
  * Creates an error message for a JavaScript error thrown during
  * executeScript or executeAsyncScript.
  *
  * @param {Error} err
  *     An Error object passed to a catch block or a message.
  * @param {string} fnName
  *     The name of the function to use in the stack trace message
@@ -265,19 +279,27 @@ this.ScriptTimeoutError = function(msg) 
 ScriptTimeoutError.prototype = Object.create(WebDriverError.prototype);
 
 this.SessionNotCreatedError = function(msg) {
   WebDriverError.call(this, msg);
   this.name = "SessionNotCreatedError";
   this.status = "session not created";
   // should be 33 to match Selenium
   this.code = 71;
-}
+};
 SessionNotCreatedError.prototype = Object.create(WebDriverError.prototype);
 
+this.StaleElementReferenceError = function(msg) {
+  WebDriverError.call(this, msg);
+  this.name = "StaleElementReferenceError";
+  this.status = "stale element reference";
+  this.code = 10;
+};
+StaleElementReferenceError.prototype = Object.create(WebDriverError.prototype);
+
 this.TimeoutError = function(msg) {
   WebDriverError.call(this, msg);
   this.name = "TimeoutError";
   this.status = "timeout";
   this.code = 21;
 };
 TimeoutError.prototype = Object.create(WebDriverError.prototype);
 
@@ -299,29 +321,8 @@ UnknownError.prototype = Object.create(W
 
 this.UnsupportedOperationError = function(msg) {
   WebDriverError.call(this, msg);
   this.name = "UnsupportedOperationError";
   this.status = "unsupported operation";
   this.code = 405;
 };
 UnsupportedOperationError.prototype = Object.create(WebDriverError.prototype);
-
-const errorObjs = [
-  this.ElementNotVisibleError,
-  this.FrameSendFailureError,
-  this.FrameSendNotInitializedError,
-  this.IllegalArgumentError,
-  this.InvalidElementStateError,
-  this.JavaScriptError,
-  this.NoAlertOpenError,
-  this.NoSuchElementError,
-  this.NoSuchFrameError,
-  this.NoSuchWindowError,
-  this.ScriptTimeoutError,
-  this.SessionNotCreatedError,
-  this.TimeoutError,
-  this.UnknownCommandError,
-  this.UnknownError,
-  this.UnsupportedOperationError,
-  this.WebDriverError,
-];
-const lookup = new Map(errorObjs.map(err => [new err().code, err]));
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -10,16 +10,17 @@ let uuidGen = Cc["@mozilla.org/uuid-gene
 
 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
                .getService(Ci.mozIJSSubScriptLoader);
 
 loader.loadSubScript("chrome://marionette/content/simpletest.js");
 loader.loadSubScript("chrome://marionette/content/common.js");
 loader.loadSubScript("chrome://marionette/content/actions.js");
 Cu.import("chrome://marionette/content/elements.js");
+Cu.import("chrome://marionette/content/error.js");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 let utils = {};
 utils.window = content;
 // Load Event/ChromeUtils for use with JS scripts:
 loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
 loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
@@ -88,17 +89,17 @@ let modalHandler = function() {
 };
 
 /**
  * Called when listener is first started up.
  * The listener sends its unique window ID and its current URI to the actor.
  * If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
  */
 function registerSelf() {
-  let msg = {value: winUtil.outerWindowID}
+  let msg = {value: winUtil.outerWindowID};
   // register will have the ID and a boolean describing if this is the main process or not
   let register = sendSyncMessage("Marionette:register", msg);
 
   if (register[0]) {
     let {id, remotenessChange} = register[0][0];
     listenerId = id;
     if (typeof id != "undefined") {
       // check if we're the main process
@@ -141,30 +142,61 @@ function emitTouchEventForIFrame(message
       typeForUtils = domWindowUtils.TOUCH_CONTACT;
       break;
   }
   domWindowUtils.sendNativeTouchPoint(identifier, typeForUtils,
     Math.round(message.screenX * ratio), Math.round(message.screenY * ratio),
     message.force, 90);
 }
 
+function dispatch(fn) {
+  return function(msg) {
+    let id = msg.json.command_id;
+    try {
+      let rv;
+      if (typeof msg.json == "undefined" || msg.json instanceof Array) {
+        rv = fn.apply(null, msg.json);
+      } else {
+        rv = fn(msg.json);
+      }
+
+      if (typeof rv == "undefined") {
+        sendOk(id);
+      } else {
+        sendResponse({value: rv}, id);
+      }
+    } catch (e) {
+      sendError(e, id);
+    }
+  };
+}
+
 /**
  * Add a message listener that's tied to our listenerId.
  */
 function addMessageListenerId(messageName, handler) {
   addMessageListener(messageName + listenerId, handler);
 }
 
 /**
  * Remove a message listener that's tied to our listenerId.
  */
 function removeMessageListenerId(messageName, handler) {
   removeMessageListener(messageName + listenerId, handler);
 }
 
+let getElementSizeFn = dispatch(getElementSize);
+let getActiveElementFn = dispatch(getActiveElement);
+let clickElementFn = dispatch(clickElement);
+let getElementAttributeFn = dispatch(getElementAttribute);
+let getElementTextFn = dispatch(getElementText);
+let getElementTagNameFn = dispatch(getElementTagName);
+let getElementRectFn = dispatch(getElementRect);
+let isElementEnabledFn = dispatch(isElementEnabled);
+
 /**
  * Start all message listeners
  */
 function startListeners() {
   addMessageListenerId("Marionette:newSession", newSession);
   addMessageListenerId("Marionette:executeScript", executeScript);
   addMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
   addMessageListenerId("Marionette:executeJSScript", executeJSScript);
@@ -177,27 +209,27 @@ function startListeners() {
   addMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl);
   addMessageListenerId("Marionette:getTitle", getTitle);
   addMessageListenerId("Marionette:getPageSource", getPageSource);
   addMessageListenerId("Marionette:goBack", goBack);
   addMessageListenerId("Marionette:goForward", goForward);
   addMessageListenerId("Marionette:refresh", refresh);
   addMessageListenerId("Marionette:findElementContent", findElementContent);
   addMessageListenerId("Marionette:findElementsContent", findElementsContent);
-  addMessageListenerId("Marionette:getActiveElement", getActiveElement);
-  addMessageListenerId("Marionette:clickElement", clickElement);
-  addMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
-  addMessageListenerId("Marionette:getElementText", getElementText);
-  addMessageListenerId("Marionette:getElementTagName", getElementTagName);
+  addMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
+  addMessageListenerId("Marionette:clickElement", clickElementFn);
+  addMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn);
+  addMessageListenerId("Marionette:getElementText", getElementTextFn);
+  addMessageListenerId("Marionette:getElementTagName", getElementTagNameFn);
   addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
   addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
   addMessageListenerId("Marionette:submitElement", submitElement);
-  addMessageListenerId("Marionette:getElementSize", getElementSize);
-  addMessageListenerId("Marionette:getElementRect", getElementRect);
-  addMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
+  addMessageListenerId("Marionette:getElementSize", getElementSizeFn);  // deprecated
+  addMessageListenerId("Marionette:getElementRect", getElementRectFn);
+  addMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
   addMessageListenerId("Marionette:isElementSelected", isElementSelected);
   addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   addMessageListenerId("Marionette:getElementLocation", getElementLocation); //deprecated
   addMessageListenerId("Marionette:clearElement", clearElement);
   addMessageListenerId("Marionette:switchToFrame", switchToFrame);
   addMessageListenerId("Marionette:deleteSession", deleteSession);
   addMessageListenerId("Marionette:sleepSession", sleepSession);
   addMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
@@ -282,27 +314,27 @@ function deleteSession(msg) {
   removeMessageListenerId("Marionette:getTitle", getTitle);
   removeMessageListenerId("Marionette:getPageSource", getPageSource);
   removeMessageListenerId("Marionette:getCurrentUrl", getCurrentUrl);
   removeMessageListenerId("Marionette:goBack", goBack);
   removeMessageListenerId("Marionette:goForward", goForward);
   removeMessageListenerId("Marionette:refresh", refresh);
   removeMessageListenerId("Marionette:findElementContent", findElementContent);
   removeMessageListenerId("Marionette:findElementsContent", findElementsContent);
-  removeMessageListenerId("Marionette:getActiveElement", getActiveElement);
-  removeMessageListenerId("Marionette:clickElement", clickElement);
-  removeMessageListenerId("Marionette:getElementAttribute", getElementAttribute);
-  removeMessageListenerId("Marionette:getElementText", getElementText);
-  removeMessageListenerId("Marionette:getElementTagName", getElementTagName);
+  removeMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
+  removeMessageListenerId("Marionette:clickElement", clickElementFn);
+  removeMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn);
+  removeMessageListenerId("Marionette:getElementText", getElementTextFn);
+  removeMessageListenerId("Marionette:getElementTagName", getElementTagNameFn);
   removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayed);
   removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssProperty);
   removeMessageListenerId("Marionette:submitElement", submitElement);
-  removeMessageListenerId("Marionette:getElementSize", getElementSize);  //deprecated
-  removeMessageListenerId("Marionette:getElementRect", getElementRect);
-  removeMessageListenerId("Marionette:isElementEnabled", isElementEnabled);
+  removeMessageListenerId("Marionette:getElementSize", getElementSizeFn); // deprecated
+  removeMessageListenerId("Marionette:getElementRect", getElementRectFn);
+  removeMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
   removeMessageListenerId("Marionette:isElementSelected", isElementSelected);
   removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElement);
   removeMessageListenerId("Marionette:getElementLocation", getElementLocation);
   removeMessageListenerId("Marionette:clearElement", clearElement);
   removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
   removeMessageListenerId("Marionette:deleteSession", deleteSession);
   removeMessageListenerId("Marionette:sleepSession", sleepSession);
   removeMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
@@ -326,50 +358,52 @@ function deleteSession(msg) {
 
 /*
  * Helper methods
  */
 
 /**
  * Generic method to send a message to the server
  */
-function sendToServer(msg, value, command_id) {
-  if (command_id) {
-    value.command_id = command_id;
+function sendToServer(name, data, objs, id) {
+  if (!data) {
+    data = {}
   }
-  sendAsyncMessage(msg, value);
+  if (id) {
+    data.command_id = id;
+  }
+  sendAsyncMessage(name, data, objs);
 }
 
 /**
  * Send response back to server
  */
 function sendResponse(value, command_id) {
-  sendToServer("Marionette:done", value, command_id);
+  sendToServer("Marionette:done", value, null, command_id);
 }
 
 /**
  * Send ack back to server
  */
 function sendOk(command_id) {
-  sendToServer("Marionette:ok", {}, command_id);
+  sendToServer("Marionette:ok", null, null, command_id);
 }
 
 /**
  * Send log message to server
  */
 function sendLog(msg) {
-  sendToServer("Marionette:log", { message: msg });
+  sendToServer("Marionette:log", {message: msg});
 }
 
 /**
  * Send error message to server
  */
-function sendError(msg, code, stack, cmdId) {
-  let payload = {message: msg, code: code, stack: stack};
-  sendToServer("Marionette:error", payload, cmdId);
+function sendError(err, cmdId) {
+  sendToServer("Marionette:error", null, {error: err}, cmdId);
 }
 
 /**
  * Clear test values after completion of test
  */
 function resetValues() {
   sandbox = null;
   curFrame = content;
@@ -453,97 +487,90 @@ function createExecuteContentSandbox(aWi
     });
   }
   else {
     XPCOMUtils.defineLazyGetter(sandbox, 'SpecialPowers', function() {
       return new SpecialPowers(aWindow);
     });
   }
 
-  sandbox.asyncComplete = function sandbox_asyncComplete(value, status, stack, commandId) {
-    if (commandId == asyncTestCommandId) {
+  sandbox.asyncComplete = function(obj, id) {
+    if (id == asyncTestCommandId) {
       curFrame.removeEventListener("unload", onunload, false);
       curFrame.clearTimeout(asyncTestTimeoutId);
 
       if (inactivityTimeoutId != null) {
         curFrame.clearTimeout(inactivityTimeoutId);
       }
 
-
       sendSyncMessage("Marionette:shareData",
-                      {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
+          {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
       marionetteLogObj.clearLogs();
 
-      if (status == 0){
+      if (error.isError(obj)) {
+        sendError(obj, id);
+      } else {
         if (Object.keys(_emu_cbs).length) {
           _emu_cbs = {};
-          sendError("Emulator callback still pending when finish() called",
-                    500, null, commandId);
+          sendError({message: "Emulator callback still pending when finish() called"}, id);
+        } else {
+          sendResponse({value: elementManager.wrapValue(obj)}, id);
         }
-        else {
-          sendResponse({value: elementManager.wrapValue(value), status: status},
-                       commandId);
-        }
-      }
-      else {
-        sendError(value, status, stack, commandId);
       }
 
       asyncTestRunning = false;
       asyncTestTimeoutId = undefined;
       asyncTestCommandId = undefined;
       inactivityTimeoutId = null;
     }
   };
   sandbox.finish = function sandbox_finish() {
     if (asyncTestRunning) {
-      sandbox.asyncComplete(marionette.generate_results(), 0, null, sandbox.asyncTestCommandId);
+      sandbox.asyncComplete(marionette.generate_results(), sandbox.asyncTestCommandId);
     } else {
       return marionette.generate_results();
     }
   };
-  sandbox.marionetteScriptFinished = function sandbox_marionetteScriptFinished(value) {
-    return sandbox.asyncComplete(value, 0, null, sandbox.asyncTestCommandId);
-  };
+  sandbox.marionetteScriptFinished = val =>
+      sandbox.asyncComplete(val, sandbox.asyncTestCommandId);
 
   return sandbox;
 }
 
 /**
  * Execute the given script either as a function body (executeScript)
- * or directly (for 'mochitest' like JS Marionette tests)
+ * or directly (for mochitest like JS Marionette tests).
  */
 function executeScript(msg, directInject) {
   // Set up inactivity timeout.
   if (msg.json.inactivityTimeout) {
     let setTimer = function() {
-        inactivityTimeoutId = curFrame.setTimeout(function() {
-        sendError('timed out due to inactivity', 28, null, asyncTestCommandId);
+      inactivityTimeoutId = curFrame.setTimeout(function() {
+        sendError(new ScriptTimeoutError("timed out due to inactivity"), asyncTestCommandId);
       }, msg.json.inactivityTimeout);
    };
 
     setTimer();
-    heartbeatCallback = function resetInactivityTimeout() {
+    heartbeatCallback = function() {
       curFrame.clearTimeout(inactivityTimeoutId);
       setTimer();
     };
   }
 
   asyncTestCommandId = msg.json.command_id;
   let script = msg.json.script;
 
   if (msg.json.newSandbox || !sandbox) {
     sandbox = createExecuteContentSandbox(curFrame,
                                           msg.json.timeout);
     if (!sandbox) {
-      sendError("Could not create sandbox!", 500, null, asyncTestCommandId);
+      sendError(new WebDriverError("Could not create sandbox!"), asyncTestCommandId);
       return;
     }
-  }
-  else {
+  } else {
     sandbox.asyncTestCommandId = asyncTestCommandId;
   }
 
   try {
     if (directInject) {
       if (importedScripts.exists()) {
         let stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
                       createInstance(Components.interfaces.nsIFileInputStream);
@@ -553,29 +580,28 @@ function executeScript(msg, directInject
         script = data + script;
       }
       let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file" ,0);
       sendSyncMessage("Marionette:shareData",
                       {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
       marionetteLogObj.clearLogs();
 
       if (res == undefined || res.passed == undefined) {
-        sendError("Marionette.finish() not called", 17, null, asyncTestCommandId);
+        sendError(new JavaScriptError("Marionette.finish() not called"), asyncTestCommandId);
       }
       else {
         sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId);
       }
     }
     else {
       try {
         sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments(
           msg.json.args, curFrame), sandbox, { wrapReflectors: true });
-      }
-      catch(e) {
-        sendError(e.message, e.code, e.stack, asyncTestCommandId);
+      } catch (e) {
+        sendError(e, asyncTestCommandId);
         return;
       }
 
       script = "let __marionetteFunc = function(){" + script + "};" +
                    "__marionetteFunc.apply(null, __marionetteParams);";
       if (importedScripts.exists()) {
         let stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
                       createInstance(Components.interfaces.nsIFileInputStream);
@@ -585,25 +611,24 @@ function executeScript(msg, directInject
         script = data + script;
       }
       let res = Cu.evalInSandbox(script, sandbox, "1.8", "dummy file", 0);
       sendSyncMessage("Marionette:shareData",
                       {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
       marionetteLogObj.clearLogs();
       sendResponse({value: elementManager.wrapValue(res)}, asyncTestCommandId);
     }
-  }
-  catch (e) {
-    // 17 = JavascriptException
-    let error = createStackMessage(e,
-                                   "execute_script",
-                                   msg.json.filename,
-                                   msg.json.line,
-                                   script);
-    sendError(error[0], 17, error[1], asyncTestCommandId);
+  } catch (e) {
+    let err = new JavaScriptError(
+        e,
+        "execute_script",
+        msg.json.filename,
+        msg.json.line,
+        script);
+    sendError(err, asyncTestCommandId);
   }
 }
 
 /**
  * Sets the test name, used in logging messages.
  */
 function setTestName(msg) {
   marionetteTestName = msg.json.value;
@@ -637,77 +662,76 @@ function executeJSScript(msg) {
  * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1]
  * method is called, or if it times out.
  */
 function executeWithCallback(msg, useFinish) {
   // Set up inactivity timeout.
   if (msg.json.inactivityTimeout) {
     let setTimer = function() {
       inactivityTimeoutId = curFrame.setTimeout(function() {
-        sandbox.asyncComplete('timed out due to inactivity', 28, null, asyncTestCommandId);
+        sandbox.asyncComplete(new ScriptTimeout("timed out due to inactivity"), asyncTestCommandId);
       }, msg.json.inactivityTimeout);
     };
 
     setTimer();
-    heartbeatCallback = function resetInactivityTimeout() {
+    heartbeatCallback = function() {
       curFrame.clearTimeout(inactivityTimeoutId);
       setTimer();
     };
   }
 
   let script = msg.json.script;
   asyncTestCommandId = msg.json.command_id;
 
   onunload = function() {
-    sendError("unload was called", 17, null, asyncTestCommandId);
+    sendError(new JavaScriptError("unload was called"), asyncTestCommandId);
   };
   curFrame.addEventListener("unload", onunload, false);
 
   if (msg.json.newSandbox || !sandbox) {
     sandbox = createExecuteContentSandbox(curFrame,
                                           msg.json.timeout);
     if (!sandbox) {
-      sendError("Could not create sandbox!", 17, null, asyncTestCommandId);
+      sendError(new JavaScriptError("Could not create sandbox!"), asyncTestCommandId);
       return;
     }
   }
   else {
     sandbox.asyncTestCommandId = asyncTestCommandId;
   }
   sandbox.tag = script;
 
   // Error code 28 is scriptTimeout, but spec says execute_async should return 21 (Timeout),
   // see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/execute_async.
   // However Selenium code returns 28, see
   // http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js.
   // We'll stay compatible with the Selenium code.
   asyncTestTimeoutId = curFrame.setTimeout(function() {
-    sandbox.asyncComplete('timed out', 28, null, asyncTestCommandId);
+    sandbox.asyncComplete(new ScriptTimeoutError("timed out"), asyncTestCommandId);
   }, msg.json.timeout);
 
   originalOnError = curFrame.onerror;
-  curFrame.onerror = function errHandler(errMsg, url, line) {
-    sandbox.asyncComplete(errMsg, 17, "@" + url + ", line " + line, asyncTestCommandId);
+  curFrame.onerror = function errHandler(msg, url, line) {
+    sandbox.asyncComplete(new JavaScriptError(msg + "@" + url + ", line " + line), asyncTestCommandId);
     curFrame.onerror = originalOnError;
   };
 
   let scriptSrc;
   if (useFinish) {
     if (msg.json.timeout == null || msg.json.timeout == 0) {
-      sendError("Please set a timeout", 21, null, asyncTestCommandId);
+      sendError(new TimeoutError("Please set a timeout"), asyncTestCommandId);
     }
     scriptSrc = script;
   }
   else {
     try {
       sandbox.__marionetteParams = Cu.cloneInto(elementManager.convertWrappedArguments(
         msg.json.args, curFrame), sandbox, { wrapReflectors: true });
-    }
-    catch(e) {
-      sendError(e.message, e.code, e.stack, asyncTestCommandId);
+    } catch (e) {
+      sendError(e, asyncTestCommandId);
       return;
     }
 
     scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" +
                 "let __marionetteFunc = function() { " + script + "};" +
                 "__marionetteFunc.apply(null, __marionetteParams); ";
   }
 
@@ -718,23 +742,23 @@ function executeWithCallback(msg, useFin
                       createInstance(Ci.nsIFileInputStream);
       stream.init(importedScripts, -1, 0, 0);
       let data = NetUtil.readInputStreamToString(stream, stream.available());
       stream.close();
       scriptSrc = data + scriptSrc;
     }
     Cu.evalInSandbox(scriptSrc, sandbox, "1.8", "dummy file", 0);
   } catch (e) {
-    // 17 = JavascriptException
-    let error = createStackMessage(e,
-                                   "execute_async_script",
-                                   msg.json.filename,
-                                   msg.json.line,
-                                   scriptSrc);
-    sandbox.asyncComplete(error[0], 17, error[1], asyncTestCommandId);
+    let err = new JavaScriptError(
+        e,
+        "execute_async_script",
+        msg.json.filename,
+        msg.json.line,
+        scriptSrc);
+    sandbox.asyncComplete(err, asyncTestCommandId);
   }
 }
 
 /**
  * This function creates a touch event given a touch type and a touch
  */
 function emitTouchEvent(type, touch) {
   if (!wasInterrupted()) {
@@ -851,35 +875,34 @@ function singleTap(msg) {
   let command_id = msg.json.command_id;
   try {
     let el = elementManager.getKnownElement(msg.json.id, curFrame);
     let acc = accessibility.getAccessibleObject(el, true);
     // after this block, the element will be scrolled into view
     let visible = checkVisible(el, msg.json.corx, msg.json.cory);
     checkVisibleAccessibility(acc, visible);
     if (!visible) {
-      sendError("Element is not currently visible and may not be manipulated", 11, null, command_id);
+      sendError(new ElementNotVisibleError("Element is not currently visible and may not be manipulated"), command_id);
       return;
     }
     checkActionableAccessibility(acc);
     if (!curFrame.document.createTouch) {
       actions.mouseEventsOnly = true;
     }
     let c = coordinates(el, msg.json.corx, msg.json.cory);
     if (!actions.mouseEventsOnly) {
       let touchId = actions.nextTouchId++;
       let touch = createATouch(el, c.x, c.y, touchId);
       emitTouchEvent('touchstart', touch);
       emitTouchEvent('touchend', touch);
     }
     actions.mouseTap(el.ownerDocument, c.x, c.y);
-    sendOk(msg.json.command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, msg.json.command_id);
+    sendOk(command_id);
+  } catch (e) {
+    sendError(e, command_id);
   }
 }
 
 /**
  * Check if the element's unavailable accessibility state matches the enabled
  * state
  * @param nsIAccessible object
  * @param Boolean enabled element's enabled state
@@ -954,37 +977,33 @@ function createATouch(el, corx, cory, to
  * Function to start action chain on one finger
  */
 function actionChain(msg) {
   let command_id = msg.json.command_id;
   let args = msg.json.chain;
   let touchId = msg.json.nextId;
 
   let callbacks = {};
-  callbacks.onSuccess = (value) => {
-    sendResponse(value, command_id);
-  };
-  callbacks.onError = (message, code, trace) => {
-    sendError(message, code, trace, msg.json.command_id);
-  };
+  callbacks.onSuccess = value => sendResponse(value, command_id);
+  callbacks.onError = err => sendError(err, command_id);
 
   let touchProvider = {};
   touchProvider.createATouch = createATouch;
   touchProvider.emitTouchEvent = emitTouchEvent;
 
   try {
     actions.dispatchActions(
         args,
         touchId,
         curFrame,
         elementManager,
         callbacks,
         touchProvider);
   } catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
+    sendError(e, command_id);
   }
 }
 
 /**
  * Function to emit touch events which allow multi touch on the screen
  * @param type represents the type of event, touch represents the current touch,touches are all pending touches
  */
 function emitMultiEvents(type, touch, touches) {
@@ -1139,19 +1158,18 @@ function multiAction(msg) {
       }
       concurrentEvent.push(row);
     }
     // now concurrent event is made of sets where each set contain a list of actions that need to be fired.
     // note: each action belongs to a different finger
     // pendingTouches keeps track of current touches that's on the screen
     let pendingTouches = [];
     setDispatch(concurrentEvent, pendingTouches, command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, msg.json.command_id);
+  } catch (e) {
+    sendError(e, command_id);
   }
 }
 
 /*
  * This implements the latter part of a get request (for the case we need to resume one
  * when a remoteness update happens in the middle of a navigate request). This is most of
  * of the work of a navigate request, but doesn't assume DOMContentLoaded is yet to fire.
  */
@@ -1173,29 +1191,27 @@ function pollForReadyState(msg, start, c
       if (curFrame.document.readyState == "complete") {
         callback();
         sendOk(command_id);
       } else if (curFrame.document.readyState == "interactive" &&
                  aboutErrorRegex.exec(curFrame.document.baseURI) &&
                  !curFrame.document.baseURI.startsWith(url)) {
         // We have reached an error url without requesting it.
         callback();
-        sendError("Error loading page", 13, null, command_id);
+        sendError(new UnknownError("Error loading page"), command_id);
       } else if (curFrame.document.readyState == "interactive" &&
                  curFrame.document.baseURI.startsWith("about:")) {
         callback();
         sendOk(command_id);
       } else {
         navTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
       }
-    }
-    else {
+    } else {
       callback();
-      sendError("Error loading page, timed out (checkLoad)", 21, null,
-                command_id);
+      sendError(new TimeoutError("Error loading page, timed out (checkLoad)"), command_id);
     }
   }
   checkLoad();
 }
 
 /**
  * Navigate to the given URL.  The operation will be performed on the
  * current browser context, and handles the case where we navigate
@@ -1215,18 +1231,17 @@ function get(msg) {
         removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
         onDOMContentLoaded = null;
       });
     }
   };
 
   function timerFunc() {
     removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
-    sendError("Error loading page, timed out (onDOMContentLoaded)", 21,
-              null, msg.json.command_id);
+    sendError(new TimeoutError("Error loading page, timed out (onDOMContentLoaded)"), msg.json.command_id);
   }
   if (msg.json.pageTimeout != null) {
     navTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
   }
   addEventListener("DOMContentLoaded", onDOMContentLoaded, false);
   curFrame.location = msg.json.url;
 }
 
@@ -1301,138 +1316,129 @@ function refresh(msg) {
 }
 
 /**
  * Find an element in the document using requested search strategy
  */
 function findElementContent(msg) {
   let command_id = msg.json.command_id;
   try {
-    let on_success = function(el, cmd_id) { sendResponse({value: el}, cmd_id) };
-    let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); };
+    let onSuccess = (el, id) => sendResponse({value: el}, id);
+    let onError = (err, id) => sendError(err, id);
     elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
-                        false /* all */, on_success, on_error, command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
+        false /* all */, onSuccess, onError, command_id);
+  } catch (e) {
+    sendError(e, command_id);
   }
 }
 
 /**
  * Find elements in the document using requested search strategy
  */
 function findElementsContent(msg) {
   let command_id = msg.json.command_id;
   try {
-    let on_success = function(els, cmd_id) { sendResponse({value: els}, cmd_id); };
-    let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); };
+    let onSuccess = (els, id) => sendResponse({value: els}, id);
+    let onError = (err, id) => sendError(err, id);
     elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
-                        true /* all */, on_success, on_error, command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
+        true /* all */, onSuccess, onError, command_id);
+  } catch (e) {
+    sendError(e, command_id);
   }
 }
 
 /**
- * Find and return the active element on the page
+ * Find and return the active element on the page.
+ *
+ * @return {WebElement}
+ *     Reference to web element.
  */
-function getActiveElement(msg) {
-  let command_id = msg.json.command_id;
-  var element = curFrame.document.activeElement;
-  var id = elementManager.addToKnownElements(element);
-  sendResponse({value: id}, command_id);
+function getActiveElement() {
+  let el = curFrame.document.activeElement;
+  return elementManager.addToKnownElements(el);
 }
 
 /**
- * Send click event to element
+ * Send click event to element.
+ *
+ * @param {WebElement} id
+ *     Reference to the web element to click.
  */
-function clickElement(msg) {
-  let command_id = msg.json.command_id;
-  let el;
-  try {
-    el = elementManager.getKnownElement(msg.json.id, curFrame);
-    let acc = accessibility.getAccessibleObject(el, true);
-    let visible = checkVisible(el);
-    checkVisibleAccessibility(acc, visible);
-    if (visible) {
-      checkActionableAccessibility(acc);
-      if (utils.isElementEnabled(el)) {
-        utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView)
-      }
-      else {
-        sendError("Element is not Enabled", 12, null, command_id)
-      }
-    }
-    else {
-      sendError("Element is not visible", 11, null, command_id)
-    }
-    sendOk(command_id);
+function clickElement(id) {
+  let el = elementManager.getKnownElement(id, curFrame);
+  let acc = accessibility.getAccessibleObject(el, true);
+  let visible = checkVisible(el);
+  checkVisibleAccessibility(acc, visible);
+  if (!visible) {
+    throw new ElementNotVisibleError("Element is not visible");
   }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
+  checkActionableAccessibility(acc);
+  if (utils.isElementEnabled(el)) {
+    utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView);
+  } else {
+    throw new InvalidElementStateError("Element is not Enabled");
   }
 }
 
 /**
- * Get a given attribute of an element
+ * Get a given attribute of an element.
+ *
+ * @param {WebElement} id
+ *     Reference to the web element to get the attribute of.
+ * @param {string} name
+ *     Name of the attribute.
+ *
+ * @return {string}
+ *     The value of the attribute.
  */
-function getElementAttribute(msg) {
-  let command_id = msg.json.command_id;
-  try {
-    let el = elementManager.getKnownElement(msg.json.id, curFrame);
-    sendResponse({value: utils.getElementAttribute(el, msg.json.name)},
-                 command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
-  }
+function getElementAttribute(id, name) {
+  let el = elementManager.getKnownElement(id, curFrame);
+  return utils.getElementAttribute(el, name);
 }
 
 /**
  * Get the text of this element. This includes text from child elements.
+ *
+ * @param {WebElement} id
+ *     Reference to web element.
+ *
+ * @return {string}
+ *     Text of element.
  */
-function getElementText(msg) {
-  let command_id = msg.json.command_id;
-  try {
-    let el = elementManager.getKnownElement(msg.json.id, curFrame);
-    sendResponse({value: utils.getElementText(el)}, command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
-  }
+function getElementText(id) {
+  let el = elementManager.getKnownElement(id, curFrame);
+  return utils.getElementText(el);
 }
 
 /**
  * Get the tag name of an element.
+ *
+ * @param {WebElement} id
+ *     Reference to web element.
+ *
+ * @return {string}
+ *     Tag name of element.
  */
-function getElementTagName(msg) {
-  let command_id = msg.json.command_id;
-  try {
-    let el = elementManager.getKnownElement(msg.json.id, curFrame);
-    sendResponse({value: el.tagName.toLowerCase()}, command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
-  }
+function getElementTagName(id) {
+  let el = elementManager.getKnownElement(id, curFrame);
+  return el.tagName.toLowerCase();
 }
 
 /**
  * Check if element is displayed
  */
 function isElementDisplayed(msg) {
   let command_id = msg.json.command_id;
   try {
     let el = elementManager.getKnownElement(msg.json.id, curFrame);
     let displayed = utils.isElementDisplayed(el);
     checkVisibleAccessibility(accessibility.getAccessibleObject(el), displayed);
     sendResponse({value: displayed}, command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
+  } catch (e) {
+    sendError(e, command_id);
   }
 }
 
 /**
  * Return the property of the computed style of an element
  *
  * @param object aRequest
  *               'element' member holds the reference id to
@@ -1441,19 +1447,18 @@ function isElementDisplayed(msg) {
  */
 function getElementValueOfCssProperty(msg){
   let command_id = msg.json.command_id;
   let propertyName = msg.json.propertyName;
   try {
     let el = elementManager.getKnownElement(msg.json.id, curFrame);
     sendResponse({value: curFrame.document.defaultView.getComputedStyle(el, null).getPropertyValue(propertyName)},
                  command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
+  } catch (e) {
+    sendError(e, command_id);
   }
 }
 
 /**
   * Submit a form on a content page by either using form or element in a form
   * @param object msg
   *               'json' JSON object containing 'id' member of the element
   */
@@ -1462,90 +1467,85 @@ function submitElement (msg) {
   try {
     let el = elementManager.getKnownElement(msg.json.id, curFrame);
     while (el.parentNode != null && el.tagName.toLowerCase() != 'form') {
       el = el.parentNode;
     }
     if (el.tagName && el.tagName.toLowerCase() == 'form') {
       el.submit();
       sendOk(command_id);
-    }
-    else {
-      sendError("Element is not a form element or in a form", 7, null, command_id);
+    } else {
+      sendError(new NoSuchElementError("Element is not a form element or in a form"), command_id);
     }
-
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
-  }
-}
-
-/**
- * Get the size of the element and return it
- */
-function getElementSize(msg){
-  let command_id = msg.json.command_id;
-  try {
-    let el = elementManager.getKnownElement(msg.json.id, curFrame);
-    let clientRect = el.getBoundingClientRect();
-    sendResponse({value: {width: clientRect.width, height: clientRect.height}},
-                 command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
+  } catch (e) {
+    sendError(e, command_id);
   }
 }
 
 /**
- * Get the size of the element and return it
+ * Get the size of the element.
+ *
+ * @param {WebElement} id
+ *     Web element reference.
+ *
+ * @return {Object.<string, number>}
+ *     The width/height dimensions of th element.
  */
-function getElementRect(msg){
-  let command_id = msg.json.command_id;
-  try {
-    let el = elementManager.getKnownElement(msg.json.id, curFrame);
-    let clientRect = el.getBoundingClientRect();
-    sendResponse({value: {x: clientRect.x + curFrame.pageXOffset,
-                          y: clientRect.y  + curFrame.pageYOffset,
-                          width: clientRect.width,
-                          height: clientRect.height}},
-                 command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
-  }
+function getElementSize(id) {
+  let el = elementManager.getKnownElement(id, curFrame);
+  let clientRect = el.getBoundingClientRect();
+  return {width: clientRect.width, height: clientRect.height};
 }
 
 /**
- * Check if element is enabled
+ * Get the size of the element.
+ *
+ * @param {WebElement} id
+ *     Reference to web element.
+ *
+ * @return {Object.<string, number>}
+ *     The x, y, width, and height properties of the element.
  */
-function isElementEnabled(msg) {
-  let command_id = msg.json.command_id;
-  try {
-    let el = elementManager.getKnownElement(msg.json.id, curFrame);
-    let enabled = utils.isElementEnabled(el);
-    checkEnabledStateAccessibility(accessibility.getAccessibleObject(el),
-      enabled);
-    sendResponse({value: enabled}, command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
-  }
+function getElementRect(id) {
+  let el = elementManager.getKnownElement(id, curFrame);
+  let clientRect = el.getBoundingClientRect();
+  return {
+    x: clientRect.x + curFrame.pageXOffset,
+    y: clientRect.y  + curFrame.pageYOffset,
+    width: clientRect.width,
+    height: clientRect.height
+  };
+}
+
+/**
+ * Check if element is enabled.
+ *
+ * @param {WebElement} id
+ *     Reference to web element.
+ *
+ * @return {boolean}
+ *     True if enabled, false otherwise.
+ */
+function isElementEnabled(id) {
+  let el = elementManager.getKnownElement(id, curFrame);
+  let enabled = utils.isElementEnabled(el);
+  checkEnabledStateAccessibility(accessibility.getAccessibleObject(el), enabled);
+  return enabled;
 }
 
 /**
  * Check if element is selected
  */
 function isElementSelected(msg) {
   let command_id = msg.json.command_id;
   try {
     let el = elementManager.getKnownElement(msg.json.id, curFrame);
     sendResponse({value: utils.isElementSelected(el)}, command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
+  } catch (e) {
+    sendError(e, command_id);
   }
 }
 
 /**
  * Send keys to element
  */
 function sendKeysToElement(msg) {
   let command_id = msg.json.command_id;
@@ -1593,19 +1593,18 @@ function getElementLocation(msg) {
     let el = elementManager.getKnownElement(msg.json.id, curFrame);
     let rect = el.getBoundingClientRect();
 
     let location = {};
     location.x = rect.left;
     location.y = rect.top;
 
     sendResponse({value: location}, command_id);
-  }
-  catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
+  } catch (e) {
+    sendError(e, command_id);
   }
 }
 
 /**
  * Clear the text of an element
  */
 function clearElement(msg) {
   let command_id = msg.json.command_id;
@@ -1613,34 +1612,34 @@ function clearElement(msg) {
     let el = elementManager.getKnownElement(msg.json.id, curFrame);
     if (el.type == "file") {
       el.value = null;
     } else {
       utils.clearElement(el);
     }
     sendOk(command_id);
   } catch (e) {
-    sendError(e.message, e.code, e.stack, command_id);
+    sendError(e, command_id);
   }
 }
 
 /**
  * Switch to frame given either the server-assigned element id,
  * its index in window.frames, or the iframe's name or id.
  */
 function switchToFrame(msg) {
   let command_id = msg.json.command_id;
   function checkLoad() {
     let errorRegex = /about:.+(error)|(blocked)\?/;
     if (curFrame.document.readyState == "complete") {
       sendOk(command_id);
       return;
-    }
-    else if (curFrame.document.readyState == "interactive" && errorRegex.exec(curFrame.document.baseURI)) {
-      sendError("Error loading page", 13, null, command_id);
+    } else if (curFrame.document.readyState == "interactive" &&
+        errorRegex.exec(curFrame.document.baseURI)) {
+      sendError(new UnknownError("Error loading page"), command_id);
       return;
     }
     checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
   }
   let foundFrame = null;
   let frames = [];
   let parWindow = null;
   // Check of the curFrame reference is dead
@@ -1670,19 +1669,18 @@ function switchToFrame(msg) {
     checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
     return;
   }
   if (msg.json.element != undefined) {
     if (elementManager.seenItems[msg.json.element] != undefined) {
       let wantedFrame;
       try {
         wantedFrame = elementManager.getKnownElement(msg.json.element, curFrame); //Frame Element
-      }
-      catch(e) {
-        sendError(e.message, e.code, e.stack, command_id);
+      } catch (e) {
+        sendError(e, command_id);
       }
 
       if (frames.length > 0) {
         for (let i = 0; i < frames.length; i++) {
           // use XPCNativeWrapper to compare elements; see bug 834266
           if (XPCNativeWrapper(frames[i].frameElement) == XPCNativeWrapper(wantedFrame)) {
             curFrame = frames[i].frameElement;
             foundFrame = i;
@@ -1730,18 +1728,19 @@ function switchToFrame(msg) {
         let iframes = curFrame.document.getElementsByTagName("iframe");
         if (msg.json.id >= 0 && msg.json.id < iframes.length) {
           curFrame = iframes[msg.json.id];
           foundFrame = msg.json.id;
         }
       }
     }
   }
+
   if (foundFrame === null) {
-    sendError("Unable to locate frame: " + (msg.json.id || msg.json.element), 8, null, command_id);
+    sendError(new NoSuchFrameError("Unable to locate frame: " + (msg.json.id || msg.json.element)), command_id);
     return true;
   }
 
   sandbox = null;
 
   // send a synchronous message to let the server update the currently active
   // frame element (for getActiveFrame)
   let frameValue = elementManager.wrapValue(curFrame.wrappedJSObject)['ELEMENT'];
@@ -1772,40 +1771,39 @@ function addCookie(msg) {
     var thePresent = new Date(Date.now());
     date.setYear(thePresent.getFullYear() + 20);
     cookie.expiry = date.getTime() / 1000;  // Stored in seconds.
   }
 
   if (!cookie.domain) {
     var location = curFrame.document.location;
     cookie.domain = location.hostname;
-  }
-  else {
+  } else {
     var currLocation = curFrame.location;
     var currDomain = currLocation.host;
     if (currDomain.indexOf(cookie.domain) == -1) {
-      sendError("You may only set cookies for the current domain", 24, null, msg.json.command_id);
+      sendError(new InvalidCookieDomainError("You may only set cookies for the current domain"), msg.json.command_id);
     }
   }
 
   // The cookie's domain may include a port. Which is bad. Remove it
   // We'll catch ip6 addresses by mistake. Since no-one uses those
   // this will be okay for now. See Bug 814416
   if (cookie.domain.match(/:\d+$/)) {
     cookie.domain = cookie.domain.replace(/:\d+$/, '');
   }
 
   var document = curFrame.document;
   if (!document || !document.contentType.match(/html/i)) {
-    sendError('You may only set cookies on html documents', 25, null, msg.json.command_id);
+    sendError(new UnableToSetCookie("You may only set cookies on html documents"), msg.json.command_id);
   }
 
   let added = sendSyncMessage("Marionette:addCookie", {value: cookie});
   if (added[0] !== true) {
-    sendError("Error setting cookie", 13, null, msg.json.command_id);
+    sendError(new UnknownError("Error setting cookie"), msg.json.command_id);
     return;
   }
   sendOk(msg.json.command_id);
 }
 
 /**
  * Get all cookies for the current domain.
  */
@@ -1836,34 +1834,34 @@ function getCookies(msg) {
  */
 function deleteCookie(msg) {
   let toDelete = msg.json.name;
   let cookies = getVisibleCookies(curFrame.location);
   for (let cookie of cookies) {
     if (cookie.name == toDelete) {
       let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie});
       if (deleted[0] !== true) {
-        sendError("Could not delete cookie: " + msg.json.name, 13, null, msg.json.command_id);
+        sendError(new UnknownError("Could not delete cookie: " + msg.json.name), msg.json.command_id);
         return;
       }
     }
   }
 
   sendOk(msg.json.command_id);
 }
 
 /**
  * Delete all the visibile cookies on a page
  */
 function deleteAllCookies(msg) {
   let cookies = getVisibleCookies(curFrame.location);
   for (let cookie of cookies) {
     let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie});
     if (!deleted[0]) {
-      sendError("Could not delete cookie: " + JSON.stringify(cookie), 13, null, msg.json.command_id);
+      sendError(new UnknownError("Could not delete cookie: " + JSON.stringify(cookie)), msg.json.command_id);
       return;
     }
   }
   sendOk(msg.json.command_id);
 }
 
 /**
  * Get all the visible cookies from a location
@@ -1907,19 +1905,18 @@ function emulatorCmdResult(msg) {
   }
   let cb = _emu_cbs[message.id];
   delete _emu_cbs[message.id];
   if (!cb) {
     return;
   }
   try {
     cb(message.result);
-  }
-  catch(e) {
-    sendError(e.message, e.code, e.stack, -1);
+  } catch (e) {
+    sendError(e, -1);
     return;
   }
 }
 
 function importScript(msg) {
   let command_id = msg.json.command_id;
   let file;
   if (importedScripts.exists()) {
--- a/testing/marionette/sendkeys.js
+++ b/testing/marionette/sendkeys.js
@@ -12,18 +12,21 @@
  *  Unless required by applicable law or agreed to in writing, software
  *  distributed under the License is distributed on an "AS IS" BASIS,
  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
 
 let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("chrome://marionette/content/error.js");
+
 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
-               .getService(Ci.mozIJSSubScriptLoader);
+    .getService(Ci.mozIJSSubScriptLoader);
 
 let utils = {};
 loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
 loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
 
 let keyModifierNames = {
     "VK_SHIFT": 'shiftKey',
     "VK_CONTROL": 'ctrlKey',
@@ -133,13 +136,12 @@ function sendKeysToElement (document, el
       metaKey: false
     };
     let value = keysToSend.join("");
     for (var i = 0; i < value.length; i++) {
       var c = value.charAt(i);
       sendSingleKey(c, modifiers, document);
     }
     successCallback(command_id);
-  }
-  else {
-    errorCallback("Element is not visible", 11, null, command_id);
+  } else {
+    errorCallback(new ElementNotVisibleError("Element is not visible"), command_id);
   }
 };