Merge m-c to b2g-inbound
authorWes Kocher <wkocher@mozilla.com>
Thu, 19 Jun 2014 18:31:17 -0700
changeset 189647 dc02747d73df7185ac15bb8a1baa45ea9a5dc9a8
parent 189644 20f19983e6a97554aa6c20e5aa8d80c30d3533cf (current diff)
parent 189634 bdac18bd6c7441154559413600d80a340d026bda (diff)
child 189648 8f0795fddae66c2847d32d66351d4fe3e08c2ec4
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
milestone33.0a1
Merge m-c to b2g-inbound
js/src/tests/ecma_6/LexicalEnvironment/browser.js
js/src/tests/ecma_6/LexicalEnvironment/shell.js
js/src/tests/ecma_6/LexicalEnvironment/with-global-ignores-global-let-variables.js
testing/marionette/client/marionette/b2gbuild.py
testing/marionette/client/marionette/b2ginstance.py
testing/marionette/client/marionette/emulator.py
testing/marionette/client/marionette/emulator_battery.py
testing/marionette/client/marionette/emulator_geo.py
testing/marionette/client/marionette/emulator_screen.py
testing/mozbase/mozrunner/mozrunner/base.py
testing/mozbase/mozrunner/mozrunner/local.py
testing/mozbase/mozrunner/mozrunner/remote.py
--- a/browser/components/translation/BingTranslator.jsm
+++ b/browser/components/translation/BingTranslator.jsm
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
-this.EXPORTED_SYMBOLS = [ "BingTranslation" ];
+this.EXPORTED_SYMBOLS = [ "BingTranslator" ];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://services-common/rest.js");
 
@@ -35,27 +35,27 @@ const MAX_REQUESTS = 15;
  * @param translationDocument  The TranslationDocument object that represents
  *                             the webpage to be translated
  * @param sourceLanguage       The source language of the document
  * @param targetLanguage       The target language for the translation
  *
  * @returns {Promise}          A promise that will resolve when the translation
  *                             task is finished.
  */
-this.BingTranslation = function(translationDocument, sourceLanguage, targetLanguage) {
+this.BingTranslator = function(translationDocument, sourceLanguage, targetLanguage) {
   this.translationDocument = translationDocument;
   this.sourceLanguage = sourceLanguage;
   this.targetLanguage = targetLanguage;
   this._pendingRequests = 0;
   this._partialSuccess = false;
   this._serviceUnavailable = false;
   this._translatedCharacterCount = 0;
 };
 
-this.BingTranslation.prototype = {
+this.BingTranslator.prototype = {
   /**
    * Performs the translation, splitting the document into several chunks
    * respecting the data limits of the API.
    *
    * @returns {Promise}          A promise that will resolve when the translation
    *                             task is finished.
    */
   translate: function() {
@@ -277,17 +277,20 @@ function BingRequest(translationData, so
 BingRequest.prototype = {
   /**
    * Initiates the request
    */
   fireRequest: function() {
     return Task.spawn(function *(){
       let token = yield BingTokenManager.getToken();
       let auth = "Bearer " + token;
-      let request = new RESTRequest("https://api.microsofttranslator.com/v2/Http.svc/TranslateArray");
+      let url = getUrlParam("https://api.microsofttranslator.com/v2/Http.svc/TranslateArray",
+                            "browser.translation.bing.translateArrayURL",
+                            false);
+      let request = new RESTRequest(url);
       request.setHeader("Content-type", "text/xml");
       request.setHeader("Authorization", auth);
 
       let requestString =
         '<TranslateArrayRequest>' +
           '<AppId/>' +
           '<From>' + this.sourceLanguage + '</From>' +
           '<Options>' +
@@ -353,25 +356,28 @@ let BingTokenManager = {
 
   /**
    * Generates a new token from the server.
    *
    * @returns {Promise}  A promise that resolves with the token
    *                     string once it is obtained.
    */
   _getNewToken: function() {
-    let request = new RESTRequest("https://datamarket.accesscontrol.windows.net/v2/OAuth2-13");
+    let url = getUrlParam("https://datamarket.accesscontrol.windows.net/v2/OAuth2-13",
+                          "browser.translation.bing.authURL",
+                          false);
+    let request = new RESTRequest(url);
     request.setHeader("Content-type", "application/x-www-form-urlencoded");
     let params = [
       "grant_type=client_credentials",
       "scope=" + encodeURIComponent("http://api.microsofttranslator.com"),
       "client_id=" +
-      getAuthTokenParam("%BING_API_CLIENTID%", "browser.translation.bing.clientIdOverride"),
+      getUrlParam("%BING_API_CLIENTID%", "browser.translation.bing.clientIdOverride"),
       "client_secret=" +
-      getAuthTokenParam("%BING_API_KEY%", "browser.translation.bing.apiKeyOverride")
+      getUrlParam("%BING_API_KEY%", "browser.translation.bing.apiKeyOverride")
     ];
 
     let deferred = Promise.defer();
     this._pendingRequest = deferred.promise;
     request.post(params.join("&"), function(err) {
       BingTokenManager._pendingRequest = null;
 
       if (err) {
@@ -411,16 +417,15 @@ function escapeXML(aStr) {
              .replace("<", "&lt;", "g")
              .replace(">", "&gt;", "g");
 }
 
 /**
  * Fetch an auth token (clientID or client secret), which may be overridden by
  * a pref if it's set.
  */
-function getAuthTokenParam(key, prefName) {
-  let val;
-  try {
-    val = Services.prefs.getCharPref(prefName);
-  } catch(ex) {}
+function getUrlParam(paramValue, prefName, encode = true) {
+  if (Services.prefs.getPrefType(prefName))
+    paramValue = Services.prefs.getCharPref(prefName);
+  paramValue = Services.urlFormatter.formatURL(paramValue);
 
-  return encodeURIComponent(Services.urlFormatter.formatURL(val || key));
+  return encode ? encodeURIComponent(paramValue) : paramValue;
 }
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -43,19 +43,26 @@ this.Translation = {
                                       .getSelectedLocale("global")
                                       .split("-")[0];
     }
     return this._defaultTargetLanguage;
   },
 
   documentStateReceived: function(aBrowser, aData) {
     if (aData.state == this.STATE_OFFER) {
-      if (this.supportedSourceLanguages.indexOf(aData.detectedLanguage) == -1 ||
-          aData.detectedLanguage == this.defaultTargetLanguage)
+      if (aData.detectedLanguage == this.defaultTargetLanguage) {
+        // Detected language is the same as the user's locale.
         return;
+      }
+
+      if (this.supportedSourceLanguages.indexOf(aData.detectedLanguage) == -1) {
+        // Detected language is not part of the supported languages.
+        TranslationHealthReport.recordMissedTranslationOpportunity(aData.detectedLanguage);
+        return;
+      }
 
       TranslationHealthReport.recordTranslationOpportunity(aData.detectedLanguage);
     }
 
     if (!Services.prefs.getBoolPref(TRANSLATION_PREF_SHOWUI))
       return;
 
     if (!aBrowser.translationUI)
@@ -88,20 +95,26 @@ this.Translation = {
  * - showOriginalContent, method showing the original page content.
  * - showTranslatedContent, method showing the translation for an
  *   already translated page whose original content is shown.
  * - originalShown, boolean indicating if the original or translated
  *   version of the page is shown.
  */
 function TranslationUI(aBrowser) {
   this.browser = aBrowser;
-  aBrowser.messageManager.addMessageListener("Translation:Finished", this);
 }
 
 TranslationUI.prototype = {
+  get browser() this._browser,
+  set browser(aBrowser) {
+    if (this._browser)
+      this._browser.messageManager.removeMessageListener("Translation:Finished", this);
+    aBrowser.messageManager.addMessageListener("Translation:Finished", this);
+    this._browser = aBrowser;
+  },
   translate: function(aFrom, aTo) {
     if (aFrom == aTo ||
         (this.state == Translation.STATE_TRANSLATED &&
          this.translatedFrom == aFrom && this.translatedTo == aTo)) {
       // Nothing to do.
       return;
     }
 
@@ -119,17 +132,27 @@ TranslationUI.prototype = {
     let chromeWin = this.browser.ownerGlobal;
     let PopupNotifications = chromeWin.PopupNotifications;
     let removeId = this.originalShown ? "translated" : "translate";
     let notification =
       PopupNotifications.getNotification(removeId, this.browser);
     if (notification)
       PopupNotifications.remove(notification);
 
-    let callback = aTopic => {
+    let callback = (aTopic, aNewBrowser) => {
+      if (aTopic == "swapping") {
+        let infoBarVisible =
+          this.notificationBox.getNotificationWithValue("translation");
+        aNewBrowser.translationUI = this;
+        this.browser = aNewBrowser;
+        if (infoBarVisible)
+          this.showTranslationInfoBar();
+        return true;
+      }
+
       if (aTopic != "showing")
         return false;
       let notification = this.notificationBox.getNotificationWithValue("translation");
       if (notification)
         notification.close();
       else
         this.showTranslationInfoBar();
       return true;
@@ -237,16 +260,27 @@ let TranslationHealthReport = {
    * Record a translation opportunity in the health report.
    * @param language
    *        The language of the page.
    */
   recordTranslationOpportunity: function (language) {
     this._withProvider(provider => provider.recordTranslationOpportunity(language));
    },
 
+  /**
+   * Record a missed translation opportunity in the health report.
+   * A missed opportunity is when the language detected is not part
+   * of the supported languages.
+   * @param language
+   *        The language of the page.
+   */
+  recordMissedTranslationOpportunity: function (language) {
+    this._withProvider(provider => provider.recordMissedTranslationOpportunity(language));
+  },
+
    /**
    * Record a translation in the health report.
    * @param langFrom
    *        The language of the page.
    * @param langTo
    *        The language translated to
    * @param numCharacters
    *        The number of characters that were translated
@@ -316,19 +350,21 @@ function TranslationMeasurement1() {
 TranslationMeasurement1.prototype = Object.freeze({
   __proto__: Metrics.Measurement.prototype,
 
   name: "translation",
   version: 1,
 
   fields: {
     translationOpportunityCount: DAILY_COUNTER_FIELD,
+    missedTranslationOpportunityCount: DAILY_COUNTER_FIELD,
     pageTranslatedCount: DAILY_COUNTER_FIELD,
     charactersTranslatedCount: DAILY_COUNTER_FIELD,
     translationOpportunityCountsByLanguage: DAILY_LAST_TEXT_FIELD,
+    missedTranslationOpportunityCountsByLanguage: DAILY_LAST_TEXT_FIELD,
     pageTranslatedCountsByLanguage: DAILY_LAST_TEXT_FIELD,
     detectedLanguageChangedBefore: DAILY_COUNTER_FIELD,
     detectedLanguageChangedAfter: DAILY_COUNTER_FIELD,
     detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD,
     showTranslationUI: DAILY_LAST_NUMERIC_FIELD,
   },
 
   shouldIncludeField: function (field) {
@@ -363,16 +399,17 @@ TranslationMeasurement1.prototype = Obje
     };
 
     return function (data) {
       let result = serializer(data);
 
       // Special case the serialization of these fields so that
       // they are sent as objects, not stringified objects.
       _parseInPlace(result, "translationOpportunityCountsByLanguage");
+      _parseInPlace(result, "missedTranslationOpportunityCountsByLanguage");
       _parseInPlace(result, "pageTranslatedCountsByLanguage");
 
       return result;
     }
   }
 });
 
 this.TranslationProvider = function () {
@@ -402,16 +439,35 @@ TranslationProvider.prototype = Object.f
       langCounts = JSON.stringify(langCounts);
 
       yield m.setDailyLastText("translationOpportunityCountsByLanguage",
                                langCounts, date);
 
     }.bind(this));
   },
 
+  recordMissedTranslationOpportunity: function (language, date=new Date()) {
+    let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
+                                TranslationMeasurement1.prototype.version);
+
+    return this._enqueueTelemetryStorageTask(function* recordTask() {
+      yield m.incrementDailyCounter("missedTranslationOpportunityCount", date);
+
+      let langCounts = yield m._getDailyLastTextFieldAsJSON(
+        "missedTranslationOpportunityCountsByLanguage", date);
+
+      langCounts[language] = (langCounts[language] || 0) + 1;
+      langCounts = JSON.stringify(langCounts);
+
+      yield m.setDailyLastText("missedTranslationOpportunityCountsByLanguage",
+                               langCounts, date);
+
+    }.bind(this));
+  },
+
   recordTranslation: function (langFrom, langTo, numCharacters, date=new Date()) {
     let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
                                 TranslationMeasurement1.prototype.version);
 
     return this._enqueueTelemetryStorageTask(function* recordTask() {
       yield m.incrementDailyCounter("pageTranslatedCount", date);
       yield m.incrementDailyCounter("charactersTranslatedCount", date,
                                     numCharacters);
--- a/browser/components/translation/TranslationContentHandler.jsm
+++ b/browser/components/translation/TranslationContentHandler.jsm
@@ -116,26 +116,26 @@ TranslationContentHandler.prototype = {
         Cu.import("resource:///modules/translation/BingTranslator.jsm");
 
         // If a TranslationDocument already exists for this document, it should
         // be used instead of creating a new one so that we can use the original
         // content of the page for the new translation instead of the newly
         // translated text.
         let translationDocument = this.global.content.translationDocument ||
                                   new TranslationDocument(this.global.content.document);
-        let bingTranslation = new BingTranslation(translationDocument,
-                                                  msg.data.from,
-                                                  msg.data.to);
+        let bingTranslator = new BingTranslator(translationDocument,
+                                                msg.data.from,
+                                                msg.data.to);
 
         this.global.content.translationDocument = translationDocument;
         translationDocument.translatedFrom = msg.data.from;
         translationDocument.translatedTo = msg.data.to;
         translationDocument.translationError = false;
 
-        bingTranslation.translate().then(
+        bingTranslator.translate().then(
           result => {
             this.global.sendAsyncMessage("Translation:Finished", {
               characterCount: result.characterCount,
               from: msg.data.from,
               to: msg.data.to,
               success: true
             });
             translationDocument.showTranslation();
new file mode 100644
--- /dev/null
+++ b/browser/components/translation/test/bing.sjs
@@ -0,0 +1,218 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, Constructor: CC} = Components;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                             "nsIBinaryInputStream",
+                             "setInputStream");
+
+function handleRequest(req, res) {
+  try {
+    reallyHandleRequest(req, res);
+  } catch (ex) {
+    res.setStatusLine("1.0", 200, "AlmostOK");
+    let msg = "Error handling request: " + ex + "\n" + ex.stack;
+    log(msg);
+    res.write(msg);
+  }
+}
+
+function log(msg) {
+  // dump("BING-SERVER-MOCK: " + msg + "\n");
+}
+
+const statusCodes = {
+  400: "Bad Request",
+  401: "Unauthorized",
+  403: "Forbidden",
+  404: "Not Found",
+  405: "Method Not Allowed",
+  500: "Internal Server Error",
+  501: "Not Implemented",
+  503: "Service Unavailable"
+};
+
+function HTTPError(code = 500, message) {
+  this.code = code;
+  this.name = statusCodes[code] || "HTTPError";
+  this.message = message || this.name;
+}
+HTTPError.prototype = new Error();
+HTTPError.prototype.constructor = HTTPError;
+
+function sendError(res, err) {
+  if (!(err instanceof HTTPError)) {
+    err = new HTTPError(typeof err == "number" ? err : 500,
+                        err.message || typeof err == "string" ? err : "");
+  }
+  res.setStatusLine("1.1", err.code, err.name);
+  res.write(err.message);
+}
+
+function parseQuery(query) {
+  let ret = {};
+  for (let param of query.replace(/^[?&]/, "").split("&")) {
+    param = param.split("=");
+    if (!param[0])
+      continue;
+    ret[unescape(param[0])] = unescape(param[1]);
+  }
+  return ret;
+}
+
+function getRequestBody(req) {
+  let avail;
+  let bytes = [];
+  let body = new BinaryInputStream(req.bodyInputStream);
+
+  while ((avail = body.available()) > 0)
+    Array.prototype.push.apply(bytes, body.readByteArray(avail));
+
+  return String.fromCharCode.apply(null, bytes);
+}
+
+function sha1(str) {
+  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                    .createInstance(Ci.nsIScriptableUnicodeConverter);
+  converter.charset = "UTF-8";
+  // `result` is an out parameter, `result.value` will contain the array length.
+  let result = {};
+  // `data` is an array of bytes.
+  let data = converter.convertToByteArray(str, result);
+  let ch = Cc["@mozilla.org/security/hash;1"]
+             .createInstance(Ci.nsICryptoHash);
+  ch.init(ch.SHA1);
+  ch.update(data, data.length);
+  let hash = ch.finish(false);
+
+  // Return the two-digit hexadecimal code for a byte.
+  function toHexString(charCode) {
+    return ("0" + charCode.toString(16)).slice(-2);
+  }
+
+  // Convert the binary hash data to a hex string.
+  return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
+}
+
+function parseXml(body) {
+  let DOMParser = Cc["@mozilla.org/xmlextras/domparser;1"]
+                    .createInstance(Ci.nsIDOMParser);
+  let xml = DOMParser.parseFromString(body, "text/xml");
+  if (xml.documentElement.localName == "parsererror")
+    throw new Error("Invalid XML");
+  return xml;
+}
+
+function getInputStream(path) {
+  let file = Cc["@mozilla.org/file/directory_service;1"]
+               .getService(Ci.nsIProperties)
+               .get("CurWorkD", Ci.nsILocalFile);
+  for (let part of path.split("/"))
+    file.append(part);
+  let fileStream  = Cc["@mozilla.org/network/file-input-stream;1"]
+                      .createInstance(Ci.nsIFileInputStream);
+  fileStream.init(file, 1, 0, false);
+  return fileStream;
+}
+
+function checkAuth(req) {
+  let err = new Error("Authorization failed");
+  err.code = 401;
+
+  if (!req.hasHeader("Authorization"))
+    throw new HTTPError(401, "No Authorization header provided.");
+
+  let auth = req.getHeader("Authorization");
+  if (!auth.startsWith("Bearer "))
+    throw new HTTPError(401, "Invalid Authorization header content: '" + auth + "'");
+}
+
+function reallyHandleRequest(req, res) {
+  log("method: " + req.method);
+  if (req.method != "POST") {
+    sendError(res, "Bing only deals with POST requests, not '" + req.method + "'.");
+    return;
+  }
+
+  let body = getRequestBody(req);
+  log("body: " + body);
+
+  // First, we'll see if we're dealing with an XML body:
+  let contentType = req.hasHeader("Content-Type") ? req.getHeader("Content-Type") : null;
+  log("contentType: " + contentType);
+
+  if (contentType == "text/xml") {
+    try {
+      // For all these requests the client needs to supply the correct
+      // authentication headers.
+      checkAuth(req);
+
+      let xml = parseXml(body);
+      let method = xml.documentElement.localName;
+      log("invoking method: " + method);
+      // If the requested method is supported, delegate it to its handler.
+      if (methodHandlers[method])
+        methodHandlers[method](res, xml);
+      else
+        throw new HTTPError(501);
+    } catch (ex) {
+      sendError(res, ex, ex.code);
+    }
+  } else {
+    // Not XML, so it must be a query-string.
+    let params = parseQuery(body);
+
+    // Delegate an authentication request to the correct handler.
+    if ("grant_type" in params && params.grant_type == "client_credentials")
+      methodHandlers.authenticate(res, params);
+    else
+      sendError(res, 501);
+  }
+}
+
+const methodHandlers = {
+  authenticate: function(res, params) {
+    // Validate a few required parameters.
+    if (params.scope != "http://api.microsofttranslator.com") {
+      sendError(res, "Invalid scope.");
+      return;
+    }
+    if (!params.client_id) {
+      sendError(res, "Missing client_id param.");
+      return;
+    }
+    if (!params.client_secret) {
+      sendError(res, "Missing client_secret param.");
+      return;
+    }
+
+    let content = JSON.stringify({
+      access_token: "test",
+      expires_in: 600
+    });
+
+    res.setStatusLine("1.1", 200, "OK");
+    res.setHeader("Content-Length", String(content.length));
+    res.setHeader("Content-Type", "application/json");
+    res.write(content);
+  },
+
+  TranslateArrayRequest: function(res, xml, body) {
+    let from = xml.querySelector("From").firstChild.nodeValue;
+    let to = xml.querySelector("To").firstChild.nodeValue
+    log("translating from '" + from + "' to '" + to + "'");
+
+    res.setStatusLine("1.1", 200, "OK");
+    res.setHeader("Content-Type", "text/xml");
+
+    let hash = sha1(body).substr(0, 10);
+    log("SHA1 hash of content: " + hash);
+    let inputStream = getInputStream(
+      "browser/browser/components/translation/test/fixtures/result-" + hash + ".txt");
+    res.bodyOutputStream.writeFrom(inputStream, inputStream.available());
+    inputStream.close();
+  }
+};
--- a/browser/components/translation/test/browser.ini
+++ b/browser/components/translation/test/browser.ini
@@ -1,6 +1,10 @@
 [DEFAULT]
+support-files =
+  bing.sjs
+  fixtures/bug1022725-fr.html
+  fixtures/result-da39a3ee5e.txt
 
+[browser_translation_bing.js]
 [browser_translation_fhr.js]
-skip-if = true # Needs to wait until bug 1022725.
 [browser_translation_infobar.js]
 [browser_translation_exceptions.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/translation/test/browser_translation_bing.js
@@ -0,0 +1,56 @@
+/* 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/. */
+
+// Test the Bing Translator client against a mock Bing service, bing.sjs.
+
+"use strict";
+
+const kClientIdPref = "browser.translation.bing.clientIdOverride";
+const kClientSecretPref = "browser.translation.bing.apiKeyOverride";
+
+const {BingTranslator} = Cu.import("resource:///modules/translation/BingTranslator.jsm", {});
+const {TranslationDocument} = Cu.import("resource:///modules/translation/TranslationDocument.jsm", {});
+
+function test() {
+  waitForExplicitFinish();
+
+  Services.prefs.setCharPref(kClientIdPref, "testClient");
+  Services.prefs.setCharPref(kClientSecretPref, "testSecret");
+
+  // Deduce the Mochitest server address in use from a pref that was pre-processed.
+  let server = Services.prefs.getCharPref("browser.translation.bing.authURL")
+                             .replace("http://", "");
+  server = server.substr(0, server.indexOf("/"));
+  let tab = gBrowser.addTab("http://" + server +
+    "/browser/browser/components/translation/test/fixtures/bug1022725-fr.html");
+  gBrowser.selectedTab = tab;
+
+  registerCleanupFunction(function () {
+    gBrowser.removeTab(tab);
+    Services.prefs.clearUserPref(kClientIdPref);
+    Services.prefs.clearUserPref(kClientSecretPref);
+  });
+
+  let browser = tab.linkedBrowser;
+  browser.addEventListener("load", function onload() {
+    if (browser.currentURI.spec == "about:blank")
+      return;
+
+    browser.removeEventListener("load", onload, true);
+    let client = new BingTranslator(
+      new TranslationDocument(browser.contentDocument), "fr", "en");
+
+    client.translate().then(
+      result => {
+        // XXXmikedeboer; here you would continue the test/ content inspection.
+        ok(result, "There should be a result.");
+        finish();
+      },
+      error => {
+        ok(false, "Unexpected Client Error: " + error);
+        finish();
+      }
+    );
+  }, true);
+}
--- a/browser/components/translation/test/browser_translation_fhr.js
+++ b/browser/components/translation/test/browser_translation_fhr.js
@@ -52,17 +52,18 @@ function retrieveTranslationCounts() {
     let values = yield measurement.getValues();
 
     let day = values.days.getDay(new Date());
     if (!day) {
       // This should never happen except when the test runs at midnight.
       return [0, 0];
     }
 
-    return [day.get("pageTranslatedCount"), day.get("charactersTranslatedCount")];
+    // .get() may return `undefined`, which we can't compute.
+    return [day.get("pageTranslatedCount") || 0, day.get("charactersTranslatedCount") || 0];
   });
 }
 
 function translate(text, from, to) {
   return Task.spawn(function* task_translate() {
     // Create some content to translate.
     let tab = gBrowser.selectedTab =
       gBrowser.addTab("data:text/html;charset=utf-8," + text);
new file mode 100644
--- /dev/null
+++ b/browser/components/translation/test/fixtures/bug1022725-fr.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html lang="fr">
+  <head>
+    <!--
+     - Text retrieved from http://fr.wikipedia.org/wiki/Coupe_du_monde_de_football_de_2014
+     - at 06/13/2014, Creative Commons Attribution-ShareAlike License.
+     -->
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>test</title>
+  </head>
+  <body>
+    <h1>Coupe du monde de football de 2014</h1>
+    <div>La Coupe du monde de football de 2014 est la 20e édition de la Coupe du monde de football, compétition organisée par la FIFA et qui réunit les trente-deux meilleures sélections nationales. Sa phase finale a lieu à l'été 2014 au Brésil. Avec le pays organisateur, toutes les équipes championnes du monde depuis 1930 (Uruguay, Italie, Allemagne, Angleterre, Argentine, France et Espagne) se sont qualifiées pour cette compétition. Elle est aussi la première compétition internationale de la Bosnie-Herzégovine.</div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/translation/test/fixtures/result-da39a3ee5e.txt
@@ -0,0 +1,22 @@
+<ArrayOfTranslateArrayResponse xmlns="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+  <TranslateArrayResponse>
+    <From>fr</From>
+    <OriginalTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
+      <a:int>34</a:int>
+    </OriginalTextSentenceLengths>
+    <TranslatedText>Football's 2014 World Cup</TranslatedText>
+    <TranslatedTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
+      <a:int>25</a:int>
+    </TranslatedTextSentenceLengths>
+  </TranslateArrayResponse>
+  <TranslateArrayResponse>
+    <From>fr</From>
+    <OriginalTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
+      <a:int>508</a:int>
+    </OriginalTextSentenceLengths>
+    <TranslatedText>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus diam sem, porttitor eget neque sit amet, ultricies posuere metus. Cras placerat rutrum risus, nec dignissim magna dictum vitae. Fusce eleifend fermentum lacinia. Nulla sagittis cursus nibh. Praesent adipiscing, elit at pulvinar dapibus, neque massa tincidunt sapien, eu consectetur lectus metus sit amet odio. Proin blandit consequat porttitor. Pellentesque vehicula justo sed luctus vestibulum. Donec metus.</TranslatedText>
+    <TranslatedTextSentenceLengths xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
+      <a:int>475</a:int>
+    </TranslatedTextSentenceLengths>
+  </TranslateArrayResponse>
+</ArrayOfTranslateArrayResponse>
--- a/browser/components/translation/test/unit/test_healthreport.js
+++ b/browser/components/translation/test/unit/test_healthreport.js
@@ -64,31 +64,57 @@ add_task(function* test_translation_oppo
   let day = values.days.getDay(now);
   Assert.ok(day.has("translationOpportunityCount"));
   Assert.equal(day.get("translationOpportunityCount"), 1);
 
   Assert.ok(day.has("translationOpportunityCountsByLanguage"));
   let countsByLanguage = JSON.parse(day.get("translationOpportunityCountsByLanguage"));
   Assert.equal(countsByLanguage["fr"], 1);
 
+  // Record a missed opportunity.
+  yield provider.recordMissedTranslationOpportunity("it", now);
+
+  values = yield m.getValues();
+  day = values.days.getDay(now);
+  Assert.equal(values.days.size, 1);
+  Assert.ok(values.days.hasDay(now));
+  Assert.ok(day.has("missedTranslationOpportunityCount"));
+  Assert.equal(day.get("missedTranslationOpportunityCount"), 1);
+
+  Assert.ok(day.has("missedTranslationOpportunityCountsByLanguage"));
+  let missedCountsByLanguage = JSON.parse(day.get("missedTranslationOpportunityCountsByLanguage"));
+  Assert.equal(missedCountsByLanguage["it"], 1);
+
   // Record more opportunities.
   yield provider.recordTranslationOpportunity("fr", now);
   yield provider.recordTranslationOpportunity("fr", now);
   yield provider.recordTranslationOpportunity("es", now);
 
+  yield provider.recordMissedTranslationOpportunity("it", now);
+  yield provider.recordMissedTranslationOpportunity("cs", now);
+  yield provider.recordMissedTranslationOpportunity("fi", now);
+
   values = yield m.getValues();
-  let day = values.days.getDay(now);
+  day = values.days.getDay(now);
   Assert.ok(day.has("translationOpportunityCount"));
   Assert.equal(day.get("translationOpportunityCount"), 4);
+  Assert.ok(day.has("missedTranslationOpportunityCount"));
+  Assert.equal(day.get("missedTranslationOpportunityCount"), 4);
 
   Assert.ok(day.has("translationOpportunityCountsByLanguage"));
   countsByLanguage = JSON.parse(day.get("translationOpportunityCountsByLanguage"));
   Assert.equal(countsByLanguage["fr"], 3);
   Assert.equal(countsByLanguage["es"], 1);
 
+  Assert.ok(day.has("missedTranslationOpportunityCountsByLanguage"));
+  missedCountsByLanguage = JSON.parse(day.get("missedTranslationOpportunityCountsByLanguage"));
+  Assert.equal(missedCountsByLanguage["it"], 2);
+  Assert.equal(missedCountsByLanguage["cs"], 1);
+  Assert.equal(missedCountsByLanguage["fi"], 1);
+
   yield provider.shutdown();
   yield storage.close();
 });
 
 // Test recording a translation.
 add_task(function* test_record_translation() {
   let storage = yield Metrics.Storage("translation");
   let provider = new TranslationProvider();
--- a/browser/components/translation/translation-infobar.xml
+++ b/browser/components/translation/translation-infobar.xml
@@ -147,21 +147,16 @@
           ]]>
         </setter>
       </property>
 
       <method name="init">
         <parameter name="aTranslation"/>
         <body>
           <![CDATA[
-            if (Translation.serviceUnavailable) {
-              this.state = Translation.STATE_UNAVAILABLE;
-              return;
-            }
-
             this.translation = aTranslation;
             let bundle = Cc["@mozilla.org/intl/stringbundle;1"]
                            .getService(Ci.nsIStringBundleService)
                            .createBundle("chrome://global/locale/languageNames.properties");
 
             // Fill the lists of supported source languages.
             let detectedLanguage = this._getAnonElt("detectedLanguage");
             let fromLanguage = this._getAnonElt("fromLanguage");
--- a/browser/devtools/app-manager/content/connection-footer.xhtml
+++ b/browser/devtools/app-manager/content/connection-footer.xhtml
@@ -122,17 +122,17 @@
 
       </div>
     </div>
   </body>
 
   <template id="simulator-item-template">
   <span>
     <button class="simulator-item action-primary" onclick="UI.startSimulator(this.dataset.version)" template='{"type":"attribute","path":"version","name":"data-version"}' title="&connection.startSimulatorTooltip;">
-      <span template='{"type":"textContent", "path":"version"}'></span>
+      <span template='{"type":"textContent", "path":"label"}'></span>
     </button>
   </span>
   </template>
 
   <template id="adb-devices-template">
   <span>
     <button class="adb-device action-primary" onclick="UI.connectToAdbDevice(this.dataset.name)" template='{"type":"attribute","path":"name","name":"data-name"}'>
       <span template='{"type":"textContent", "path":"name"}'></span>
--- a/browser/devtools/app-manager/simulators-store.js
+++ b/browser/devtools/app-manager/simulators-store.js
@@ -5,17 +5,21 @@
 const {Cu} = require("chrome");
 const ObservableObject = require("devtools/shared/observable-object");
 const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
 
 let store = new ObservableObject({versions:[]});
 
 function feedStore() {
   store.object.versions = Simulator.availableVersions().map(v => {
-    return {version:v}
+    let simulator = Simulator.getByVersion(v);
+    return {
+      version: v,
+      label: simulator.appinfo.label
+    }
   });
 }
 
 Simulator.on("register", feedStore);
 Simulator.on("unregister", feedStore);
 feedStore();
 
 module.exports = store;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/content/addons.js
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Cu = Components.utils;
+const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+const {GetAvailableAddons} = require("devtools/webide/addons");
+const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
+
+window.addEventListener("load", function onLoad() {
+  window.removeEventListener("load", onLoad);
+  document.querySelector("#aboutaddons").onclick = function() {
+    window.parent.UI.openInBrowser("about:addons");
+  }
+  document.querySelector("#close").onclick = CloseUI;
+  GetAvailableAddons().then(BuildUI, (e) => {
+    console.error(e);
+    window.alert(Strings.formatStringFromName("error_cantFetchAddonsJSON", [e], 1));
+  });
+}, true);
+
+function CloseUI() {
+  window.parent.UI.openProject();
+}
+
+function BuildUI(addons) {
+  BuildItem(addons.adb, true /* is adb */);
+  for (let addon of addons.simulators) {
+    BuildItem(addon, false /* is adb */);
+  }
+}
+
+function BuildItem(addon, isADB) {
+
+  function onAddonUpdate(event, arg) {
+    switch (event) {
+      case "update":
+        progress.removeAttribute("value");
+        li.setAttribute("status", addon.status);
+        status.textContent = Strings.GetStringFromName("addons_status_" + addon.status);
+        break;
+      case "failure":
+        console.error(arg);
+        window.alert(arg);
+        break;
+      case "progress":
+        if (arg == -1) {
+          progress.removeAttribute("value");
+        } else {
+          progress.value = arg;
+        }
+        break;
+    }
+  }
+
+  let events = ["update", "failure", "progress"];
+  for (let e of events) {
+    addon.on(e, onAddonUpdate);
+  }
+  window.addEventListener("unload", function onUnload() {
+    window.removeEventListener("unload", onUnload);
+    for (let e of events) {
+      addon.off(e, onAddonUpdate);
+    }
+  });
+
+  let li = document.createElement("li");
+  li.setAttribute("status", addon.status);
+
+  // Used in tests
+  if (isADB) {
+    li.setAttribute("addon", "adb");
+  } else {
+    li.setAttribute("addon", "simulator-" + addon.version);
+  }
+
+  let name = document.createElement("span");
+  name.className = "name";
+  if (isADB) {
+    name.textContent = Strings.GetStringFromName("addons_adb_label");
+  } else {
+    let stability = Strings.GetStringFromName("addons_" + addon.stability);
+    name.textContent = Strings.formatStringFromName("addons_simulator_label", [addon.version, stability], 2);
+  }
+
+  li.appendChild(name);
+
+  let status = document.createElement("span");
+  status.className = "status";
+  status.textContent = Strings.GetStringFromName("addons_status_" + addon.status);
+  li.appendChild(status);
+
+  let installButton = document.createElement("button");
+  installButton.className = "install-button";
+  installButton.onclick = () => addon.install();
+  installButton.textContent = Strings.GetStringFromName("addons_install_button");
+  li.appendChild(installButton);
+
+  let uninstallButton = document.createElement("button");
+  uninstallButton.className = "uninstall-button";
+  uninstallButton.onclick = () => addon.uninstall();
+  uninstallButton.textContent = Strings.GetStringFromName("addons_uninstall_button");
+  li.appendChild(uninstallButton);
+
+  let progress = document.createElement("progress");
+  li.appendChild(progress);
+
+  if (isADB) {
+    let warning = document.createElement("p");
+    warning.textContent = Strings.GetStringFromName("addons_adb_warning");
+    warning.className = "warning";
+    li.appendChild(warning);
+  }
+
+  document.querySelector("ul").appendChild(li);
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/content/addons.xhtml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE html [
+  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta charset="utf8"/>
+    <link rel="stylesheet" href="chrome://webide/skin/addons.css" type="text/css"/>
+    <script type="application/javascript;version=1.8" src="chrome://webide/content/addons.js"></script>
+  </head>
+  <body>
+
+    <div id="controls">
+      <a id="aboutaddons">&addons_aboutaddons;</a>
+      <a id="close">&deck_close;</a>
+    </div>
+
+    <h1>&addons_title;</h1>
+
+    <ul></ul>
+
+  </body>
+</html>
--- a/browser/devtools/webide/content/jar.mn
+++ b/browser/devtools/webide/content/jar.mn
@@ -6,14 +6,20 @@ webide.jar:
 %   content webide %content/
     content/webide.xul                (webide.xul)
     content/webide.js                 (webide.js)
     content/newapp.xul                (newapp.xul)
     content/newapp.js                 (newapp.js)
     content/details.xhtml             (details.xhtml)
     content/details.js                (details.js)
     content/cli.js                    (cli.js)
+    content/addons.js                 (addons.js)
+    content/addons.xhtml              (addons.xhtml)
+    content/permissionstable.js       (permissionstable.js)
+    content/permissionstable.xhtml    (permissionstable.xhtml)
+    content/runtimedetails.js         (runtimedetails.js)
+    content/runtimedetails.xhtml      (runtimedetails.xhtml)
 
 # Temporarily include locales in content, until we're ready
 # to localize webide
 
     content/webide.dtd                (../locales/en-US/webide.dtd)
     content/webide.properties         (../locales/en-US/webide.properties)
--- a/browser/devtools/webide/content/newapp.js
+++ b/browser/devtools/webide/content/newapp.js
@@ -12,46 +12,39 @@ Cu.import("resource://gre/modules/XPCOMU
 XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils", "resource://gre/modules/ZipUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
 
 const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
 const {AppProjects} = require("devtools/app-manager/app-projects");
 const APP_CREATOR_LIST = "devtools.webide.templatesURL";
 const {AppManager} = require("devtools/webide/app-manager");
+const {GetTemplatesJSON} = require("devtools/webide/remote-resources");
 
 let gTemplateList = null;
 
 // See bug 989619
 console.log = console.log.bind(console);
 console.warn = console.warn.bind(console);
 console.error = console.error.bind(console);
 
 window.addEventListener("load", function onLoad() {
   window.removeEventListener("load", onLoad);
   let projectNameNode = document.querySelector("#project-name");
   projectNameNode.addEventListener("input", canValidate, true);
   getJSON();
 }, true);
 
 function getJSON() {
-  let xhr = new XMLHttpRequest();
-  xhr.overrideMimeType('text/plain');
-  xhr.onload = function() {
-    let list;
-    try {
-      list = JSON.parse(this.responseText);
-      if (!Array.isArray(list)) {
-        throw new Error("JSON response not an array");
-      }
-      if (list.length == 0) {
-        throw new Error("JSON response is an empty array");
-      }
-    } catch(e) {
-      return failAndBail("Invalid response from server");
+  GetTemplatesJSON().then(list => {
+    if (!Array.isArray(list)) {
+      throw new Error("JSON response not an array");
+    }
+    if (list.length == 0) {
+      throw new Error("JSON response is an empty array");
     }
     gTemplateList = list;
     let templatelistNode = document.querySelector("#templatelist");
     templatelistNode.innerHTML = "";
     for (let template of list) {
       let richlistitemNode = document.createElement("richlistitem");
       let imageNode = document.createElement("image");
       imageNode.setAttribute("src", template.icon);
@@ -71,23 +64,19 @@ function getJSON() {
 
     /* Chrome mochitest support */
     let testOptions = window.arguments[0].testOptions;
     if (testOptions) {
       templatelistNode.selectedIndex = testOptions.index;
       document.querySelector("#project-name").value = testOptions.name;
       doOK();
     }
-  };
-  xhr.onerror = function() {
-    failAndBail("Can't download app templates");
-  };
-  let url = Services.prefs.getCharPref(APP_CREATOR_LIST);
-  xhr.open("get", url);
-  xhr.send();
+  }, (e) => {
+    failAndBail("Can't download app templates: " + e);
+  });
 }
 
 function failAndBail(msg) {
   let promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
   promptService.alert(window, "error", msg);
   window.close();
 }
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/content/permissionstable.js
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Cu = Components.utils;
+const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+const {AppManager} = require("devtools/webide/app-manager");
+const {Connection} = require("devtools/client/connection-manager");
+
+window.addEventListener("load", function onLoad() {
+  window.removeEventListener("load", onLoad);
+  document.querySelector("#close").onclick = CloseUI;
+  AppManager.on("app-manager-update", OnAppManagerUpdate);
+  BuildUI();
+}, true);
+
+window.addEventListener("unload", function onUnload() {
+  window.removeEventListener("unload", onUnload);
+  AppManager.off("app-manager-update", OnAppManagerUpdate);
+});
+
+function CloseUI() {
+  window.parent.UI.openProject();
+}
+
+function OnAppManagerUpdate(event, what) {
+  if (what == "connection" || what == "list-tabs-response") {
+    BuildUI();
+  }
+}
+
+let getRawPermissionsTablePromise; // Used by tests
+function BuildUI() {
+  let table = document.querySelector("table");
+  let lines = table.querySelectorAll(".line");
+  for (let line of lines) {
+    line.remove();
+  }
+
+  if (AppManager.connection &&
+      AppManager.connection.status == Connection.Status.CONNECTED &&
+      AppManager.deviceFront) {
+    getRawPermissionsTablePromise = AppManager.deviceFront.getRawPermissionsTable();
+    getRawPermissionsTablePromise.then(json => {
+      let permissionsTable = json.rawPermissionsTable;
+      for (let name in permissionsTable) {
+        let tr = document.createElement("tr");
+        tr.className = "line";
+        let td = document.createElement("td");
+        td.textContent = name;
+        tr.appendChild(td);
+        for (let type of ["app","privileged","certified"]) {
+          let td = document.createElement("td");
+          if (permissionsTable[name][type] == json.ALLOW_ACTION) {
+            td.textContent = "✓";
+            td.className = "permallow";
+          }
+          if (permissionsTable[name][type] == json.PROMPT_ACTION) {
+            td.textContent = "!";
+            td.className = "permprompt";
+          }
+          if (permissionsTable[name][type] == json.DENY_ACTION) {
+            td.textContent = "✕";
+            td.className = "permdeny"
+          }
+          tr.appendChild(td);
+        }
+        table.appendChild(tr);
+      }
+    });
+  } else {
+    CloseUI();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/content/permissionstable.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE html [
+  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta charset="utf8"/>
+    <link rel="stylesheet" href="chrome://webide/skin/tabledoc.css" type="text/css"/>
+    <script type="application/javascript;version=1.8" src="chrome://webide/content/permissionstable.js"></script>
+  </head>
+  <body>
+
+    <div id="controls">
+      <a id="close">&deck_close;</a>
+    </div>
+
+    <h1>&permissionstable_title;</h1>
+
+    <table class="permissionstable">
+      <tr>
+        <th>&permissionstable_name_header;</th>
+        <th>type:web</th>
+        <th>type:privileged</th>
+        <th>type:certified</th>
+      </tr>
+    </table>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/content/runtimedetails.js
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Cu = Components.utils;
+const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+const {AppManager} = require("devtools/webide/app-manager");
+const {Connection} = require("devtools/client/connection-manager");
+
+window.addEventListener("load", function onLoad() {
+  window.removeEventListener("load", onLoad);
+  document.querySelector("#close").onclick = CloseUI;
+  AppManager.on("app-manager-update", OnAppManagerUpdate);
+  BuildUI();
+}, true);
+
+window.addEventListener("unload", function onUnload() {
+  window.removeEventListener("unload", onUnload);
+  AppManager.off("app-manager-update", OnAppManagerUpdate);
+});
+
+function CloseUI() {
+  window.parent.UI.openProject();
+}
+
+function OnAppManagerUpdate(event, what) {
+  if (what == "connection" || what == "list-tabs-response") {
+    BuildUI();
+  }
+}
+
+let getDescriptionPromise; // Used by tests
+function BuildUI() {
+  let table = document.querySelector("table");
+  table.innerHTML = "";
+  if (AppManager.connection &&
+      AppManager.connection.status == Connection.Status.CONNECTED &&
+      AppManager.deviceFront) {
+    getDescriptionPromise = AppManager.deviceFront.getDescription();
+    getDescriptionPromise.then(json => {
+      for (let name in json) {
+        let tr = document.createElement("tr");
+        let td = document.createElement("td");
+        td.textContent = name;
+        tr.appendChild(td);
+        td = document.createElement("td");
+        td.textContent = json[name];
+        tr.appendChild(td);
+        table.appendChild(tr);
+      }
+    });
+  } else {
+    CloseUI();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/content/runtimedetails.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- 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/. -->
+
+<!DOCTYPE html [
+  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
+  %webideDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta charset="utf8"/>
+    <link rel="stylesheet" href="chrome://webide/skin/tabledoc.css" type="text/css"/>
+    <script type="application/javascript;version=1.8" src="chrome://webide/content/runtimedetails.js"></script>
+  </head>
+  <body>
+
+    <div id="controls">
+      <a id="close">&deck_close;</a>
+    </div>
+
+    <h1>&runtimedetails_title;</h1>
+
+    <table></table>
+  </body>
+</html>
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -13,22 +13,29 @@ Cu.import("resource://gre/modules/Task.j
 const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const {require} = devtools;
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {AppProjects} = require("devtools/app-manager/app-projects");
 const {Connection} = require("devtools/client/connection-manager");
 const {AppManager} = require("devtools/webide/app-manager");
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 const ProjectEditor = require("projecteditor/projecteditor");
+const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
+const {GetAvailableAddons} = require("devtools/webide/addons");
+const {GetTemplatesJSON, GetAddonsJSON} = require("devtools/webide/remote-resources");
 
 const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
 
 const HTML = "http://www.w3.org/1999/xhtml";
 const HELP_URL = "https://developer.mozilla.org/Firefox_OS/Using_the_App_Manager#Troubleshooting";
 
+// download some JSON early.
+GetTemplatesJSON(true);
+GetAddonsJSON(true);
+
 // See bug 989619
 console.log = console.log.bind(console);
 console.warn = console.warn.bind(console);
 console.error = console.error.bind(console);
 
 window.addEventListener("load", function onLoad() {
   window.removeEventListener("load", onLoad);
   UI.init();
@@ -51,28 +58,42 @@ let UI = {
 
     this.updateCommands();
     this.updateRuntimeList();
 
     this.onfocus = this.onfocus.bind(this);
     window.addEventListener("focus", this.onfocus, true);
 
     AppProjects.load().then(() => {
-      let lastProjectLocation = Services.prefs.getCharPref("devtools.webide.lastprojectlocation");
-      if (lastProjectLocation) {
-        let lastProject = AppProjects.get(lastProjectLocation);
-        if (lastProject) {
-          AppManager.selectedProject = lastProject;
-        } else {
-          AppManager.selectedProject = null;
-        }
+      this.openLastProject();
+    });
+
+    // Auto install the ADB Addon Helper. Only once.
+    // If the user decides to uninstall the addon, we won't install it again.
+    let autoInstallADBHelper = Services.prefs.getBoolPref("devtools.webide.autoinstallADBHelper");
+    if (autoInstallADBHelper && !Devices.helperAddonInstalled) {
+      GetAvailableAddons().then(addons => {
+        addons.adb.install();
+      }, console.error);
+    }
+    Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
+  },
+
+  openLastProject: function() {
+    let lastProjectLocation = Services.prefs.getCharPref("devtools.webide.lastprojectlocation");
+    if (lastProjectLocation) {
+      let lastProject = AppProjects.get(lastProjectLocation);
+      if (lastProject) {
+        AppManager.selectedProject = lastProject;
       } else {
         AppManager.selectedProject = null;
       }
-    });
+    } else {
+      AppManager.selectedProject = null;
+    }
   },
 
   uninit: function() {
     window.removeEventListener("focus", this.onfocus, true);
     AppManager.off("app-manager-update", this.appManagerUpdate);
     AppManager.uninit();
     window.removeEventListener("message", this.onMessage);
   },
@@ -198,29 +219,51 @@ let UI = {
     let buttons = [{
       label: Strings.GetStringFromName("notification_showTroubleShooting_label"),
       accessKey: Strings.GetStringFromName("notification_showTroubleShooting_accesskey"),
       callback: function () {
         Cmds.showTroubleShooting();
       }
     }];
 
-    let nbox = document.querySelector("#body");
+    let nbox = document.querySelector("#notificationbox");
     nbox.removeAllNotifications(true);
     nbox.appendNotification(text, "webide:errornotification", null,
                             nbox.PRIORITY_WARNING_LOW, buttons);
   },
 
   /********** RUNTIME **********/
 
   updateRuntimeList: function() {
     let USBListNode = document.querySelector("#runtime-panel-usbruntime");
     let simulatorListNode = document.querySelector("#runtime-panel-simulators");
     let customListNode = document.querySelector("#runtime-panel-custom");
 
+    let noHelperNode = document.querySelector("#runtime-panel-noadbhelper");
+    let noUSBNode = document.querySelector("#runtime-panel-nousbdevice");
+    let noSimulatorNode = document.querySelector("#runtime-panel-nosimulator");
+
+    if (Devices.helperAddonInstalled) {
+      noHelperNode.setAttribute("hidden", "true");
+    } else {
+      noHelperNode.removeAttribute("hidden");
+    }
+
+    if (AppManager.runtimeList.usb.length == 0 && Devices.helperAddonInstalled) {
+      noUSBNode.removeAttribute("hidden");
+    } else {
+      noUSBNode.setAttribute("hidden", "true");
+    }
+
+    if (AppManager.runtimeList.simulator.length > 0) {
+      noSimulatorNode.setAttribute("hidden", "true");
+    } else {
+      noSimulatorNode.removeAttribute("hidden");
+    }
+
     for (let [type, parent] of [
       ["usb", USBListNode],
       ["simulator", simulatorListNode],
       ["custom", customListNode],
     ]) {
       while (parent.hasChildNodes()) {
         parent.firstChild.remove();
       }
@@ -278,17 +321,17 @@ let UI = {
 
   // ProjectEditor & details screen
 
   getProjectEditor: function() {
     if (this.projecteditor) {
       return this.projecteditor.loaded;
     }
 
-    let projecteditorIframe = document.querySelector("#projecteditor");
+    let projecteditorIframe = document.querySelector("#deck-panel-projecteditor");
     this.projecteditor = ProjectEditor.ProjectEditor(projecteditorIframe);
     this.projecteditor.on("onEditorSave", (editor, resource) => {
       AppManager.validateProject(AppManager.selectedProject);
     });
     return this.projecteditor.loaded;
   },
 
   updateProjectEditorHeader: function() {
@@ -301,72 +344,84 @@ let UI = {
       status = "error";
     }
     this.getProjectEditor().then((projecteditor) => {
       projecteditor.setProjectToAppPath(project.location, {
         name: project.name,
         iconUrl: project.icon,
         projectOverviewURL: "chrome://webide/content/details.xhtml",
         validationStatus: status
-      });
+      }).then(null, console.error);
     }, console.error);
   },
 
   isProjectEditorEnabled: function() {
     return Services.prefs.getBoolPref("devtools.webide.showProjectEditor");
   },
 
   openProject: function() {
-    let detailsIframe = document.querySelector("#details");
-    let projecteditorIframe = document.querySelector("#projecteditor");
-
     let project = AppManager.selectedProject;
 
     // Nothing to show
 
     if (!project) {
-      detailsIframe.setAttribute("hidden", "true");
-      projecteditorIframe.setAttribute("hidden", "true");
-      document.commandDispatcher.focusedElement = document.documentElement;
+      this.resetDeck();
       return;
     }
 
     // Make sure the directory exist before we show Project Editor
 
     let forceDetailsOnly = false;
     if (project.type == "packaged") {
       let directory = new FileUtils.File(project.location);
       forceDetailsOnly = !directory.exists();
     }
 
     // Show only the details screen
 
     if (project.type != "packaged" ||
         !this.isProjectEditorEnabled() ||
         forceDetailsOnly) {
-      detailsIframe.removeAttribute("hidden");
-      projecteditorIframe.setAttribute("hidden", "true");
-      document.commandDispatcher.focusedElement = document.documentElement;
+      this.selectDeckPanel("details");
       return;
     }
 
     // Show ProjectEditor
 
-    detailsIframe.setAttribute("hidden", "true");
-    projecteditorIframe.removeAttribute("hidden");
+    this.selectDeckPanel("projecteditor");
 
     this.getProjectEditor().then(() => {
       this.updateProjectEditorHeader();
     }, console.error);
 
     if (project.location) {
       Services.prefs.setCharPref("devtools.webide.lastprojectlocation", project.location);
     }
   },
 
+  /********** DECK **********/
+
+  resetFocus: function() {
+    document.commandDispatcher.focusedElement = document.documentElement;
+  },
+
+  selectDeckPanel: function(id) {
+    this.hidePanels();
+    this.resetFocus();
+    let deck = document.querySelector("#deck");
+    let panel = deck.querySelector("#deck-panel-" + id);
+    deck.selectedPanel = panel;
+  },
+
+  resetDeck: function() {
+    this.resetFocus();
+    let deck = document.querySelector("#deck");
+    deck.selectedPanel = null;
+  },
+
   /********** COMMANDS **********/
 
   updateCommands: function() {
 
     if (document.querySelector("window").classList.contains("busy")) {
       document.querySelector("#cmd_newApp").setAttribute("disabled", "true");
       document.querySelector("#cmd_importPackagedApp").setAttribute("disabled", "true");
       document.querySelector("#cmd_importHostedApp").setAttribute("disabled", "true");
@@ -501,19 +556,17 @@ let UI = {
     iframe.height = height;
 
     document.querySelector("#action-button-debug").setAttribute("active", "true");
 
     return gDevTools.showToolbox(target, null, host, options);
   },
 
   closeToolboxUI: function() {
-    let body = document.querySelector("#body");
-    body.removeAttribute("hidden");
-
+    this.resetFocus();
     Services.prefs.setIntPref("devtools.toolbox.footer.height", this.toolboxIframe.height);
 
     // We have to destroy the iframe, otherwise, the keybindings of webide don't work
     // properly anymore.
     this.toolboxIframe.remove();
     this.toolboxIframe = null;
 
     let splitter = document.querySelector(".devtools-horizontal-splitter");
@@ -722,91 +775,21 @@ let Cmds = {
        return longstr.string().then(dataURL => {
          longstr.release().then(null, console.error);
          UI.openInBrowser(dataURL);
        });
     }), "taking screenshot");
   },
 
   showPermissionsTable: function() {
-    return UI.busyUntil(AppManager.deviceFront.getRawPermissionsTable().then(json => {
-      let styleContent = "";
-      styleContent += "body {background:white; font-family: monospace}";
-      styleContent += "table {border-collapse: collapse}";
-      styleContent += "th, td {padding: 5px; border: 1px solid #EEE}";
-      styleContent += "th {min-width: 130px}";
-      styleContent += "td {text-align: center}";
-      styleContent += "th:first-of-type, td:first-of-type {text-align:left}";
-      styleContent += ".permallow  {color:rgb(152, 207, 57)}";
-      styleContent += ".permprompt {color:rgb(0,158,237)}";
-      styleContent += ".permdeny   {color:rgb(204,73,8)}";
-      let style = document.createElementNS(HTML, "style");
-      style.textContent = styleContent;
-      let table = document.createElementNS(HTML, "table");
-      table.innerHTML = "<tr><th>Name</th><th>type:web</th><th>type:privileged</th><th>type:certified</th></tr>";
-      let permissionsTable = json.rawPermissionsTable;
-      for (let name in permissionsTable) {
-        let tr = document.createElementNS(HTML, "tr");
-        let td = document.createElementNS(HTML, "td");
-        td.textContent = name;
-        tr.appendChild(td);
-        for (let type of ["app","privileged","certified"]) {
-          let td = document.createElementNS(HTML, "td");
-          if (permissionsTable[name][type] == json.ALLOW_ACTION) {
-            td.textContent = "✓";
-            td.className = "permallow";
-          }
-          if (permissionsTable[name][type] == json.PROMPT_ACTION) {
-            td.textContent = "!";
-            td.className = "permprompt";
-          }
-          if (permissionsTable[name][type] == json.DENY_ACTION) {
-            td.textContent = "✕";
-            td.className = "permdeny"
-          }
-          tr.appendChild(td);
-        }
-        table.appendChild(tr);
-      }
-      let body = document.createElementNS(HTML, "body");
-      body.appendChild(style);
-      body.appendChild(table);
-      let url = "data:text/html;charset=utf-8,";
-      url += encodeURIComponent(body.outerHTML);
-      UI.openInBrowser(url);
-    }), "showing permission table");
+    UI.selectDeckPanel("permissionstable");
   },
 
   showRuntimeDetails: function() {
-    return UI.busyUntil(AppManager.deviceFront.getDescription().then(json => {
-      let styleContent = "";
-      styleContent += "body {background:white; font-family: monospace}";
-      styleContent += "table {border-collapse: collapse}";
-      styleContent += "th, td {padding: 5px; border: 1px solid #EEE}";
-      let style = document.createElementNS(HTML, "style");
-      style.textContent = styleContent;
-      let table = document.createElementNS(HTML, "table");
-      for (let name in json) {
-        let tr = document.createElementNS(HTML, "tr");
-        let td = document.createElementNS(HTML, "td");
-        td.textContent = name;
-        tr.appendChild(td);
-        td = document.createElementNS(HTML, "td");
-        td.textContent = json[name];
-        tr.appendChild(td);
-        table.appendChild(tr);
-      }
-      let body = document.createElementNS(HTML, "body");
-      body.appendChild(style);
-      body.appendChild(table);
-      let url = "data:text/html;charset=utf-8,";
-      url += encodeURIComponent(body.outerHTML);
-      UI.openInBrowser(url);
-    }), "showing runtime details");
-
+    UI.selectDeckPanel("runtimedetails");
   },
 
   play: function() {
     switch(AppManager.selectedProject.type) {
       case "packaged":
       case "hosted":
         return UI.busyUntil(AppManager.installAndRunProject(), "installing and running app");
         break;
@@ -841,9 +824,13 @@ let Cmds = {
   toggleEditors: function() {
     Services.prefs.setBoolPref("devtools.webide.showProjectEditor", !UI.isProjectEditorEnabled());
     UI.openProject();
   },
 
   showTroubleShooting: function() {
     UI.openInBrowser(HELP_URL);
   },
+
+  showAddons: function() {
+    UI.selectDeckPanel("addons");
+  },
 }
--- a/browser/devtools/webide/content/webide.xul
+++ b/browser/devtools/webide/content/webide.xul
@@ -36,16 +36,18 @@
       <command id="cmd_removeProject" oncommand="Cmds.removeProject()" label="&projectMenu_remove_label;"/>
       <command id="cmd_showProjectPanel" oncommand="Cmds.showProjectPanel()"/>
       <command id="cmd_showRuntimePanel" oncommand="Cmds.showRuntimePanel()"/>
       <command id="cmd_disconnectRuntime" oncommand="Cmds.disconnectRuntime()" label="&runtimeMenu_disconnect_label;"/>
       <command id="cmd_showPermissionsTable" oncommand="Cmds.showPermissionsTable()" label="&runtimeMenu_showPermissionTable_label;"/>
       <command id="cmd_showRuntimeDetails" oncommand="Cmds.showRuntimeDetails()" label="&runtimeMenu_showDetails_label;"/>
       <command id="cmd_takeScreenshot" oncommand="Cmds.takeScreenshot()" label="&runtimeMenu_takeScreenshot_label;"/>
       <command id="cmd_toggleEditor" oncommand="Cmds.toggleEditors()" label="&viewMenu_toggleEditor_label;"/>
+      <command id="cmd_showAddons" oncommand="Cmds.showAddons()"/>
+      <command id="cmd_showTroubleShooting" oncommand="Cmds.showTroubleShooting()"/>
       <command id="cmd_play" oncommand="Cmds.play()"/>
       <command id="cmd_stop" oncommand="Cmds.stop()"/>
       <command id="cmd_toggleToolbox" oncommand="Cmds.toggleToolbox()"/>
     </commandset>
   </commandset>
 
   <menubar id="main-menubar">
     <menu id="menu-project" label="&projectMenu_label;" accesskey="&projectMenu_accesskey;">
@@ -71,16 +73,17 @@
         <menuseparator/>
         <menuitem command="cmd_disconnectRuntime" accesskey="&runtimeMenu_disconnect_accesskey;"/>
       </menupopup>
     </menu>
 
     <menu id="menu-view" label="&viewMenu_label;" accesskey="&viewMenu_accesskey;">
       <menupopup id="menu-ViewPopup">
         <menuitem command="cmd_toggleEditor" key="key_toggleEditor" accesskey="&viewMenu_toggleEditor_accesskey;"/>
+        <menuitem command="cmd_showAddons" label="&viewMenu_showAddons_label;" accesskey="&viewMenu_showAddons_accesskey;"/>
       </menupopup>
     </menu>
 
   </menubar>
 
   <keyset id="mainKeyset">
     <key key="&key_quit;" id="key_quit" command="cmd_quit" modifiers="accel"/>
     <key key="&key_showProjectPanel;" id="key_showProjectPanel" command="cmd_showProjectPanel" modifiers="accel"/>
@@ -131,33 +134,41 @@
         <vbox flex="1" id="project-panel-runtimeapps"/>
       </vbox>
     </panel>
 
     <!-- Runtime panel -->
     <panel id="runtime-panel" type="arrow" position="bottomcenter topright" consumeoutsideclicks="true" animate="false">
       <vbox flex="1">
         <label class="panel-header">&runtimePanel_USBDevices;</label>
+        <toolbarbutton class="panel-item-help" label="&runtimePanel_nousbdevice;" id="runtime-panel-nousbdevice" command="cmd_showTroubleShooting"/>
+        <toolbarbutton class="panel-item-help" label="&runtimePanel_noadbhelper;" id="runtime-panel-noadbhelper" command="cmd_showAddons"/>
         <vbox id="runtime-panel-usbruntime"></vbox>
         <label class="panel-header">&runtimePanel_simulators;</label>
+        <toolbarbutton class="panel-item-help" label="&runtimePanel_nosimulator;" id="runtime-panel-nosimulator" command="cmd_showAddons"/>
         <vbox id="runtime-panel-simulators"></vbox>
         <label class="panel-header">&runtimePanel_custom;</label>
         <vbox id="runtime-panel-custom"></vbox>
         <vbox flex="1" id="runtime-actions" hidden="true">
           <toolbarbutton class="panel-item" id="runtime-details" command="cmd_showRuntimeDetails"/>
           <toolbarbutton class="panel-item" id="runtime-permissions" command="cmd_showPermissionsTable"/>
           <toolbarbutton class="panel-item" id="runtime-screenshot"  command="cmd_takeScreenshot"/>
         </vbox>
       </vbox>
     </panel>
 
   </popupset>
 
-  <notificationbox flex="1" id="body">
-    <iframe id="details" flex="1" hidden="true" src="details.xhtml"/>
-    <iframe id="projecteditor" flex="1" hidden="true"/>
+  <notificationbox flex="1" id="notificationbox">
+    <deck flex="1" id="deck" selectedIndex="-1">
+      <iframe id="deck-panel-details" flex="1" src="details.xhtml"/>
+      <iframe id="deck-panel-projecteditor" flex="1"/>
+      <iframe id="deck-panel-addons" flex="1" src="addons.xhtml"/>
+      <iframe id="deck-panel-permissionstable" flex="1" src="permissionstable.xhtml"/>
+      <iframe id="deck-panel-runtimedetails" flex="1" src="runtimedetails.xhtml"/>
+    </deck>
   </notificationbox>
 
   <splitter hidden="true" class="devtools-horizontal-splitter" orient="vertical"/>
 
   <!-- toolbox iframe will be inserted here -->
 
 </window>
--- a/browser/devtools/webide/locales/en-US/webide.dtd
+++ b/browser/devtools/webide/locales/en-US/webide.dtd
@@ -33,16 +33,18 @@
 <!ENTITY runtimeMenu_takeScreenshot_accesskey "S">
 <!ENTITY runtimeMenu_showDetails_label "Runtime Info">
 <!ENTITY runtimeMenu_showDetails_accesskey "E">
 
 <!ENTITY viewMenu_label "View">
 <!ENTITY viewMenu_accesskey "V">
 <!ENTITY viewMenu_toggleEditor_label "Toggle Editor">
 <!ENTITY viewMenu_toggleEditor_accesskey "E">
+<!ENTITY viewMenu_showAddons_label "Manage simulators">
+<!ENTITY viewMenu_showAddons_accesskey "M">
 
 <!ENTITY projectButton_label "Open App">
 <!ENTITY runtimeButton_label "Select Runtime">
 
 <!-- We try to repicate Firefox' bindings: -->
 <!-- quit app -->
 <!ENTITY key_quit "Q">
 <!-- open menu -->
@@ -56,23 +58,42 @@
 <!-- toggle sidebar -->
 <!ENTITY key_toggleEditor "B">
 
 <!ENTITY projectPanel_myProjects "My Projects">
 <!ENTITY projectPanel_runtimeApps "Runtime Apps">
 <!ENTITY runtimePanel_USBDevices "USB Devices">
 <!ENTITY runtimePanel_simulators "Simulators">
 <!ENTITY runtimePanel_custom "Custom">
+<!ENTITY runtimePanel_nosimulator "Install Simulator">
+<!ENTITY runtimePanel_noadbhelper "Install ADB Helper">
+<!ENTITY runtimePanel_nousbdevice "Can't see your device?">
 
 <!-- Lense -->
 <!ENTITY details_valid_header "valid">
 <!ENTITY details_warning_header "warnings">
 <!ENTITY details_error_header "errors">
 <!ENTITY details_description "Description">
 <!ENTITY details_location "Location">
 <!ENTITY details_manifestURL "App ID">
 <!ENTITY details_removeProject_button "Remove Project">
 
 <!-- New App -->
 <!ENTITY newAppWindowTitle "New App">
 <!ENTITY newAppHeader "Select template">
 <!ENTITY newAppLoadingTemplate "Loading templates…">
 <!ENTITY newAppProjectName "Project Name:">
+
+
+<!-- Decks -->
+
+<!ENTITY deck_close "close">
+
+<!-- Addons -->
+<!ENTITY addons_title "Extra Components:">
+<!ENTITY addons_aboutaddons "Open Addons Manager">
+
+<!-- Permissions Table -->
+<!ENTITY permissionstable_title "Permissions Table">
+<!ENTITY permissionstable_name_header "Name">
+
+<!-- Runtime Details -->
+<!ENTITY runtimedetails_title "Runtime Info">
--- a/browser/devtools/webide/locales/en-US/webide.properties
+++ b/browser/devtools/webide/locales/en-US/webide.properties
@@ -21,8 +21,23 @@ notification_showTroubleShooting_label=t
 notification_showTroubleShooting_accesskey=t
 
 error_operationTimeout=Operation timed out: %1$S
 error_operationFail=Operation failed: %1$S
 error_listRunningApps=Can't get app list from device
 error_cantConnectToApp=Can't connect to app: %1$S
 error_cantInstallNotFullyConnected=Can't install project. Not fully connected.
 error_cantInstallValidationErrors=Can't install project. Validation errors.
+error_cantFetchAddonsJSON=Can't fetch the addon list: %S
+
+addons_stable=stable
+addons_unstable=unstable
+addons_simulator_label=Firefox OS %1$S Simulator (%2$S)
+addons_install_button=install
+addons_uninstall_button=uninstall
+addons_adb_label=ADB Addon Helper
+addons_adb_warning=USB devices won't be detected without this add-on
+addons_status_unknown=?
+addons_status_installed=Installed
+addons_status_uninstalled=Not Installed
+addons_status_preparing=preparing
+addons_status_downloading=downloading
+addons_status_installing=installing
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/modules/addons.js
@@ -0,0 +1,206 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {Cu} = require("chrome");
+const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm");
+const {AddonManager} = Cu.import("resource://gre/modules/AddonManager.jsm");
+const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js");
+const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
+const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
+const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+const {GetAddonsJSON} = require("devtools/webide/remote-resources");
+
+let SIMULATOR_LINK = Services.prefs.getCharPref("devtools.webide.simulatorAddonsURL");
+let ADB_LINK = Services.prefs.getCharPref("devtools.webide.adbAddonURL");
+let SIMULATOR_ADDON_ID = Services.prefs.getCharPref("devtools.webide.simulatorAddonID");
+let ADB_ADDON_ID = Services.prefs.getCharPref("devtools.webide.adbAddonID");
+
+let platform = Services.appShell.hiddenDOMWindow.navigator.platform;
+let OS = "";
+if (platform.indexOf("Win") != -1) {
+  OS = "win32";
+} else if (platform.indexOf("Mac") != -1) {
+  OS = "mac64";
+} else if (platform.indexOf("Linux") != -1) {
+  if (platform.indexOf("x86_64") != -1) {
+    OS = "linux64";
+  } else {
+    OS = "linux";
+  }
+}
+
+Simulator.on("unregister", updateSimulatorAddons);
+Simulator.on("register", updateSimulatorAddons);
+Devices.on("addon-status-updated", updateAdbAddon);
+
+function updateSimulatorAddons(event, version) {
+  GetAvailableAddons().then(addons => {
+    let foundAddon = null;
+    for (let addon of addons.simulators) {
+      if (addon.version == version) {
+        foundAddon = addon;
+        break;
+      }
+    }
+    if (!foundAddon) {
+      console.warn("An unknown simulator (un)registered", version);
+      return;
+    }
+    foundAddon.updateInstallStatus();
+  });
+}
+
+function updateAdbAddon() {
+  GetAvailableAddons().then(addons => {
+    addons.adb.updateInstallStatus();
+  });
+}
+
+let GetAvailableAddons_promise = null;
+let GetAvailableAddons = exports.GetAvailableAddons = function() {
+  if (!GetAvailableAddons_promise) {
+    let deferred = promise.defer();
+    GetAvailableAddons_promise = deferred.promise;
+    let addons = {
+      simulators: [],
+      adb: null
+    }
+    GetAddonsJSON().then(json => {
+      for (let stability in json) {
+        for (let version of json[stability]) {
+          addons.simulators.push(new SimulatorAddon(stability, version));
+        }
+      }
+      addons.adb = new ADBAddon();
+      deferred.resolve(addons);
+    }, e => {
+      GetAvailableAddons_promise = null;
+      deferred.reject(e);
+    });
+  }
+  return GetAvailableAddons_promise;
+}
+
+function Addon() {}
+Addon.prototype = {
+  _status: "unknown",
+  set status(value) {
+    if (this._status != value) {
+      this._status = value;
+      this.emit("update");
+    }
+  },
+  get status() {
+    return this._status;
+  },
+
+  install: function() {
+    if (this.status != "uninstalled") {
+      throw new Error("Not uninstalled");
+    }
+    this.status = "preparing";
+
+    AddonManager.getAddonByID(this.addonID, (addon) => {
+      if (addon && addon.userDisabled) {
+        addon.userDisabled = false;
+      } else {
+        AddonManager.getInstallForURL(this.xpiLink, (install) => {
+          install.addListener(this);
+          install.install();
+        }, "application/x-xpinstall");
+      }
+    });
+
+  },
+
+  uninstall: function() {
+    AddonManager.getAddonByID(this.addonID, (addon) => {
+      addon.uninstall();
+    });
+  },
+
+  installFailureHandler: function(install, message) {
+    this.status = "uninstalled";
+    this.emit("failure", message);
+  },
+
+  onDownloadStarted: function() {
+    this.status = "downloading";
+  },
+
+  onInstallStarted: function() {
+    this.status = "installing";
+  },
+
+  onDownloadProgress: function(install) {
+    if (install.maxProgress == -1) {
+      this.emit("progress", -1);
+    } else {
+      this.emit("progress", install.progress / install.maxProgress);
+    }
+  },
+
+  onInstallEnded: function({addon}) {
+    addon.userDisabled = false;
+  },
+
+  onDownloadCancelled: function(install) {
+    this.installFailureHandler(install, "Download cancelled");
+  },
+  onDownloadFailed: function(install) {
+    this.installFailureHandler(install, "Download failed");
+  },
+  onInstallCancelled: function(install) {
+    this.installFailureHandler(install, "Install cancelled");
+  },
+  onInstallFailed: function(install) {
+    this.installFailureHandler(install, "Install failed");
+  },
+}
+
+function SimulatorAddon(stability, version) {
+  EventEmitter.decorate(this);
+  this.stability = stability;
+  this.version = version;
+  this.xpiLink = SIMULATOR_LINK.replace(/#OS#/g, OS)
+                               .replace(/#VERSION#/g, version)
+                               .replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
+  this.addonID = SIMULATOR_ADDON_ID.replace(/#SLASHED_VERSION#/g, version.replace(/\./g, "_"));
+  this.updateInstallStatus();
+}
+
+SimulatorAddon.prototype = Object.create(Addon.prototype, {
+  updateInstallStatus: {
+    enumerable: true,
+    value: function() {
+      let sim = Simulator.getByVersion(this.version);
+      if (sim) {
+        this.status = "installed";
+      } else {
+        this.status = "uninstalled";
+      }
+    }
+  },
+});
+
+function ADBAddon() {
+  EventEmitter.decorate(this);
+  this.xpiLink = ADB_LINK.replace(/#OS#/g, OS);
+  this.addonID = ADB_ADDON_ID;
+  this.updateInstallStatus();
+}
+
+ADBAddon.prototype = Object.create(Addon.prototype, {
+  updateInstallStatus: {
+    enumerable: true,
+    value: function() {
+      if (Devices.helperAddonInstalled) {
+        this.status = "installed";
+      } else {
+        this.status = "uninstalled";
+      }
+    }
+  },
+});
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/modules/remote-resources.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {Cu, CC} = require("chrome");
+const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+
+const XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
+
+function getJSON(bypassCache, pref) {
+  if (!bypassCache) {
+    try {
+      let str = Services.prefs.getCharPref(pref + "_cache");
+      let json = JSON.parse(str);
+      return promise.resolve(json);
+    } catch(e) {/* no pref or invalid json. Let's continue */}
+  }
+
+
+  let deferred = promise.defer();
+
+  let xhr = new XMLHttpRequest();
+
+  xhr.onload = () => {
+    let json;
+    try {
+      json = JSON.parse(xhr.responseText);
+    } catch(e) {
+      return deferred.reject("Not valid JSON");
+    }
+    Services.prefs.setCharPref(pref + "_cache", xhr.responseText);
+    deferred.resolve(json);
+  }
+
+  xhr.onerror = (e) => {
+    deferred.reject("Network error");
+  }
+
+  xhr.open("get", Services.prefs.getCharPref(pref));
+  xhr.send();
+
+  return deferred.promise;
+}
+
+
+
+exports.GetTemplatesJSON = function(bypassCache) {
+  return getJSON(bypassCache, "devtools.webide.templatesURL");
+}
+
+exports.GetAddonsJSON = function(bypassCache) {
+  return getJSON(bypassCache, "devtools.webide.addonsURL");
+}
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -51,22 +51,21 @@ SimulatorRuntime.prototype = {
       connection.keepConnecting = true;
       connection.connect();
     });
   },
   getID: function() {
     return this.version;
   },
   getName: function() {
-    return this.version;
+    return Simulator.getByVersion(this.version).appinfo.label;
   },
 }
 
 let gLocalRuntime = {
-  supportApps: false, // Temporary static value
   connect: function(connection) {
     if (!DebuggerServer.initialized) {
       DebuggerServer.init();
       DebuggerServer.addBrowserActors();
     }
     connection.port = null;
     connection.host = null; // Force Pipe transport
     connection.connect();
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b56cc03e349e4b871ea95b61ceeb53d6969759ab
GIT binary patch
literal 1293
zc$^FHW@Zs#U|`^2U~Ag!az0|k)eVdc3<1mx3_J`n3`zO<CB-F0i3NID#i1db49q%0
zRly)!TEWf0$np}X3{3P+a?HN%Akg~$mni>ymE<Lp7Ps=}dMQq5QhdAc_*RomX0dO(
z=0@(${ZRk@M3GaB$L-VWmi&EowtDlbX%;sU&#DOAyVKBrxu|Z5{Nj&mXUI+q4b%O&
zbbC+xge2!3yt)sAJD9&nFTbm7swJRc>!`ukb75}0U3>gB%jVo$mbX(P9^Es0GBeC#
z$_~x@Q+K>hxX|+Px%b&`a_)yeI9`aFmQn7x;^;-M^$kxwmu>bl-PhW3tfFPE#Om4V
z5{~*t>sDvYsGhF)PW$SbEzzNViJR0a#O_4pn4C1onklZgHvIM6%#PRTO0mM8Yc3x?
z^K!>LyGxFXKV&)uWr}u9(pUOe>2j-H-qUu=Nx^VkSrwM!SC_7;d6F1-W%DJowu44%
zk5~S;Kk9ukGNX0*<gV1%PrWxDBwpQHsF&Y6LsUtUZ(qO*(M1kZO#XGI9N2tx-W`vR
zwYS;o&0oJ%%~3E6{L9t7<+}Hx8TwYA=kBwsnZd6X?B%%rTJ>MsYZaMu-KCl?ggq7&
z=Xm81^kv7H1#^OSGe5Pn48M~8Y{_ep&swhYm+-p(pDuB7|I-aC{?FZJ+_&;r=;bH(
z=e*AkfX6y-<7Ss8Hp56sCI$vJ76t}xV61266_+ID<meTpq=6$nN}wt@RRAN>Q-e?E
z-8K-|^ICh`sjX8|ZgwOe4xDw3S4zRLd6~wA$6hf<b5w&A!t1uY*gt(*&QUi%q4e}`
zhEMeJww1DGZ}O9@mF-)k!g8r~qSyYIcHQZZwDz2AI%=)>;J24$h1ptN4m(NDg7h3-
zx#HY^-CS&D2eg^gqOA>5%PUOkLqZj8wpK3f0=mRzMbnnW-#7Fr^iJY<=k1{3vspUC
zpMQfR`z-^Hr#DvZNu9JIaL*@}r@vf)5i@Q6vQ{S38FK=E_NzIxi5468Ki;)oUh=ol
z?=>6Oi@Phhh_Q6%pSJ$+IJ;a{?#Ar1YPWi<KV_KBKI$p8eC38sB6-Utw>$0b-f;cQ
zO1sH(#k?k`2nFvIk87FvzI3BXc*NBW<&p;Gy4A&N=gGSiFO^LW?UdT8b+S<XlhLZ@
zUTGSiMMHUS@~gXw70SzWw0oaX%M`bgYqxkWb?R5dcWaiS<QvgFzQ?vL?fbyRyC%#$
z;?Fs+H8JNc863~wu%BR^y3*wKxw{LsZizp-z3k)mejAgLypw{`YX7{Ch8p>uTB&h~
z<>-^5psvVy6C`>IdyHrK9uJ>fpDreIb?YU2zQxO@2R1G4D{Y#$xzKvU^1w^Q@f(xZ
zf1I6GEq|Y{{85|ftcRPYeeme<PO&j|OZL6__LFkW%XQmMUr%Iqo_A%>*Yj>(|119V
z30i%+y=d!w%l*r$U%dEpJ$=HKZ?B*3s`<3%^@Fo2-8bGli2Y#?@MdI^W5!jgNHBl^
zkjt>75yV0*V^|?&3|dk{HW4$$BAfUPNTH-(R!I8AFp!lEq=^X#7X#@K77z~r445He
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b56cc03e349e4b871ea95b61ceeb53d6969759ab
GIT binary patch
literal 1293
zc$^FHW@Zs#U|`^2U~Ag!az0|k)eVdc3<1mx3_J`n3`zO<CB-F0i3NID#i1db49q%0
zRly)!TEWf0$np}X3{3P+a?HN%Akg~$mni>ymE<Lp7Ps=}dMQq5QhdAc_*RomX0dO(
z=0@(${ZRk@M3GaB$L-VWmi&EowtDlbX%;sU&#DOAyVKBrxu|Z5{Nj&mXUI+q4b%O&
zbbC+xge2!3yt)sAJD9&nFTbm7swJRc>!`ukb75}0U3>gB%jVo$mbX(P9^Es0GBeC#
z$_~x@Q+K>hxX|+Px%b&`a_)yeI9`aFmQn7x;^;-M^$kxwmu>bl-PhW3tfFPE#Om4V
z5{~*t>sDvYsGhF)PW$SbEzzNViJR0a#O_4pn4C1onklZgHvIM6%#PRTO0mM8Yc3x?
z^K!>LyGxFXKV&)uWr}u9(pUOe>2j-H-qUu=Nx^VkSrwM!SC_7;d6F1-W%DJowu44%
zk5~S;Kk9ukGNX0*<gV1%PrWxDBwpQHsF&Y6LsUtUZ(qO*(M1kZO#XGI9N2tx-W`vR
zwYS;o&0oJ%%~3E6{L9t7<+}Hx8TwYA=kBwsnZd6X?B%%rTJ>MsYZaMu-KCl?ggq7&
z=Xm81^kv7H1#^OSGe5Pn48M~8Y{_ep&swhYm+-p(pDuB7|I-aC{?FZJ+_&;r=;bH(
z=e*AkfX6y-<7Ss8Hp56sCI$vJ76t}xV61266_+ID<meTpq=6$nN}wt@RRAN>Q-e?E
z-8K-|^ICh`sjX8|ZgwOe4xDw3S4zRLd6~wA$6hf<b5w&A!t1uY*gt(*&QUi%q4e}`
zhEMeJww1DGZ}O9@mF-)k!g8r~qSyYIcHQZZwDz2AI%=)>;J24$h1ptN4m(NDg7h3-
zx#HY^-CS&D2eg^gqOA>5%PUOkLqZj8wpK3f0=mRzMbnnW-#7Fr^iJY<=k1{3vspUC
zpMQfR`z-^Hr#DvZNu9JIaL*@}r@vf)5i@Q6vQ{S38FK=E_NzIxi5468Ki;)oUh=ol
z?=>6Oi@Phhh_Q6%pSJ$+IJ;a{?#Ar1YPWi<KV_KBKI$p8eC38sB6-Utw>$0b-f;cQ
zO1sH(#k?k`2nFvIk87FvzI3BXc*NBW<&p;Gy4A&N=gGSiFO^LW?UdT8b+S<XlhLZ@
zUTGSiMMHUS@~gXw70SzWw0oaX%M`bgYqxkWb?R5dcWaiS<QvgFzQ?vL?fbyRyC%#$
z;?Fs+H8JNc863~wu%BR^y3*wKxw{LsZizp-z3k)mejAgLypw{`YX7{Ch8p>uTB&h~
z<>-^5psvVy6C`>IdyHrK9uJ>fpDreIb?YU2zQxO@2R1G4D{Y#$xzKvU^1w^Q@f(xZ
zf1I6GEq|Y{{85|ftcRPYeeme<PO&j|OZL6__LFkW%XQmMUr%Iqo_A%>*Yj>(|119V
z30i%+y=d!w%l*r$U%dEpJ$=HKZ?B*3s`<3%^@Fo2-8bGli2Y#?@MdI^W5!jgNHBl^
zkjt>75yV0*V^|?&3|dk{HW4$$BAfUPNTH-(R!I8AFp!lEq=^X#7X#@K77z~r445He
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b56cc03e349e4b871ea95b61ceeb53d6969759ab
GIT binary patch
literal 1293
zc$^FHW@Zs#U|`^2U~Ag!az0|k)eVdc3<1mx3_J`n3`zO<CB-F0i3NID#i1db49q%0
zRly)!TEWf0$np}X3{3P+a?HN%Akg~$mni>ymE<Lp7Ps=}dMQq5QhdAc_*RomX0dO(
z=0@(${ZRk@M3GaB$L-VWmi&EowtDlbX%;sU&#DOAyVKBrxu|Z5{Nj&mXUI+q4b%O&
zbbC+xge2!3yt)sAJD9&nFTbm7swJRc>!`ukb75}0U3>gB%jVo$mbX(P9^Es0GBeC#
z$_~x@Q+K>hxX|+Px%b&`a_)yeI9`aFmQn7x;^;-M^$kxwmu>bl-PhW3tfFPE#Om4V
z5{~*t>sDvYsGhF)PW$SbEzzNViJR0a#O_4pn4C1onklZgHvIM6%#PRTO0mM8Yc3x?
z^K!>LyGxFXKV&)uWr}u9(pUOe>2j-H-qUu=Nx^VkSrwM!SC_7;d6F1-W%DJowu44%
zk5~S;Kk9ukGNX0*<gV1%PrWxDBwpQHsF&Y6LsUtUZ(qO*(M1kZO#XGI9N2tx-W`vR
zwYS;o&0oJ%%~3E6{L9t7<+}Hx8TwYA=kBwsnZd6X?B%%rTJ>MsYZaMu-KCl?ggq7&
z=Xm81^kv7H1#^OSGe5Pn48M~8Y{_ep&swhYm+-p(pDuB7|I-aC{?FZJ+_&;r=;bH(
z=e*AkfX6y-<7Ss8Hp56sCI$vJ76t}xV61266_+ID<meTpq=6$nN}wt@RRAN>Q-e?E
z-8K-|^ICh`sjX8|ZgwOe4xDw3S4zRLd6~wA$6hf<b5w&A!t1uY*gt(*&QUi%q4e}`
zhEMeJww1DGZ}O9@mF-)k!g8r~qSyYIcHQZZwDz2AI%=)>;J24$h1ptN4m(NDg7h3-
zx#HY^-CS&D2eg^gqOA>5%PUOkLqZj8wpK3f0=mRzMbnnW-#7Fr^iJY<=k1{3vspUC
zpMQfR`z-^Hr#DvZNu9JIaL*@}r@vf)5i@Q6vQ{S38FK=E_NzIxi5468Ki;)oUh=ol
z?=>6Oi@Phhh_Q6%pSJ$+IJ;a{?#Ar1YPWi<KV_KBKI$p8eC38sB6-Utw>$0b-f;cQ
zO1sH(#k?k`2nFvIk87FvzI3BXc*NBW<&p;Gy4A&N=gGSiFO^LW?UdT8b+S<XlhLZ@
zUTGSiMMHUS@~gXw70SzWw0oaX%M`bgYqxkWb?R5dcWaiS<QvgFzQ?vL?fbyRyC%#$
z;?Fs+H8JNc863~wu%BR^y3*wKxw{LsZizp-z3k)mejAgLypw{`YX7{Ch8p>uTB&h~
z<>-^5psvVy6C`>IdyHrK9uJ>fpDreIb?YU2zQxO@2R1G4D{Y#$xzKvU^1w^Q@f(xZ
zf1I6GEq|Y{{85|ftcRPYeeme<PO&j|OZL6__LFkW%XQmMUr%Iqo_A%>*Yj>(|119V
z30i%+y=d!w%l*r$U%dEpJ$=HKZ?B*3s`<3%^@Fo2-8bGli2Y#?@MdI^W5!jgNHBl^
zkjt>75yV0*V^|?&3|dk{HW4$$BAfUPNTH-(R!I8AFp!lEq=^X#7X#@K77z~r445He
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b56cc03e349e4b871ea95b61ceeb53d6969759ab
GIT binary patch
literal 1293
zc$^FHW@Zs#U|`^2U~Ag!az0|k)eVdc3<1mx3_J`n3`zO<CB-F0i3NID#i1db49q%0
zRly)!TEWf0$np}X3{3P+a?HN%Akg~$mni>ymE<Lp7Ps=}dMQq5QhdAc_*RomX0dO(
z=0@(${ZRk@M3GaB$L-VWmi&EowtDlbX%;sU&#DOAyVKBrxu|Z5{Nj&mXUI+q4b%O&
zbbC+xge2!3yt)sAJD9&nFTbm7swJRc>!`ukb75}0U3>gB%jVo$mbX(P9^Es0GBeC#
z$_~x@Q+K>hxX|+Px%b&`a_)yeI9`aFmQn7x;^;-M^$kxwmu>bl-PhW3tfFPE#Om4V
z5{~*t>sDvYsGhF)PW$SbEzzNViJR0a#O_4pn4C1onklZgHvIM6%#PRTO0mM8Yc3x?
z^K!>LyGxFXKV&)uWr}u9(pUOe>2j-H-qUu=Nx^VkSrwM!SC_7;d6F1-W%DJowu44%
zk5~S;Kk9ukGNX0*<gV1%PrWxDBwpQHsF&Y6LsUtUZ(qO*(M1kZO#XGI9N2tx-W`vR
zwYS;o&0oJ%%~3E6{L9t7<+}Hx8TwYA=kBwsnZd6X?B%%rTJ>MsYZaMu-KCl?ggq7&
z=Xm81^kv7H1#^OSGe5Pn48M~8Y{_ep&swhYm+-p(pDuB7|I-aC{?FZJ+_&;r=;bH(
z=e*AkfX6y-<7Ss8Hp56sCI$vJ76t}xV61266_+ID<meTpq=6$nN}wt@RRAN>Q-e?E
z-8K-|^ICh`sjX8|ZgwOe4xDw3S4zRLd6~wA$6hf<b5w&A!t1uY*gt(*&QUi%q4e}`
zhEMeJww1DGZ}O9@mF-)k!g8r~qSyYIcHQZZwDz2AI%=)>;J24$h1ptN4m(NDg7h3-
zx#HY^-CS&D2eg^gqOA>5%PUOkLqZj8wpK3f0=mRzMbnnW-#7Fr^iJY<=k1{3vspUC
zpMQfR`z-^Hr#DvZNu9JIaL*@}r@vf)5i@Q6vQ{S38FK=E_NzIxi5468Ki;)oUh=ol
z?=>6Oi@Phhh_Q6%pSJ$+IJ;a{?#Ar1YPWi<KV_KBKI$p8eC38sB6-Utw>$0b-f;cQ
zO1sH(#k?k`2nFvIk87FvzI3BXc*NBW<&p;Gy4A&N=gGSiFO^LW?UdT8b+S<XlhLZ@
zUTGSiMMHUS@~gXw70SzWw0oaX%M`bgYqxkWb?R5dcWaiS<QvgFzQ?vL?fbyRyC%#$
z;?Fs+H8JNc863~wu%BR^y3*wKxw{LsZizp-z3k)mejAgLypw{`YX7{Ch8p>uTB&h~
z<>-^5psvVy6C`>IdyHrK9uJ>fpDreIb?YU2zQxO@2R1G4D{Y#$xzKvU^1w^Q@f(xZ
zf1I6GEq|Y{{85|ftcRPYeeme<PO&j|OZL6__LFkW%XQmMUr%Iqo_A%>*Yj>(|119V
z30i%+y=d!w%l*r$U%dEpJ$=HKZ?B*3s`<3%^@Fo2-8bGli2Y#?@MdI^W5!jgNHBl^
zkjt>75yV0*V^|?&3|dk{HW4$$BAfUPNTH-(R!I8AFp!lEq=^X#7X#@K77z~r445He
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..81e1abc6e1d339d1f84b102b9806821dd36a4fe1
GIT binary patch
literal 1233
zc$^FHW@Zs#U|`^2NNl;}BHupyS2ZI8!!0HT1|9|(hNS%blH!u0!~(sn;?NLI24++L
zg~1?PTEWf0$nuhzfdNePPV~>eZNPK%{XY>Gzf6IM*nYh#t$+lFcGeSm)~AeQH&1YN
zUNR}qao_J3TV7vdkS)7-U;cjelsjDCOw5|Nw93sD+AA&eKURKP8GQfHsn9&u`)oot
zicT^<oM+v_eopzu%qibEn>h_$G%T3m&iL;9Z2dozlfG=w%v}<2kV{T8K`7?%zP8!F
zcD0?it>CcvTe144&7p(bDq$}?OoF3DtSu(+u}wH*Wb1nQ$2ry}{{w5QHfbtWv^th%
z&5@4ZlYP!Qeb%`<F}CKxtbU6M{0q|O_Dr6tI(610*Y~v!JOBDl`R)?fRra&0`p%_@
zMd9a`OKO`+&M4h^LgRV*CZ63r(-vzB9xdGw{!HWD+I4l43JXi4U90~+R{QMHboPtU
z|M}0dFGgnY9-rJ9TI=c9DLi567ehVk^>w_Gdkc0Mz0g0>uqJhvi&lfZqVc;o;s*NS
z{8KhQPyey2)&J|)oEOVGgU+8d{3VuL>^bL|!}~vNi)7_5+4Fk;y70+q-4g!lKdb>L
z5y)$L@}m$i@^n}j7`TBEn3-2xl9-dDSCo>5FaA=4WAkSlh}1q0zjJYWCY#eDQy0#=
zTXZfh30pla>(V?W=IKYIZ=Bd?;<^0q_niVB8S^%rw|!nWC;M4>`=*BUD{dM)3*6%#
zFr5lXb;=XZ^R=A4t9+wz{c#DKw=IoZSCw{sa82dVUh2JJ#>&a}X9!JTx}_-a>Qck0
z*{7SU1YHk4+~~(O(Td@~gSaIcymbxf{7(8G9z^rW1cl00Uwv%*W+&rH4x>bm?j>qx
z67)0DIS*~=EDF4#vp<AabNT|IM~CWEnAC3wh8o}3zx`|8rRe)<Uvq8mGapMjKJ$OX
zx&wXsXAjwK{Qi6Ic6swBfg-MT0>2VZluR=&o^piWXO{H#t7kUIoY3QLbu{?6u}1XX
zj*V|i_qj~Y3Y=IfS+OW<)3Yo~>&`SMsYk8{z4ToB+N35<O|%q_oFhNwW{=syV;!6H
zEHwYVTy*PBe&pUqTh3M(<vHEm+JAZDA&Z=(vm5zUye58~6>j)aO8SuGtH`ZuDt<lp
zvAjEX+J|2OVNS{G_<B#*2Cm!gF@N=`eN29}_RiT>Yv%vaUv@b`=E-&)UgmRZ^6Hk|
zlNfGhoH=fD=tY^$+&|J57gl;J*3VOuca6JHptnJBy1bPQ&z|qP-+M~u{t92aO)aj!
z>|txzqz9|dJYek9xBS?sJ88N^d0lOM^IfAgyLHWPO1qsuuV?k(gIfmMp*OjkYlW^h
zJbU?vPfzf9)}@{K|EJve$sgd&$Rx*%t7MR100AJEVM!y1g<4LqLed#p8bdb`HK`(-
aco0aTBwSnuva*3RF#+LXAT7ZH;sF3L{sbTZ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..81e1abc6e1d339d1f84b102b9806821dd36a4fe1
GIT binary patch
literal 1233
zc$^FHW@Zs#U|`^2NNl;}BHupyS2ZI8!!0HT1|9|(hNS%blH!u0!~(sn;?NLI24++L
zg~1?PTEWf0$nuhzfdNePPV~>eZNPK%{XY>Gzf6IM*nYh#t$+lFcGeSm)~AeQH&1YN
zUNR}qao_J3TV7vdkS)7-U;cjelsjDCOw5|Nw93sD+AA&eKURKP8GQfHsn9&u`)oot
zicT^<oM+v_eopzu%qibEn>h_$G%T3m&iL;9Z2dozlfG=w%v}<2kV{T8K`7?%zP8!F
zcD0?it>CcvTe144&7p(bDq$}?OoF3DtSu(+u}wH*Wb1nQ$2ry}{{w5QHfbtWv^th%
z&5@4ZlYP!Qeb%`<F}CKxtbU6M{0q|O_Dr6tI(610*Y~v!JOBDl`R)?fRra&0`p%_@
zMd9a`OKO`+&M4h^LgRV*CZ63r(-vzB9xdGw{!HWD+I4l43JXi4U90~+R{QMHboPtU
z|M}0dFGgnY9-rJ9TI=c9DLi567ehVk^>w_Gdkc0Mz0g0>uqJhvi&lfZqVc;o;s*NS
z{8KhQPyey2)&J|)oEOVGgU+8d{3VuL>^bL|!}~vNi)7_5+4Fk;y70+q-4g!lKdb>L
z5y)$L@}m$i@^n}j7`TBEn3-2xl9-dDSCo>5FaA=4WAkSlh}1q0zjJYWCY#eDQy0#=
zTXZfh30pla>(V?W=IKYIZ=Bd?;<^0q_niVB8S^%rw|!nWC;M4>`=*BUD{dM)3*6%#
zFr5lXb;=XZ^R=A4t9+wz{c#DKw=IoZSCw{sa82dVUh2JJ#>&a}X9!JTx}_-a>Qck0
z*{7SU1YHk4+~~(O(Td@~gSaIcymbxf{7(8G9z^rW1cl00Uwv%*W+&rH4x>bm?j>qx
z67)0DIS*~=EDF4#vp<AabNT|IM~CWEnAC3wh8o}3zx`|8rRe)<Uvq8mGapMjKJ$OX
zx&wXsXAjwK{Qi6Ic6swBfg-MT0>2VZluR=&o^piWXO{H#t7kUIoY3QLbu{?6u}1XX
zj*V|i_qj~Y3Y=IfS+OW<)3Yo~>&`SMsYk8{z4ToB+N35<O|%q_oFhNwW{=syV;!6H
zEHwYVTy*PBe&pUqTh3M(<vHEm+JAZDA&Z=(vm5zUye58~6>j)aO8SuGtH`ZuDt<lp
zvAjEX+J|2OVNS{G_<B#*2Cm!gF@N=`eN29}_RiT>Yv%vaUv@b`=E-&)UgmRZ^6Hk|
zlNfGhoH=fD=tY^$+&|J57gl;J*3VOuca6JHptnJBy1bPQ&z|qP-+M~u{t92aO)aj!
z>|txzqz9|dJYek9xBS?sJ88N^d0lOM^IfAgyLHWPO1qsuuV?k(gIfmMp*OjkYlW^h
zJbU?vPfzf9)}@{K|EJve$sgd&$Rx*%t7MR100AJEVM!y1g<4LqLed#p8bdb`HK`(-
aco0aTBwSnuva*3RF#+LXAT7ZH;sF3L{sbTZ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..81e1abc6e1d339d1f84b102b9806821dd36a4fe1
GIT binary patch
literal 1233
zc$^FHW@Zs#U|`^2NNl;}BHupyS2ZI8!!0HT1|9|(hNS%blH!u0!~(sn;?NLI24++L
zg~1?PTEWf0$nuhzfdNePPV~>eZNPK%{XY>Gzf6IM*nYh#t$+lFcGeSm)~AeQH&1YN
zUNR}qao_J3TV7vdkS)7-U;cjelsjDCOw5|Nw93sD+AA&eKURKP8GQfHsn9&u`)oot
zicT^<oM+v_eopzu%qibEn>h_$G%T3m&iL;9Z2dozlfG=w%v}<2kV{T8K`7?%zP8!F
zcD0?it>CcvTe144&7p(bDq$}?OoF3DtSu(+u}wH*Wb1nQ$2ry}{{w5QHfbtWv^th%
z&5@4ZlYP!Qeb%`<F}CKxtbU6M{0q|O_Dr6tI(610*Y~v!JOBDl`R)?fRra&0`p%_@
zMd9a`OKO`+&M4h^LgRV*CZ63r(-vzB9xdGw{!HWD+I4l43JXi4U90~+R{QMHboPtU
z|M}0dFGgnY9-rJ9TI=c9DLi567ehVk^>w_Gdkc0Mz0g0>uqJhvi&lfZqVc;o;s*NS
z{8KhQPyey2)&J|)oEOVGgU+8d{3VuL>^bL|!}~vNi)7_5+4Fk;y70+q-4g!lKdb>L
z5y)$L@}m$i@^n}j7`TBEn3-2xl9-dDSCo>5FaA=4WAkSlh}1q0zjJYWCY#eDQy0#=
zTXZfh30pla>(V?W=IKYIZ=Bd?;<^0q_niVB8S^%rw|!nWC;M4>`=*BUD{dM)3*6%#
zFr5lXb;=XZ^R=A4t9+wz{c#DKw=IoZSCw{sa82dVUh2JJ#>&a}X9!JTx}_-a>Qck0
z*{7SU1YHk4+~~(O(Td@~gSaIcymbxf{7(8G9z^rW1cl00Uwv%*W+&rH4x>bm?j>qx
z67)0DIS*~=EDF4#vp<AabNT|IM~CWEnAC3wh8o}3zx`|8rRe)<Uvq8mGapMjKJ$OX
zx&wXsXAjwK{Qi6Ic6swBfg-MT0>2VZluR=&o^piWXO{H#t7kUIoY3QLbu{?6u}1XX
zj*V|i_qj~Y3Y=IfS+OW<)3Yo~>&`SMsYk8{z4ToB+N35<O|%q_oFhNwW{=syV;!6H
zEHwYVTy*PBe&pUqTh3M(<vHEm+JAZDA&Z=(vm5zUye58~6>j)aO8SuGtH`ZuDt<lp
zvAjEX+J|2OVNS{G_<B#*2Cm!gF@N=`eN29}_RiT>Yv%vaUv@b`=E-&)UgmRZ^6Hk|
zlNfGhoH=fD=tY^$+&|J57gl;J*3VOuca6JHptnJBy1bPQ&z|qP-+M~u{t92aO)aj!
z>|txzqz9|dJYek9xBS?sJ88N^d0lOM^IfAgyLHWPO1qsuuV?k(gIfmMp*OjkYlW^h
zJbU?vPfzf9)}@{K|EJve$sgd&$Rx*%t7MR100AJEVM!y1g<4LqLed#p8bdb`HK`(-
aco0aTBwSnuva*3RF#+LXAT7ZH;sF3L{sbTZ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..81e1abc6e1d339d1f84b102b9806821dd36a4fe1
GIT binary patch
literal 1233
zc$^FHW@Zs#U|`^2NNl;}BHupyS2ZI8!!0HT1|9|(hNS%blH!u0!~(sn;?NLI24++L
zg~1?PTEWf0$nuhzfdNePPV~>eZNPK%{XY>Gzf6IM*nYh#t$+lFcGeSm)~AeQH&1YN
zUNR}qao_J3TV7vdkS)7-U;cjelsjDCOw5|Nw93sD+AA&eKURKP8GQfHsn9&u`)oot
zicT^<oM+v_eopzu%qibEn>h_$G%T3m&iL;9Z2dozlfG=w%v}<2kV{T8K`7?%zP8!F
zcD0?it>CcvTe144&7p(bDq$}?OoF3DtSu(+u}wH*Wb1nQ$2ry}{{w5QHfbtWv^th%
z&5@4ZlYP!Qeb%`<F}CKxtbU6M{0q|O_Dr6tI(610*Y~v!JOBDl`R)?fRra&0`p%_@
zMd9a`OKO`+&M4h^LgRV*CZ63r(-vzB9xdGw{!HWD+I4l43JXi4U90~+R{QMHboPtU
z|M}0dFGgnY9-rJ9TI=c9DLi567ehVk^>w_Gdkc0Mz0g0>uqJhvi&lfZqVc;o;s*NS
z{8KhQPyey2)&J|)oEOVGgU+8d{3VuL>^bL|!}~vNi)7_5+4Fk;y70+q-4g!lKdb>L
z5y)$L@}m$i@^n}j7`TBEn3-2xl9-dDSCo>5FaA=4WAkSlh}1q0zjJYWCY#eDQy0#=
zTXZfh30pla>(V?W=IKYIZ=Bd?;<^0q_niVB8S^%rw|!nWC;M4>`=*BUD{dM)3*6%#
zFr5lXb;=XZ^R=A4t9+wz{c#DKw=IoZSCw{sa82dVUh2JJ#>&a}X9!JTx}_-a>Qck0
z*{7SU1YHk4+~~(O(Td@~gSaIcymbxf{7(8G9z^rW1cl00Uwv%*W+&rH4x>bm?j>qx
z67)0DIS*~=EDF4#vp<AabNT|IM~CWEnAC3wh8o}3zx`|8rRe)<Uvq8mGapMjKJ$OX
zx&wXsXAjwK{Qi6Ic6swBfg-MT0>2VZluR=&o^piWXO{H#t7kUIoY3QLbu{?6u}1XX
zj*V|i_qj~Y3Y=IfS+OW<)3Yo~>&`SMsYk8{z4ToB+N35<O|%q_oFhNwW{=syV;!6H
zEHwYVTy*PBe&pUqTh3M(<vHEm+JAZDA&Z=(vm5zUye58~6>j)aO8SuGtH`ZuDt<lp
zvAjEX+J|2OVNS{G_<B#*2Cm!gF@N=`eN29}_RiT>Yv%vaUv@b`=E-&)UgmRZ^6Hk|
zlNfGhoH=fD=tY^$+&|J57gl;J*3VOuca6JHptnJBy1bPQ&z|qP-+M~u{t92aO)aj!
z>|txzqz9|dJYek9xBS?sJ88N^d0lOM^IfAgyLHWPO1qsuuV?k(gIfmMp*OjkYlW^h
zJbU?vPfzf9)}@{K|EJve$sgd&$Rx*%t7MR100AJEVM!y1g<4LqLed#p8bdb`HK`(-
aco0aTBwSnuva*3RF#+LXAT7ZH;sF3L{sbTZ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1ce3f959e1a18af8e9df037328f9a8d7d3183fac
GIT binary patch
literal 1232
zc$^FHW@Zs#U|`^2NNl;}a-;juyedWphFeSw3_J`n3`zO<CB-F0i3NID#i1db49uqf
z3xh$pw1S&~k>w>b0|S`oo#>x^+d<&y`(Gl>{(XkGCM<UHYjjXlW&3{VahB;OwKZi&
z&aIhy?Z@u&CrdJuJzl2%|MzUK`R+W{YAI<))~UOtCmgn!GyR9r&nqkQ6Q62rZ^~!p
z%-Q**q2RpiA?D|TZ+yId_Z(!AC~*ilvzX!g^RwaqJPoUEOfd^uk-*w7V!*jBG5)Y`
zZS3LcayBgU>h@gEk$d=nRY<!;W!6gFE?F55c4m*Xne!H9);woAsQw_#_U06UJ%<)p
znV#!if6w&!Tw~w#ymj+tuVhjW+@Zc>v){3krzUy%o?P_ZZo!>@YNy;6X}IpL*=?Jb
zsdG{L`Q@Ievv|(zib<LB**LN-*6sAgsU453?r0Z_e2)vaKfPneuC<G7{})cKR5_Sd
zHTS>&=d~|%wzNGyd1TeTB@2#lo(TFSF@0|M{<fYti&)7o;z<tIjAE5U56qt+S^kZC
z#`JFXQ#U?y+vGl9Kb!ZjN#LuUmTvcxa`ZYc%e-j5AF$=I^RMNJp7W#PPE7rh*}tD5
z044tT|8GAi1dKZ!76t}xVEkp~6_+ID<meTpq~VLc)Zp0s*#;uD&%^Ir+@8tiw8+$j
z^X?X%OH0C5Ps_SAPl<W@5$PKz_L+Dt|NDKXfJer>4d-p2*Uia(R^Gm;A^nP*#?Auw
zxCcz9LQ<Xb#PfVDXYVTCXk33>!scyD<JMKBT_0RiIkcC0Z<w)i^8Fb?6PRu(3cR}1
zaBBAH<|;wggAX_QaZR*hIPf5Di3V?7Lpr~c{)Y$Ad@@0yvej1~+rHV!xRS#t(W85b
z+L;9XjC9UJTRMvZZ|Lj~;nkeJK<Lq-Iu$1M8-k(6_w{f8ns+Jse%jYu+xyJNl8(>(
zAF=L0pZ?iHwj00y-n(7i{7ImQYn{Na#1kdcjEkon;rE#(z5VK$4KgS6xLX|!K5ncL
zy|-iI+tPh5ld}RRmP%GE%G&fS%hI|t%}MH!>p?F)*S<EXiBl6Tg(K(4Pr2DLJ0aO6
zQhd&oe<gvr@3yarE4-CvBWb?y-L2y<BNAoIHa@$-F0}MS)!EfEN_u%8@_bzr6=w6d
zc-fqHe%=+e8rlmD!`qKN-KTLqddd0F)Og0_`{pk+mA&TwPyBMG0pF+SX>E+@llv#n
zarJD-F?p6Omsnyw&+i|v%!?~aC)oQ>?pM;!un@m7;WWRj9NWF$)2ff{^833wELwQI
z`|b};+MXXm(>^pDnLg*o0kM<bJ=Xj8tv{G2dF}4B*>89kKTj8zEvQg7VNU$E?WSF4
zmc!?gI`-)u#ilRsY_Iov^M^gan~_P58CSs|!2kk4F2j;W5DT@KV1=YJv^0ipB5G1a
bHt_(ELP@x|3}j^kX<`Dx#Xwq|1;hgYu3idX
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1ce3f959e1a18af8e9df037328f9a8d7d3183fac
GIT binary patch
literal 1232
zc$^FHW@Zs#U|`^2NNl;}a-;juyedWphFeSw3_J`n3`zO<CB-F0i3NID#i1db49uqf
z3xh$pw1S&~k>w>b0|S`oo#>x^+d<&y`(Gl>{(XkGCM<UHYjjXlW&3{VahB;OwKZi&
z&aIhy?Z@u&CrdJuJzl2%|MzUK`R+W{YAI<))~UOtCmgn!GyR9r&nqkQ6Q62rZ^~!p
z%-Q**q2RpiA?D|TZ+yId_Z(!AC~*ilvzX!g^RwaqJPoUEOfd^uk-*w7V!*jBG5)Y`
zZS3LcayBgU>h@gEk$d=nRY<!;W!6gFE?F55c4m*Xne!H9);woAsQw_#_U06UJ%<)p
znV#!if6w&!Tw~w#ymj+tuVhjW+@Zc>v){3krzUy%o?P_ZZo!>@YNy;6X}IpL*=?Jb
zsdG{L`Q@Ievv|(zib<LB**LN-*6sAgsU453?r0Z_e2)vaKfPneuC<G7{})cKR5_Sd
zHTS>&=d~|%wzNGyd1TeTB@2#lo(TFSF@0|M{<fYti&)7o;z<tIjAE5U56qt+S^kZC
z#`JFXQ#U?y+vGl9Kb!ZjN#LuUmTvcxa`ZYc%e-j5AF$=I^RMNJp7W#PPE7rh*}tD5
z044tT|8GAi1dKZ!76t}xVEkp~6_+ID<meTpq~VLc)Zp0s*#;uD&%^Ir+@8tiw8+$j
z^X?X%OH0C5Ps_SAPl<W@5$PKz_L+Dt|NDKXfJer>4d-p2*Uia(R^Gm;A^nP*#?Auw
zxCcz9LQ<Xb#PfVDXYVTCXk33>!scyD<JMKBT_0RiIkcC0Z<w)i^8Fb?6PRu(3cR}1
zaBBAH<|;wggAX_QaZR*hIPf5Di3V?7Lpr~c{)Y$Ad@@0yvej1~+rHV!xRS#t(W85b
z+L;9XjC9UJTRMvZZ|Lj~;nkeJK<Lq-Iu$1M8-k(6_w{f8ns+Jse%jYu+xyJNl8(>(
zAF=L0pZ?iHwj00y-n(7i{7ImQYn{Na#1kdcjEkon;rE#(z5VK$4KgS6xLX|!K5ncL
zy|-iI+tPh5ld}RRmP%GE%G&fS%hI|t%}MH!>p?F)*S<EXiBl6Tg(K(4Pr2DLJ0aO6
zQhd&oe<gvr@3yarE4-CvBWb?y-L2y<BNAoIHa@$-F0}MS)!EfEN_u%8@_bzr6=w6d
zc-fqHe%=+e8rlmD!`qKN-KTLqddd0F)Og0_`{pk+mA&TwPyBMG0pF+SX>E+@llv#n
zarJD-F?p6Omsnyw&+i|v%!?~aC)oQ>?pM;!un@m7;WWRj9NWF$)2ff{^833wELwQI
z`|b};+MXXm(>^pDnLg*o0kM<bJ=Xj8tv{G2dF}4B*>89kKTj8zEvQg7VNU$E?WSF4
zmc!?gI`-)u#ilRsY_Iov^M^gan~_P58CSs|!2kk4F2j;W5DT@KV1=YJv^0ipB5G1a
bHt_(ELP@x|3}j^kX<`Dx#Xwq|1;hgYu3idX
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1ce3f959e1a18af8e9df037328f9a8d7d3183fac
GIT binary patch
literal 1232
zc$^FHW@Zs#U|`^2NNl;}a-;juyedWphFeSw3_J`n3`zO<CB-F0i3NID#i1db49uqf
z3xh$pw1S&~k>w>b0|S`oo#>x^+d<&y`(Gl>{(XkGCM<UHYjjXlW&3{VahB;OwKZi&
z&aIhy?Z@u&CrdJuJzl2%|MzUK`R+W{YAI<))~UOtCmgn!GyR9r&nqkQ6Q62rZ^~!p
z%-Q**q2RpiA?D|TZ+yId_Z(!AC~*ilvzX!g^RwaqJPoUEOfd^uk-*w7V!*jBG5)Y`
zZS3LcayBgU>h@gEk$d=nRY<!;W!6gFE?F55c4m*Xne!H9);woAsQw_#_U06UJ%<)p
znV#!if6w&!Tw~w#ymj+tuVhjW+@Zc>v){3krzUy%o?P_ZZo!>@YNy;6X}IpL*=?Jb
zsdG{L`Q@Ievv|(zib<LB**LN-*6sAgsU453?r0Z_e2)vaKfPneuC<G7{})cKR5_Sd
zHTS>&=d~|%wzNGyd1TeTB@2#lo(TFSF@0|M{<fYti&)7o;z<tIjAE5U56qt+S^kZC
z#`JFXQ#U?y+vGl9Kb!ZjN#LuUmTvcxa`ZYc%e-j5AF$=I^RMNJp7W#PPE7rh*}tD5
z044tT|8GAi1dKZ!76t}xVEkp~6_+ID<meTpq~VLc)Zp0s*#;uD&%^Ir+@8tiw8+$j
z^X?X%OH0C5Ps_SAPl<W@5$PKz_L+Dt|NDKXfJer>4d-p2*Uia(R^Gm;A^nP*#?Auw
zxCcz9LQ<Xb#PfVDXYVTCXk33>!scyD<JMKBT_0RiIkcC0Z<w)i^8Fb?6PRu(3cR}1
zaBBAH<|;wggAX_QaZR*hIPf5Di3V?7Lpr~c{)Y$Ad@@0yvej1~+rHV!xRS#t(W85b
z+L;9XjC9UJTRMvZZ|Lj~;nkeJK<Lq-Iu$1M8-k(6_w{f8ns+Jse%jYu+xyJNl8(>(
zAF=L0pZ?iHwj00y-n(7i{7ImQYn{Na#1kdcjEkon;rE#(z5VK$4KgS6xLX|!K5ncL
zy|-iI+tPh5ld}RRmP%GE%G&fS%hI|t%}MH!>p?F)*S<EXiBl6Tg(K(4Pr2DLJ0aO6
zQhd&oe<gvr@3yarE4-CvBWb?y-L2y<BNAoIHa@$-F0}MS)!EfEN_u%8@_bzr6=w6d
zc-fqHe%=+e8rlmD!`qKN-KTLqddd0F)Og0_`{pk+mA&TwPyBMG0pF+SX>E+@llv#n
zarJD-F?p6Omsnyw&+i|v%!?~aC)oQ>?pM;!un@m7;WWRj9NWF$)2ff{^833wELwQI
z`|b};+MXXm(>^pDnLg*o0kM<bJ=Xj8tv{G2dF}4B*>89kKTj8zEvQg7VNU$E?WSF4
zmc!?gI`-)u#ilRsY_Iov^M^gan~_P58CSs|!2kk4F2j;W5DT@KV1=YJv^0ipB5G1a
bHt_(ELP@x|3}j^kX<`Dx#Xwq|1;hgYu3idX
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1ce3f959e1a18af8e9df037328f9a8d7d3183fac
GIT binary patch
literal 1232
zc$^FHW@Zs#U|`^2NNl;}a-;juyedWphFeSw3_J`n3`zO<CB-F0i3NID#i1db49uqf
z3xh$pw1S&~k>w>b0|S`oo#>x^+d<&y`(Gl>{(XkGCM<UHYjjXlW&3{VahB;OwKZi&
z&aIhy?Z@u&CrdJuJzl2%|MzUK`R+W{YAI<))~UOtCmgn!GyR9r&nqkQ6Q62rZ^~!p
z%-Q**q2RpiA?D|TZ+yId_Z(!AC~*ilvzX!g^RwaqJPoUEOfd^uk-*w7V!*jBG5)Y`
zZS3LcayBgU>h@gEk$d=nRY<!;W!6gFE?F55c4m*Xne!H9);woAsQw_#_U06UJ%<)p
znV#!if6w&!Tw~w#ymj+tuVhjW+@Zc>v){3krzUy%o?P_ZZo!>@YNy;6X}IpL*=?Jb
zsdG{L`Q@Ievv|(zib<LB**LN-*6sAgsU453?r0Z_e2)vaKfPneuC<G7{})cKR5_Sd
zHTS>&=d~|%wzNGyd1TeTB@2#lo(TFSF@0|M{<fYti&)7o;z<tIjAE5U56qt+S^kZC
z#`JFXQ#U?y+vGl9Kb!ZjN#LuUmTvcxa`ZYc%e-j5AF$=I^RMNJp7W#PPE7rh*}tD5
z044tT|8GAi1dKZ!76t}xVEkp~6_+ID<meTpq~VLc)Zp0s*#;uD&%^Ir+@8tiw8+$j
z^X?X%OH0C5Ps_SAPl<W@5$PKz_L+Dt|NDKXfJer>4d-p2*Uia(R^Gm;A^nP*#?Auw
zxCcz9LQ<Xb#PfVDXYVTCXk33>!scyD<JMKBT_0RiIkcC0Z<w)i^8Fb?6PRu(3cR}1
zaBBAH<|;wggAX_QaZR*hIPf5Di3V?7Lpr~c{)Y$Ad@@0yvej1~+rHV!xRS#t(W85b
z+L;9XjC9UJTRMvZZ|Lj~;nkeJK<Lq-Iu$1M8-k(6_w{f8ns+Jse%jYu+xyJNl8(>(
zAF=L0pZ?iHwj00y-n(7i{7ImQYn{Na#1kdcjEkon;rE#(z5VK$4KgS6xLX|!K5ncL
zy|-iI+tPh5ld}RRmP%GE%G&fS%hI|t%}MH!>p?F)*S<EXiBl6Tg(K(4Pr2DLJ0aO6
zQhd&oe<gvr@3yarE4-CvBWb?y-L2y<BNAoIHa@$-F0}MS)!EfEN_u%8@_bzr6=w6d
zc-fqHe%=+e8rlmD!`qKN-KTLqddd0F)Og0_`{pk+mA&TwPyBMG0pF+SX>E+@llv#n
zarJD-F?p6Omsnyw&+i|v%!?~aC)oQ>?pM;!un@m7;WWRj9NWF$)2ff{^833wELwQI
z`|b};+MXXm(>^pDnLg*o0kM<bJ=Xj8tv{G2dF}4B*>89kKTj8zEvQg7VNU$E?WSF4
zmc!?gI`-)u#ilRsY_Iov^M^gan~_P58CSs|!2kk4F2j;W5DT@KV1=YJv^0ipB5G1a
bHt_(ELP@x|3}j^kX<`Dx#Xwq|1;hgYu3idX
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ec9645da40d54ea47b2e9c7b418b6f777f9dc72e
GIT binary patch
literal 1233
zc$^FHW@Zs#U|`^2NNl;}l3KvEx0;cG;T97E0}q1?LsEWzNpVS0Vu4;(acBr91GB0B
z!e9_Ct>9*0WO>QVzyKzCC;DgKb`UuF{+CFzf1ly43HL;bxLCL}O?OO}3tv6aG;ynP
zS=!qz$L{Y{R`->5_R|0T-TeLDo2A+}F5lqLT5;!YgUqvsdyYImx!kXoTRVKGXpOW-
z!f93Zmg;u`au)pw=|Q*DdDIRpU~Wp1W8Gn&zwd*8*TuujwoG(l)vj35;90=?TPp9>
zYbm?$4-`LqdAv65GY7l=gouU87kzRA-#l=zl5j9O{YAw0v9&md9pm;_CzrK65M{f%
zvM{ga<2uXtb26=pi@)CS5w++%WP5OKsgYmUw2&+>vAy3JpMEnByeI6cdF$!xw}sxx
zopF}))7D%`Nx1e@Y4M!-Cym}1MfI(75xM>_Zsy`0+jsr)Ja+VIuISsJ{j<*~bDCW~
z{ilA`de`)YrZWC&VP92QRs9^MT{^zw&7NPTDPIn~JhfoI0Q074FNIbz{A)S0V{`n0
zJux-`$L7s{_*%^F>h;YF=c{?ynje1=-E&s8a2E64&(d9GH6H&>bY422=H5BU?#)N>
z0F($k=i-|#1dKc#76t}xU<7976_+ID<meTpq~VLd)Zp0s*#;uD&%^Ir+@8tCbWv(i
zOJ0;%X3*+TZ_`ZwiH+Wmcym(XXDzw>?{^G`O6I%`=WU<Y&B;zH|Glau{fe80Wx0FY
z1Ey0xPo46f<vFv?-cjCZTzy=^CbzY5YF0_x2iK<@-b=MN%*ecSe}>=$rdvt^uTC{+
z-8$V|C762h;YLsIiIxlp9>k?+@YXe?^E>H(co5AfV-zaScJ;CCn;ncRIf@cJy0<8v
zNmS3s<~&r=$rX4@XMYH@<n#qWj}FzTFsa`Vx*B|c{jOj0E=k`{`<iQepZS>3@tJ=k
zbPo3EpS@$dseJd`?cGtIH2RY5IsO`?Sn1EO^mtUQcDDC+=CX*`6X&>F9Sy#1tP;Jq
zqw{U)HiyZXfs;!mD^^KudX{Br+qKL|>QU-REj`!9wy23y6HP@cgWgPybbp<Y>vDC<
zoGJfG!g8OLuW>BAlV;R+E1+KXQOOEJ&f5{yGE6IjQuaP8b=q}sT0s+Am~!^Kzm|c0
z<;#`#*iBh_A!0@C>rVkwUh`ghzH8h1hRYYuFEzC_^Z6&f*;9b^sP5@Q4Vyn6FFg3j
z#KEkq;z)8&{^FBA?zb}U-6iztdEuwPH>|qPd3HbfRNLR)_<i5wc^mcx|6g_W+>zH8
z&+WP3B~&xj(njGUt9o5x_Y+aIz47<M6SniLt~;IkZJObS&zt!y_6W}6PW-p+rebH7
z<L8np_U9eNrZ3NIulIWNhdscXkx7mjSIHp300KZR!;(f23$>hJg`_jIG=^>>YEnfu
a@gR^wNw~NSWMu<sVgka&Kw5$Y!~+0TOA5jO
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ec9645da40d54ea47b2e9c7b418b6f777f9dc72e
GIT binary patch
literal 1233
zc$^FHW@Zs#U|`^2NNl;}l3KvEx0;cG;T97E0}q1?LsEWzNpVS0Vu4;(acBr91GB0B
z!e9_Ct>9*0WO>QVzyKzCC;DgKb`UuF{+CFzf1ly43HL;bxLCL}O?OO}3tv6aG;ynP
zS=!qz$L{Y{R`->5_R|0T-TeLDo2A+}F5lqLT5;!YgUqvsdyYImx!kXoTRVKGXpOW-
z!f93Zmg;u`au)pw=|Q*DdDIRpU~Wp1W8Gn&zwd*8*TuujwoG(l)vj35;90=?TPp9>
zYbm?$4-`LqdAv65GY7l=gouU87kzRA-#l=zl5j9O{YAw0v9&md9pm;_CzrK65M{f%
zvM{ga<2uXtb26=pi@)CS5w++%WP5OKsgYmUw2&+>vAy3JpMEnByeI6cdF$!xw}sxx
zopF}))7D%`Nx1e@Y4M!-Cym}1MfI(75xM>_Zsy`0+jsr)Ja+VIuISsJ{j<*~bDCW~
z{ilA`de`)YrZWC&VP92QRs9^MT{^zw&7NPTDPIn~JhfoI0Q074FNIbz{A)S0V{`n0
zJux-`$L7s{_*%^F>h;YF=c{?ynje1=-E&s8a2E64&(d9GH6H&>bY422=H5BU?#)N>
z0F($k=i-|#1dKc#76t}xU<7976_+ID<meTpq~VLd)Zp0s*#;uD&%^Ir+@8tCbWv(i
zOJ0;%X3*+TZ_`ZwiH+Wmcym(XXDzw>?{^G`O6I%`=WU<Y&B;zH|Glau{fe80Wx0FY
z1Ey0xPo46f<vFv?-cjCZTzy=^CbzY5YF0_x2iK<@-b=MN%*ecSe}>=$rdvt^uTC{+
z-8$V|C762h;YLsIiIxlp9>k?+@YXe?^E>H(co5AfV-zaScJ;CCn;ncRIf@cJy0<8v
zNmS3s<~&r=$rX4@XMYH@<n#qWj}FzTFsa`Vx*B|c{jOj0E=k`{`<iQepZS>3@tJ=k
zbPo3EpS@$dseJd`?cGtIH2RY5IsO`?Sn1EO^mtUQcDDC+=CX*`6X&>F9Sy#1tP;Jq
zqw{U)HiyZXfs;!mD^^KudX{Br+qKL|>QU-REj`!9wy23y6HP@cgWgPybbp<Y>vDC<
zoGJfG!g8OLuW>BAlV;R+E1+KXQOOEJ&f5{yGE6IjQuaP8b=q}sT0s+Am~!^Kzm|c0
z<;#`#*iBh_A!0@C>rVkwUh`ghzH8h1hRYYuFEzC_^Z6&f*;9b^sP5@Q4Vyn6FFg3j
z#KEkq;z)8&{^FBA?zb}U-6iztdEuwPH>|qPd3HbfRNLR)_<i5wc^mcx|6g_W+>zH8
z&+WP3B~&xj(njGUt9o5x_Y+aIz47<M6SniLt~;IkZJObS&zt!y_6W}6PW-p+rebH7
z<L8np_U9eNrZ3NIulIWNhdscXkx7mjSIHp300KZR!;(f23$>hJg`_jIG=^>>YEnfu
a@gR^wNw~NSWMu<sVgka&Kw5$Y!~+0TOA5jO
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ec9645da40d54ea47b2e9c7b418b6f777f9dc72e
GIT binary patch
literal 1233
zc$^FHW@Zs#U|`^2NNl;}l3KvEx0;cG;T97E0}q1?LsEWzNpVS0Vu4;(acBr91GB0B
z!e9_Ct>9*0WO>QVzyKzCC;DgKb`UuF{+CFzf1ly43HL;bxLCL}O?OO}3tv6aG;ynP
zS=!qz$L{Y{R`->5_R|0T-TeLDo2A+}F5lqLT5;!YgUqvsdyYImx!kXoTRVKGXpOW-
z!f93Zmg;u`au)pw=|Q*DdDIRpU~Wp1W8Gn&zwd*8*TuujwoG(l)vj35;90=?TPp9>
zYbm?$4-`LqdAv65GY7l=gouU87kzRA-#l=zl5j9O{YAw0v9&md9pm;_CzrK65M{f%
zvM{ga<2uXtb26=pi@)CS5w++%WP5OKsgYmUw2&+>vAy3JpMEnByeI6cdF$!xw}sxx
zopF}))7D%`Nx1e@Y4M!-Cym}1MfI(75xM>_Zsy`0+jsr)Ja+VIuISsJ{j<*~bDCW~
z{ilA`de`)YrZWC&VP92QRs9^MT{^zw&7NPTDPIn~JhfoI0Q074FNIbz{A)S0V{`n0
zJux-`$L7s{_*%^F>h;YF=c{?ynje1=-E&s8a2E64&(d9GH6H&>bY422=H5BU?#)N>
z0F($k=i-|#1dKc#76t}xU<7976_+ID<meTpq~VLd)Zp0s*#;uD&%^Ir+@8tCbWv(i
zOJ0;%X3*+TZ_`ZwiH+Wmcym(XXDzw>?{^G`O6I%`=WU<Y&B;zH|Glau{fe80Wx0FY
z1Ey0xPo46f<vFv?-cjCZTzy=^CbzY5YF0_x2iK<@-b=MN%*ecSe}>=$rdvt^uTC{+
z-8$V|C762h;YLsIiIxlp9>k?+@YXe?^E>H(co5AfV-zaScJ;CCn;ncRIf@cJy0<8v
zNmS3s<~&r=$rX4@XMYH@<n#qWj}FzTFsa`Vx*B|c{jOj0E=k`{`<iQepZS>3@tJ=k
zbPo3EpS@$dseJd`?cGtIH2RY5IsO`?Sn1EO^mtUQcDDC+=CX*`6X&>F9Sy#1tP;Jq
zqw{U)HiyZXfs;!mD^^KudX{Br+qKL|>QU-REj`!9wy23y6HP@cgWgPybbp<Y>vDC<
zoGJfG!g8OLuW>BAlV;R+E1+KXQOOEJ&f5{yGE6IjQuaP8b=q}sT0s+Am~!^Kzm|c0
z<;#`#*iBh_A!0@C>rVkwUh`ghzH8h1hRYYuFEzC_^Z6&f*;9b^sP5@Q4Vyn6FFg3j
z#KEkq;z)8&{^FBA?zb}U-6iztdEuwPH>|qPd3HbfRNLR)_<i5wc^mcx|6g_W+>zH8
z&+WP3B~&xj(njGUt9o5x_Y+aIz47<M6SniLt~;IkZJObS&zt!y_6W}6PW-p+rebH7
z<L8np_U9eNrZ3NIulIWNhdscXkx7mjSIHp300KZR!;(f23$>hJg`_jIG=^>>YEnfu
a@gR^wNw~NSWMu<sVgka&Kw5$Y!~+0TOA5jO
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ec9645da40d54ea47b2e9c7b418b6f777f9dc72e
GIT binary patch
literal 1233
zc$^FHW@Zs#U|`^2NNl;}l3KvEx0;cG;T97E0}q1?LsEWzNpVS0Vu4;(acBr91GB0B
z!e9_Ct>9*0WO>QVzyKzCC;DgKb`UuF{+CFzf1ly43HL;bxLCL}O?OO}3tv6aG;ynP
zS=!qz$L{Y{R`->5_R|0T-TeLDo2A+}F5lqLT5;!YgUqvsdyYImx!kXoTRVKGXpOW-
z!f93Zmg;u`au)pw=|Q*DdDIRpU~Wp1W8Gn&zwd*8*TuujwoG(l)vj35;90=?TPp9>
zYbm?$4-`LqdAv65GY7l=gouU87kzRA-#l=zl5j9O{YAw0v9&md9pm;_CzrK65M{f%
zvM{ga<2uXtb26=pi@)CS5w++%WP5OKsgYmUw2&+>vAy3JpMEnByeI6cdF$!xw}sxx
zopF}))7D%`Nx1e@Y4M!-Cym}1MfI(75xM>_Zsy`0+jsr)Ja+VIuISsJ{j<*~bDCW~
z{ilA`de`)YrZWC&VP92QRs9^MT{^zw&7NPTDPIn~JhfoI0Q074FNIbz{A)S0V{`n0
zJux-`$L7s{_*%^F>h;YF=c{?ynje1=-E&s8a2E64&(d9GH6H&>bY422=H5BU?#)N>
z0F($k=i-|#1dKc#76t}xU<7976_+ID<meTpq~VLd)Zp0s*#;uD&%^Ir+@8tCbWv(i
zOJ0;%X3*+TZ_`ZwiH+Wmcym(XXDzw>?{^G`O6I%`=WU<Y&B;zH|Glau{fe80Wx0FY
z1Ey0xPo46f<vFv?-cjCZTzy=^CbzY5YF0_x2iK<@-b=MN%*ecSe}>=$rdvt^uTC{+
z-8$V|C762h;YLsIiIxlp9>k?+@YXe?^E>H(co5AfV-zaScJ;CCn;ncRIf@cJy0<8v
zNmS3s<~&r=$rX4@XMYH@<n#qWj}FzTFsa`Vx*B|c{jOj0E=k`{`<iQepZS>3@tJ=k
zbPo3EpS@$dseJd`?cGtIH2RY5IsO`?Sn1EO^mtUQcDDC+=CX*`6X&>F9Sy#1tP;Jq
zqw{U)HiyZXfs;!mD^^KudX{Br+qKL|>QU-REj`!9wy23y6HP@cgWgPybbp<Y>vDC<
zoGJfG!g8OLuW>BAlV;R+E1+KXQOOEJ&f5{yGE6IjQuaP8b=q}sT0s+Am~!^Kzm|c0
z<;#`#*iBh_A!0@C>rVkwUh`ghzH8h1hRYYuFEzC_^Z6&f*;9b^sP5@Q4Vyn6FFg3j
z#KEkq;z)8&{^FBA?zb}U-6iztdEuwPH>|qPd3HbfRNLR)_<i5wc^mcx|6g_W+>zH8
z&+WP3B~&xj(njGUt9o5x_Y+aIz47<M6SniLt~;IkZJObS&zt!y_6W}6PW-p+rebH7
z<L8np_U9eNrZ3NIulIWNhdscXkx7mjSIHp300KZR!;(f23$>hJg`_jIG=^>>YEnfu
a@gR^wNw~NSWMu<sVgka&Kw5$Y!~+0TOA5jO
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/test/addons/simulators.json
@@ -0,0 +1,4 @@
+{
+    "stable": ["1.0", "2.0"],
+     "unstable": ["3.0"]
+}
--- a/browser/devtools/webide/test/chrome.ini
+++ b/browser/devtools/webide/test/chrome.ini
@@ -1,15 +1,34 @@
 [DEFAULT]
 support-files =
   app/index.html
   app/manifest.webapp
   app.zip
+  addons/simulators.json
+  addons/fxos_1_0_simulator-linux.xpi
+  addons/fxos_1_0_simulator-linux64.xpi
+  addons/fxos_1_0_simulator-win32.xpi
+  addons/fxos_1_0_simulator-mac64.xpi
+  addons/fxos_2_0_simulator-linux.xpi
+  addons/fxos_2_0_simulator-linux64.xpi
+  addons/fxos_2_0_simulator-win32.xpi
+  addons/fxos_2_0_simulator-mac64.xpi
+  addons/fxos_3_0_simulator-linux.xpi
+  addons/fxos_3_0_simulator-linux64.xpi
+  addons/fxos_3_0_simulator-win32.xpi
+  addons/fxos_3_0_simulator-mac64.xpi
+  addons/adbhelper-linux.xpi
+  addons/adbhelper-linux64.xpi
+  addons/adbhelper-win32.xpi
+  addons/adbhelper-mac64.xpi
   head.js
   hosted_app.manifest
   templates.json
 
 [test_basic.html]
 [test_newapp.html]
 [test_import.html]
 [test_runtime.html]
 [test_cli.html]
 [test_manifestUpdate.html]
+[test_addons.html]
+[test_deviceinfo.html]
--- a/browser/devtools/webide/test/head.js
+++ b/browser/devtools/webide/test/head.js
@@ -14,24 +14,39 @@ const {devtools} = Cu.import("resource:/
 const {require} = devtools;
 const {AppProjects} = require("devtools/app-manager/app-projects");
 
 const TEST_BASE = "chrome://mochitests/content/chrome/browser/devtools/webide/test/";
 
 Services.prefs.setBoolPref("devtools.webide.enabled", true);
 Services.prefs.setBoolPref("devtools.webide.enableLocalRuntime", true);
 
+Services.prefs.setCharPref("devtools.webide.addonsURL", TEST_BASE + "addons/simulators.json");
+Services.prefs.setCharPref("devtools.webide.simulatorAddonsURL", TEST_BASE + "addons/fxos_#SLASHED_VERSION#_simulator-#OS#.xpi");
+Services.prefs.setCharPref("devtools.webide.adbAddonURL", TEST_BASE + "addons/adbhelper-#OS#.xpi");
+Services.prefs.setCharPref("devtools.webide.templatesURL", TEST_BASE + "templates.json");
+
+
 SimpleTest.registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("devtools.webide.templatesURL");
   Services.prefs.clearUserPref("devtools.webide.enabled");
   Services.prefs.clearUserPref("devtools.webide.enableLocalRuntime");
+  Services.prefs.clearUserPref("devtools.webide.addonsURL");
+  Services.prefs.clearUserPref("devtools.webide.simulatorAddonsURL");
+  Services.prefs.clearUserPref("devtools.webide.adbAddonURL");
+  Services.prefs.clearUserPref("devtools.webide.autoInstallADBHelper", false);
 });
 
-function openWebIDE() {
+function openWebIDE(autoInstallADBHelper) {
   info("opening WebIDE");
 
+  if (!autoInstallADBHelper) {
+    Services.prefs.setBoolPref("devtools.webide.autoinstallADBHelper", false);
+  }
+
   let deferred = promise.defer();
 
   let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
   let win = ww.openWindow(null, "chrome://webide/content/", "webide", "chrome,centerscreen,resizable", null);
 
   win.addEventListener("load", function onLoad() {
     win.removeEventListener("load", onLoad);
     info("WebIDE open");
@@ -75,8 +90,23 @@ function removeAllProjects() {
 function nextTick() {
   let deferred = promise.defer();
   SimpleTest.executeSoon(() => {
     deferred.resolve();
   });
 
   return deferred.promise;
 }
+
+function documentIsLoaded(doc) {
+  let deferred = promise.defer();
+  if (doc.readyState == "complete") {
+    deferred.resolve();
+  } else {
+    doc.addEventListener("readystatechange", function onChange() {
+      if (doc.readyState == "complete") {
+        doc.removeEventListener("readystatechange", onChange);
+        deferred.resolve();
+      }
+    });
+  }
+  return deferred.promise;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/test/test_addons.html
@@ -0,0 +1,168 @@
+<!DOCTYPE html>
+
+<html>
+
+  <head>
+    <meta charset="utf8">
+    <title></title>
+
+    <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
+    <script type="application/javascript;version=1.8" src="head.js"></script>
+    <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  </head>
+
+  <body>
+
+    <script type="application/javascript;version=1.8">
+      window.onload = function() {
+        SimpleTest.waitForExplicitFinish();
+
+        const {GetAvailableAddons} = require("devtools/webide/addons");
+        const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
+        const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
+
+        let adbAddonsInstalled = promise.defer();
+        Devices.on("addon-status-updated", function onUpdate1() {
+          Devices.off("addon-status-updated", onUpdate1);
+          adbAddonsInstalled.resolve();
+        });
+
+        function onSimulatorInstalled(version) {
+          let deferred = promise.defer();
+          Simulator.on("register", function onUpdate() {
+            if (Simulator.getByVersion(version)) {
+              Simulator.off("register", onUpdate);
+              nextTick().then(deferred.resolve);
+            }
+          });
+          return deferred.promise;
+        }
+
+        function installSimulatorFromUI(doc, version) {
+          let li = doc.querySelector('[addon="simulator-' + version + '"]');
+          li.querySelector(".install-button").click();
+          return onSimulatorInstalled(version);
+        }
+
+        function uninstallSimulatorFromUI(doc, version) {
+          let deferred = promise.defer();
+          Simulator.on("unregister", function onUpdate() {
+            nextTick().then(() => {
+              let li = doc.querySelector('[status="uninstalled"][addon="simulator-' + version + '"]');
+              if (li) {
+                Simulator.off("unregister", onUpdate);
+                deferred.resolve();
+              } else {
+                deferred.reject("Can't find item");
+              }
+            })
+          });
+          let li = doc.querySelector('[status="installed"][addon="simulator-' + version + '"]');
+          li.querySelector(".uninstall-button").click();
+          return deferred.promise;
+        }
+
+        function uninstallADBFromUI(doc) {
+          let deferred = promise.defer();
+          Devices.on("addon-status-updated", function onUpdate() {
+            nextTick().then(() => {
+              let li = doc.querySelector('[status="uninstalled"][addon="adb"]');
+              if (li) {
+                Devices.off("addon-status-updated", onUpdate);
+                deferred.resolve();
+              } else {
+                deferred.reject("Can't find item");
+              }
+            })
+          });
+          let li = doc.querySelector('[status="installed"][addon="adb"]');
+          li.querySelector(".uninstall-button").click();
+          return deferred.promise;
+        }
+
+        Task.spawn(function* () {
+
+          ok(!Devices.helperAddonInstalled, "Helper not installed");
+
+          let win = yield openWebIDE(true);
+
+          yield adbAddonsInstalled.promise;
+
+          ok(Devices.helperAddonInstalled, "Helper has been auto-installed");
+
+          yield nextTick();
+
+          let addons = yield GetAvailableAddons();
+
+          is(addons.simulators.length, 3, "3 simulator addons to install");
+
+          let sim10 = addons.simulators.filter(a => a.version == "1.0")[0];
+          sim10.install();
+
+          yield onSimulatorInstalled("1.0");
+
+          win.Cmds.showAddons();
+
+          let frame = win.document.querySelector("#deck-panel-addons");
+          let addonDoc = frame.contentWindow.document;
+          let lis;
+
+          lis = addonDoc.querySelectorAll("li");
+          is(lis.length, 4, "4 addons listed");
+
+          lis = addonDoc.querySelectorAll('li[status="installed"]');
+          is(lis.length, 2, "2 addons installed");
+
+          lis = addonDoc.querySelectorAll('li[status="uninstalled"]');
+          is(lis.length, 2, "2 addons uninstalled");
+
+          info("Uninstalling Simulator 2.0");
+
+          yield installSimulatorFromUI(addonDoc, "2.0");
+
+          info("Uninstalling Simulator 3.0");
+
+          yield installSimulatorFromUI(addonDoc, "3.0");
+
+          yield nextTick();
+
+          let panelNode = win.document.querySelector("#runtime-panel");
+          let items;
+
+          items = panelNode.querySelectorAll(".runtime-panel-item-usb");
+          is(items.length, 1, "Found one runtime button");
+
+          items = panelNode.querySelectorAll(".runtime-panel-item-simulator");
+          is(items.length, 3, "Found 3 simulators button");
+
+          yield uninstallSimulatorFromUI(addonDoc, "1.0");
+          yield uninstallSimulatorFromUI(addonDoc, "2.0");
+          yield uninstallSimulatorFromUI(addonDoc, "3.0");
+
+          items = panelNode.querySelectorAll(".runtime-panel-item-simulator");
+          is(items.length, 0, "No simulator listed");
+
+          let w = addonDoc.querySelector(".warning");
+          let display = addonDoc.defaultView.getComputedStyle(w).display
+          is(display, "none", "Warning about missing ADB hidden");
+
+          yield uninstallADBFromUI(addonDoc, "adb");
+
+          items = panelNode.querySelectorAll(".runtime-panel-item-usb");
+          is(items.length, 0, "No usb runtime listed");
+
+          display = addonDoc.defaultView.getComputedStyle(w).display
+          is(display, "block", "Warning about missing ADB present");
+
+          yield closeWebIDE(win);
+
+          SimpleTest.finish();
+
+        });
+      }
+
+
+    </script>
+  </body>
+</html>
--- a/browser/devtools/webide/test/test_basic.html
+++ b/browser/devtools/webide/test/test_basic.html
@@ -24,17 +24,17 @@
             ok(win, "Found a window");
             ok(win.AppManager, "App Manager accessible");
             let appmgr = win.AppManager;
             ok(appmgr.connection, "App Manager connection ready");
             ok(appmgr.runtimeList, "Runtime list ready");
             ok(appmgr.webAppsStore, "WebApps store ready");
 
             // test error reporting
-            let nbox = win.document.querySelector("#body");
+            let nbox = win.document.querySelector("#notificationbox");
             let notification =  nbox.getNotificationWithValue("webide:errornotification");
             ok(!notification, "No notification yet");
             let deferred = promise.defer();
             nextTick().then(() => {
               deferred.reject("BOOM!");
             });
             try {
               yield win.UI.busyUntil(deferred.promise, "xx");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/test/test_deviceinfo.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+
+<html>
+
+  <head>
+    <meta charset="utf8">
+    <title></title>
+
+    <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
+    <script type="application/javascript;version=1.8" src="head.js"></script>
+    <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  </head>
+
+  <body>
+
+    <script type="application/javascript;version=1.8">
+      window.onload = function() {
+        SimpleTest.waitForExplicitFinish();
+
+        Task.spawn(function* () {
+          Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
+          DebuggerServer.init(function () { return true; });
+          DebuggerServer.addBrowserActors();
+
+          let win = yield openWebIDE();
+
+          let permIframe = win.document.querySelector("#deck-panel-permissionstable");
+          let infoIframe = win.document.querySelector("#deck-panel-runtimedetails");
+
+          yield documentIsLoaded(permIframe.contentWindow.document);
+          yield documentIsLoaded(infoIframe.contentWindow.document);
+
+          win.AppManager.update("runtimelist");
+
+          let panelNode = win.document.querySelector("#runtime-panel");
+          let items = panelNode.querySelectorAll(".runtime-panel-item-custom");
+          is(items.length, 2, "Found 2 custom runtimes button");
+
+          let deferred = promise.defer();
+          win.AppManager.on("app-manager-update", function onUpdate(e,w) {
+            if (w == "list-tabs-response") {
+              win.AppManager.off("app-manager-update", onUpdate);
+              deferred.resolve();
+            }
+          });
+
+          items[1].click();
+
+          yield deferred.promise;
+
+          yield nextTick();
+
+          let perm = win.document.querySelector("#cmd_showPermissionsTable");
+          let info = win.document.querySelector("#cmd_showRuntimeDetails");
+
+          ok(!perm.hasAttribute("disabled"), "perm cmd enabled");
+          ok(!info.hasAttribute("disabled"), "info cmd enabled");
+
+          let deck = win.document.querySelector("#deck");
+
+          win.Cmds.showRuntimeDetails();
+          is(deck.selectedPanel, infoIframe, "info iframe selected");
+
+          yield infoIframe.contentWindow.getRawPermissionsTablePromise;
+
+          yield nextTick();
+
+          // device info and permissions content is checked in other tests
+          // We just test one value to make sure we get something
+
+          let doc = infoIframe.contentWindow.document;
+          let trs = doc.querySelectorAll("tr");
+          let found = false;
+
+          for (let tr of trs) {
+            let [name,val] = tr.querySelectorAll("td");
+            if (name.textContent == "appid") {
+              found = true;
+              is(val.textContent, Services.appinfo.ID, "appid has the right value");
+            }
+          }
+          ok(found, "Found appid line");
+
+          win.Cmds.showPermissionsTable();
+          is(deck.selectedPanel, permIframe, "permission iframe selected");
+
+          yield infoIframe.contentWindow.getDescriptionPromise;
+
+          yield nextTick();
+
+          doc = permIframe.contentWindow.document;
+          trs = doc.querySelectorAll(".line");
+          found = false;
+          for (let tr of trs) {
+            let [name,v1,v2,v3] = tr.querySelectorAll("td");
+            if (name.textContent == "geolocation") {
+              found = true;
+              is(v1.className, "permprompt", "geolocation perm is valid");
+              is(v2.className, "permprompt", "geolocation perm is valid");
+              is(v3.className, "permprompt", "geolocation perm is valid");
+            }
+          }
+          ok(found, "Found geolocation line");
+
+          doc.querySelector("#close").click();
+
+          ok(!deck.selectedPanel, "No panel selected");
+
+          DebuggerServer.destroy();
+
+          yield closeWebIDE(win);
+
+          SimpleTest.finish();
+
+
+        }).then(null, e => {
+          ok(false, "Exception: " + e);
+          SimpleTest.finish();
+        });
+      }
+
+
+    </script>
+  </body>
+</html>
--- a/browser/devtools/webide/test/test_newapp.html
+++ b/browser/devtools/webide/test/test_newapp.html
@@ -13,18 +13,16 @@
   </head>
 
   <body>
 
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
-        Services.prefs.setCharPref("devtools.webide.templatesURL", TEST_BASE + "templates.json");
-
         Task.spawn(function* () {
             let win = yield openWebIDE();
             let tmpDir = FileUtils.getDir("TmpD", []);
             yield win.Cmds.newApp({
               index: 0,
               name: "webideTmpApp",
               folder: tmpDir
             });
@@ -32,17 +30,16 @@
             let project = win.AppManager.selectedProject;
             let tmpDir = FileUtils.getDir("TmpD", ["webidetmpapp"]);
             ok(tmpDir.isDirectory(), "Directory created");
             is(project.location, tmpDir.path, "Location is valid (and lowercase)");
             is(project.name, "webideTmpApp", "name field has been updated");
 
             // Clean up
             tmpDir.remove(true);
-            Services.prefs.clearUserPref("devtools.webide.templatesURL");
             yield closeWebIDE(win);
             yield removeAllProjects();
             SimpleTest.finish();
         });
       }
     </script>
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/themes/addons.css
@@ -0,0 +1,121 @@
+/* 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/. */
+
+@import url("chrome://browser/skin/in-content/common.css");
+
+html {
+  font: message-box;
+  font-size: 15px;
+  font-weight: normal;
+  margin: 0;
+  color: #737980;
+  background-image: linear-gradient(#fff, #ededed 100px);
+  height: 100%;
+}
+
+body {
+  padding: 20px;
+}
+
+h1 {
+  font-size: 2.5em;
+  font-weight: lighter;
+  line-height: 1.2;
+  margin: 0;
+  margin-bottom: .5em;
+}
+
+button {
+  line-height: 20px;
+  font-size: 1em;
+  height: 30px;
+  max-height: 30px;
+  min-width: 120px;
+  padding: 3px;
+  color: #737980;
+  border: 1px solid rgba(23,50,77,.4);
+  border-radius: 5px;
+  background-color: #f1f1f1;
+  background-image: linear-gradient(#fff, rgba(255,255,255,.1));
+  box-shadow: 0 1px 1px 0 #fff, inset 0 2px 2px 0 #fff;
+  text-shadow: 0 1px 1px #fefffe;
+  -moz-appearance: none;
+  -moz-border-top-colors: none !important;
+  -moz-border-right-colors: none !important;
+  -moz-border-bottom-colors: none !important;
+  -moz-border-left-colors: none !important;
+}
+
+button:hover {
+  background-image: linear-gradient(#fff, rgba(255,255,255,.6));
+  cursor: pointer;
+}
+
+button:hover:active {
+  background-image: linear-gradient(rgba(255,255,255,.1), rgba(255,255,255,.6));
+}
+
+progress {
+  height: 30px;
+  vertical-align: middle;
+  padding: 0;
+  width: 120px;
+}
+
+li {
+  margin: 20px 0;
+}
+
+.name {
+  display: inline-block;
+  min-width: 280px;
+}
+
+.status {
+  display: inline-block;
+  min-width: 120px;
+}
+
+.warning {
+  color: #F06;
+  margin: 0;
+  font-size: 0.9em;
+}
+
+
+#controls {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+}
+
+#controls > a {
+  color: #4C9ED9;
+  font-size: small;
+  cursor: pointer;
+  border-bottom: 1px dotted;
+}
+
+#close {
+  margin-left: 10px;
+}
+
+li[status="unknown"],
+li > .uninstall-button,
+li > .install-button,
+li > progress {
+  display: none;
+}
+
+li[status="installed"] > .uninstall-button,
+li[status="uninstalled"] > .install-button,
+li[status="preparing"] > progress,
+li[status="downloading"] > progress,
+li[status="installing"] > progress {
+  display: inline;
+}
+
+li:not([status="uninstalled"]) > .warning {
+  display: none;
+}
--- a/browser/devtools/webide/themes/jar.mn
+++ b/browser/devtools/webide/themes/jar.mn
@@ -4,8 +4,10 @@
 
 webide.jar:
 % skin webide classic/1.0 %skin/
 * skin/webide.css         (webide.css)
   skin/icons.png          (icons.png)
   skin/details.css        (details.css)
   skin/newapp.css         (newapp.css)
   skin/throbber.svg       (throbber.svg)
+  skin/addons.css         (addons.css)
+  skin/tabledoc.css       (tabledoc.css)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/themes/tabledoc.css
@@ -0,0 +1,54 @@
+body {
+  background: white;
+}
+
+#controls {
+  position: fixed;
+  top: 10px;
+  right: 10px;
+}
+
+#controls > a {
+  color: #4C9ED9;
+  font-size: small;
+  cursor: pointer;
+  border-bottom: 1px dotted;
+}
+
+#close {
+  margin-left: 10px;
+}
+
+table {
+  font-family: monospace;
+  border-collapse: collapse;
+}
+
+th, td {
+  padding: 5px;
+  border: 1px solid #EEE;
+}
+
+th {
+  min-width: 130px;
+}
+
+.permissionstable td {
+  text-align: center;
+}
+
+th:first-of-type, td:first-of-type {
+  text-align: left;
+}
+
+.permallow {
+  color: rgb(152,207,57);
+}
+
+.permprompt {
+  color: rgb(0,158,237);
+}
+
+.permdeny {
+  color: rgb(204,73,8);
+}
--- a/browser/devtools/webide/themes/webide.css
+++ b/browser/devtools/webide/themes/webide.css
@@ -35,17 +35,17 @@ window:not(.busy) #action-busy {
 
 .panel-button {
   -moz-box-align: center;
 }
 
 .panel-button-anchor {
   list-style-image: url('icons.png');
   -moz-image-region: rect(43px, 563px, 61px, 535px);
-  width: 12;
+  width: 12px;
   height: 7px;
   margin-bottom: -5px;
 }
 
 .panel-button:hover > .panel-button-anchor {
   -moz-image-region: rect(243px, 563px, 261px, 535px);
 }
 
@@ -113,23 +113,29 @@ panel > vbox {
   overflow-x: hidden;
 }
 
 panel > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 12px 0;
   width: 180px;
 }
 
-.panel-item {
+.panel-item,
+.panel-item-help {
   padding: 3px 12px;
   margin: 0;
   -moz-appearance: none;
 }
 
-.panel-item:hover {
+.panel-item-help {
+  font-size: 0.9em;
+}
+
+.panel-item:hover,
+.panel-item-help:hover {
   background: #CBF0FE;
 }
 
 .panel-header {
   /* We can't use borders or vertical padding here because
    * panels don't take these into account when calculated the
    * height of the panel.
    */
@@ -146,17 +152,18 @@ panel > .panel-arrowcontainer > .panel-a
   font-weight: bold;
 }
 
 .panel-item > .toolbarbutton-icon {
   width: 18px;
   height: 18px;
 }
 
-.panel-item > .toolbarbutton-text {
+.panel-item > .toolbarbutton-text,
+.panel-item-help > .toolbarbutton-text {
   text-align: start;
 }
 
 /* project panel */
 
 .project-panel-item-newapp,
 .project-panel-item-openpackaged,
 .project-panel-item-openhosted {
@@ -209,17 +216,17 @@ panel > .panel-arrowcontainer > .panel-a
 }
 
 #runtime-actions > toolbarbutton:last-child {
   border-radius: 0 0 3px 3px;
 }
 
 /* Main view */
 
-#body {
+#deck {
   background-color: rgb(225, 225, 225);
   background-image: url('chrome://browser/skin/devtools/app-manager/rocket.svg'), url('chrome://browser/skin/devtools/app-manager/noise.png');
   background-repeat: no-repeat, repeat;
   background-size: 35%, auto;
   background-position: center center, top left;
 %ifndef XP_MACOSX
   border-top: 1px solid #AAA;
 %endif
--- a/browser/devtools/webide/webide-prefs.js
+++ b/browser/devtools/webide/webide-prefs.js
@@ -1,9 +1,15 @@
 # -*- Mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 pref("devtools.webide.showProjectEditor", true);
-pref("devtools.webide.templatesURL", "http://people.mozilla.org/~prouget/webidetemplates/template.json"); // See bug 1021504
+pref("devtools.webide.templatesURL", "http://code.cdn.mozilla.net/templates/list.json");
+pref("devtools.webide.autoinstallADBHelper", true);
 pref("devtools.webide.lastprojectlocation", "");
 pref("devtools.webide.enableLocalRuntime", false);
+pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
+pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
+pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
+pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
+pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
--- a/browser/installer/windows/nsis/maintenanceservice_installer.nsi
+++ b/browser/installer/windows/nsis/maintenanceservice_installer.nsi
@@ -185,21 +185,21 @@ Section "MaintenanceService"
   ; If a service already exists, the command line parameter will stop the
   ; service and only install itself if it is newer than the already installed
   ; service.  If successful it will remove the old maintenanceservice.exe
   ; and replace it with maintenanceservice_tmp.exe.
   ClearErrors
   ${GetParameters} $0
   ${GetOptions} "$0" "/Upgrade" $0
   ${If} ${Errors}
-    nsExec::Exec '"$INSTDIR\$TempMaintServiceName" install'
+    ExecWait '"$INSTDIR\$TempMaintServiceName" install'
   ${Else}
     ; The upgrade cmdline is the same as install except
     ; It will fail if the service isn't already installed.
-    nsExec::Exec '"$INSTDIR\$TempMaintServiceName" upgrade'
+    ExecWait '"$INSTDIR\$TempMaintServiceName" upgrade'
   ${EndIf}
 
   WriteUninstaller "$INSTDIR\Uninstall.exe"
   WriteRegStr HKLM "${MaintUninstallKey}" "DisplayName" "${MaintFullName}"
   WriteRegStr HKLM "${MaintUninstallKey}" "UninstallString" \
                    '"$INSTDIR\uninstall.exe"'
   WriteRegStr HKLM "${MaintUninstallKey}" "DisplayIcon" \
                    "$INSTDIR\Uninstall.exe,0"
@@ -250,17 +250,17 @@ Function un.RenameDelete
   ${Else} 
     Delete /REBOOTOK "$9.moz-delete"
   ${EndIf}
   ClearErrors
 FunctionEnd
 
 Section "Uninstall"
   ; Delete the service so that no updates will be attempted
-  nsExec::Exec '"$INSTDIR\maintenanceservice.exe" uninstall'
+  ExecWait '"$INSTDIR\maintenanceservice.exe" uninstall'
 
   Push "$INSTDIR\updater.ini"
   Call un.RenameDelete
   Push "$INSTDIR\maintenanceservice.exe"
   Call un.RenameDelete
   Push "$INSTDIR\maintenanceservice_tmp.exe"
   Call un.RenameDelete
   Push "$INSTDIR\maintenanceservice.old"
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1817,29 +1817,49 @@ richlistitem[type~="action"][actiontype=
 .tabs-newtab-button,
 #TabsToolbar > #new-tab-button ,
 #TabsToolbar > #wrapper-new-tab-button > #new-tab-button {
   list-style-image: url("moz-icon://stock/gtk-add?size=menu");
   -moz-image-region: auto;
 }
 
 /* Tabbrowser arrowscrollbox arrows */
+.tabbrowser-arrowscrollbox > .scrollbutton-up > .toolbarbutton-icon,
+.tabbrowser-arrowscrollbox > .scrollbutton-down > .toolbarbutton-icon {
+  -moz-appearance: none;
+}
+
 .tabbrowser-arrowscrollbox > .scrollbutton-up,
 .tabbrowser-arrowscrollbox > .scrollbutton-down {
   -moz-appearance: none;
+  list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left.png");
   margin: 0 0 @tabToolbarNavbarOverlap@;
 }
 
+#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-up,
+#TabsToolbar[brighttext] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-down {
+  list-style-image: url(chrome://browser/skin/tabbrowser/tab-arrow-left-inverted.png);
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up[disabled],
+.tabbrowser-arrowscrollbox > .scrollbutton-down[disabled] {
+  opacity: .4;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl),
+.tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr) {
+  transform: scaleX(-1);
+}
+
 .tabbrowser-arrowscrollbox > .scrollbutton-down {
-  transition: 1s box-shadow ease-out;
-  border-radius: 4px;
+  transition: 1s background-color ease-out;
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-down[notifybgtab] {
-  box-shadow: 0 0 5px 5px Highlight inset;
+  background-color: Highlight;
   transition: none;
 }
 
 #TabsToolbar .toolbarbutton-1 {
   margin-bottom: @tabToolbarNavbarOverlap@;
 }
 
 #TabsToolbar .toolbarbutton-1 > .toolbarbutton-icon,
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -158,16 +158,18 @@ browser.jar:
   skin/classic/browser/social/share-button.png        (social/share-button.png)
   skin/classic/browser/social/share-button-active.png (social/share-button-active.png)
   skin/classic/browser/social/chat-icons.png          (social/chat-icons.png)
   skin/classic/browser/social/gear_default.png        (../shared/social/gear_default.png)
   skin/classic/browser/social/gear_clicked.png        (../shared/social/gear_clicked.png)
   skin/classic/browser/tabbrowser/connecting.png      (tabbrowser/connecting.png)
   skin/classic/browser/tabbrowser/loading.png         (tabbrowser/loading.png)
   skin/classic/browser/tabbrowser/tab-active-middle.png     (tabbrowser/tab-active-middle.png)
+  skin/classic/browser/tabbrowser/tab-arrow-left.png        (tabbrowser/tab-arrow-left.png)
+  skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
   skin/classic/browser/tabbrowser/tab-background-end.png    (tabbrowser/tab-background-end.png)
   skin/classic/browser/tabbrowser/tab-background-middle.png (tabbrowser/tab-background-middle.png)
   skin/classic/browser/tabbrowser/tab-background-start.png  (tabbrowser/tab-background-start.png)
   skin/classic/browser/tabbrowser/tab-overflow-indicator.png (../shared/tabbrowser/tab-overflow-indicator.png)
 
 # NOTE: The following two files (tab-selected-end.svg, tab-selected-start.svg) get pre-processed in
 #       Makefile.in with a non-default marker of "%" and the result of that gets packaged.
   skin/classic/browser/tabbrowser/tab-selected-end.svg      (tab-selected-end.svg)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..16cd7a2775ae1f56274035fcde4ed5e8c211a52c
GIT binary patch
literal 250
zc%17D@N?(olHy`uVBq!ia0vp^{6H+o!3HFmxV|j}Qj#UE5hcO-X(i=}MX3yqDfvmM
z3ZA)%>8U}fi7AzZCsS>JispE_IEGZ*O8WEvzdiHLhR#N&15IZVQ&NsBJI2|O+xWvk
z;JtI1rsC|sjW?bjY-YD~xFwk-z|`Q=l&B;1``+H_@1h#t<ueXsE9{qZwdFgjA?0Y&
z!M|zdgtQo`zlJeg%OuykInAoRVDe2l+vU2C#+IE85e&<9?9%_YbDUGTc2k5^;%K02
wp^~E^!>Wy<chlTbe3zv0xOsgtc4%N^kb38u%%>m}4Rj}ir>mdKI;Vst0OD<0yZ`_I
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e0fb348d66f4001a50ca9488b9daddcf37bbc3ac
GIT binary patch
literal 368
zc%17D@N?(olHy`uVBq!ia0vp^{6H+o!3-o#udq!9QjEnx?oJHr&dIz4a+U`8gt*>$
z{+>wm^265`@4u*NXsXrEQfr!{)-q4q*h1gJ)_wl=!0;GTD?8Kv<(3mySWQ}K<Lu_-
z>Ejm?5ioyqP()17{LLZD_l7Rq8kvw1o0J}#oSu-Lm6Vmw*|v;ZNL-+Eg<#)0A!#`=
zIYqIVdn6`qkW<rES#Utd#7f`X#=y$n$ja_^()*tv*OvtO1v5B2yO9RsBze2LFm$lW
zdH^|`1s;*b3=G`DAk4@xYmNj^kiEpy*OmP?2Me2lnY^DX6Hv(9)5S4F;&O7r0wDnb
zho@Pd3kph~2JEQf(ui>u6*+5^_Gm@h>RBC&xK_+w#F-({y?|@Q$w^On88&`r?tb+}
Q0%R(Kr>mdKI;Vst0Co0^sQ>@~
--- a/build/autoconf/compiler-opts.m4
+++ b/build/autoconf/compiler-opts.m4
@@ -192,44 +192,31 @@ AC_DEFUN([MOZ_COMPILER_OPTS],
 
   MOZ_DEBUGGING_OPTS
   MOZ_RTTI
 if test "$CLANG_CXX"; then
     ## We disable return-type-c-linkage because jsval is defined as a C++ type but is
     ## returned by C functions. This is possible because we use knowledge about the ABI
     ## to typedef it to a C type with the same layout when the headers are included
     ## from C.
-    ##
-    ## mismatched-tags is disabled (bug 780474) mostly because it's useless.
-    ## Worse, it's not supported by gcc, so it will cause tryserver bustage
-    ## without any easy way for non-Clang users to check for it.
-    _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wno-unknown-warning-option -Wno-return-type-c-linkage -Wno-mismatched-tags"
+    _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wno-unknown-warning-option -Wno-return-type-c-linkage"
 fi
 
 AC_MSG_CHECKING([whether the C++ compiler ($CXX $CXXFLAGS $LDFLAGS) actually is a C++ compiler])
 AC_LANG_SAVE
 AC_LANG_CPLUSPLUS
 _SAVE_LIBS=$LIBS
 LIBS=
 AC_TRY_LINK([#include <new>], [int *foo = new int;],,
             AC_MSG_RESULT([no])
             AC_MSG_ERROR([$CXX $CXXFLAGS $LDFLAGS failed to compile and link a simple C++ source.]))
 LIBS=$_SAVE_LIBS
 AC_LANG_RESTORE
 AC_MSG_RESULT([yes])
 
-if test -z "$GNU_CC"; then
-    case "$target" in
-    *-mingw*)
-        ## Warning 4099 (equivalent of mismatched-tags) is disabled (bug 780474)
-        ## for the same reasons as above.
-        _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -wd4099"
-    esac
-fi
-
 if test -n "$DEVELOPER_OPTIONS"; then
     MOZ_FORCE_GOLD=1
 fi
 
 MOZ_ARG_ENABLE_BOOL(gold,
 [  --enable-gold           Enable GNU Gold Linker when it is not already the default],
     MOZ_FORCE_GOLD=1,
     MOZ_FORCE_GOLD=
--- a/build/mobile/b2gautomation.py
+++ b/build/mobile/b2gautomation.py
@@ -189,17 +189,17 @@ class B2GRemoteAutomation(Automation):
     def restartB2G(self):
         # TODO hangs in subprocess.Popen without this delay
         time.sleep(5)
         self._devicemanager._checkCmd(['shell', 'stop', 'b2g'])
         # Wait for a bit to make sure B2G has completely shut down.
         time.sleep(10)
         self._devicemanager._checkCmd(['shell', 'start', 'b2g'])
         if self._is_emulator:
-            self.marionette.emulator.wait_for_port()
+            self.marionette.emulator.wait_for_port(self.marionette.port)
 
     def rebootDevice(self):
         # find device's current status and serial number
         serial, status = self.getDeviceStatus()
 
         # reboot!
         self._devicemanager._runCmd(['shell', '/system/bin/reboot'])
 
@@ -257,17 +257,17 @@ class B2GRemoteAutomation(Automation):
         # Set up port forwarding again for Marionette, since any that
         # existed previously got wiped out by the reboot.
         if not self._is_emulator:
             self._devicemanager._checkCmd(['forward',
                                            'tcp:%s' % self.marionette.port,
                                            'tcp:%s' % self.marionette.port])
 
         if self._is_emulator:
-            self.marionette.emulator.wait_for_port()
+            self.marionette.emulator.wait_for_port(self.marionette.port)
         else:
             time.sleep(5)
 
         # start a marionette session
         session = self.marionette.start_session()
         if 'b2g' not in session:
             raise Exception("bad session value %s returned by start_session" % session)
 
--- a/build/valgrind/mach_commands.py
+++ b/build/valgrind/mach_commands.py
@@ -126,17 +126,17 @@ class MachCommands(MachCommandBase):
                 valgrind_args.append('--suppressions=' + supps_file2)
 
             exitcode = None
             try:
                 runner = FirefoxRunner(profile=profile,
                                        binary=self.get_binary_path(),
                                        cmdargs=firefox_args,
                                        env=env,
-                                       kp_kwargs=kp_kwargs)
+                                       process_args=kp_kwargs)
                 runner.start(debug_args=valgrind_args)
                 exitcode = runner.wait()
 
             finally:
                 errs = outputHandler.error_count
                 supps = outputHandler.suppression_count
                 if errs != supps:
                     status = 1  # turns the TBPL job orange
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -2035,21 +2035,18 @@ public:
   };
   /**
    * Parses the value of the autocomplete attribute into aResult, ensuring it's
    * composed of valid tokens, otherwise the value "" is used.
    * Note that this method is used for form fields, not on a <form> itself.
    *
    * @return whether aAttr was valid and can be cached.
    */
-  static AutocompleteAttrState
-  SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
-                                 nsAString& aResult,
-                                 AutocompleteAttrState aCachedState =
-                                   eAutocompleteAttrState_Unknown);
+  static AutocompleteAttrState SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
+                                                          nsAString& aResult);
 
   /**
    * This will parse aSource, to extract the value of the pseudo attribute
    * with the name specified in aName. See
    * http://www.w3.org/TR/xml-stylesheet/#NT-StyleSheetPI for the specification
    * which is used to parse aSource.
    *
    * @param aSource the string to parse
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -850,53 +850,35 @@ nsContentUtils::IsAutocompleteEnabled(ns
     form->GetAutocomplete(autocomplete);
   }
 
   return !autocomplete.EqualsLiteral("off");
 }
 
 nsContentUtils::AutocompleteAttrState
 nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
-                                               nsAString& aResult,
-                                               AutocompleteAttrState aCachedState)
-{
-  if (!aAttr ||
-      aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
-    return aCachedState;
-  }
-
-  if (aCachedState == nsContentUtils::eAutocompleteAttrState_Valid) {
-    uint32_t atomCount = aAttr->GetAtomCount();
-    for (uint32_t i = 0; i < atomCount; i++) {
-      if (i != 0) {
-        aResult.Append(' ');
-      }
-      aResult.Append(nsDependentAtomString(aAttr->AtomAt(i)));
-    }
-    nsContentUtils::ASCIIToLower(aResult);
-    return aCachedState;
-  }
-
+                                           nsAString& aResult)
+{
   AutocompleteAttrState state = InternalSerializeAutocompleteAttribute(aAttr, aResult);
   if (state == eAutocompleteAttrState_Valid) {
     ASCIIToLower(aResult);
   } else {
     aResult.Truncate();
   }
   return state;
 }
 
 /**
  * Helper to validate the @autocomplete tokens.
  *
  * @return {AutocompleteAttrState} The state of the attribute (invalid/valid).
  */
 nsContentUtils::AutocompleteAttrState
 nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
-                                                       nsAString& aResult)
+                                                   nsAString& aResult)
 {
   // No sandbox attribute so we are done
   if (!aAttrVal) {
     return eAutocompleteAttrState_Invalid;
   }
 
   uint32_t numTokens = aAttrVal->GetAtomCount();
   if (!numTokens) {
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -1515,20 +1515,33 @@ NS_IMPL_STRING_ATTR(HTMLInputElement, Pl
 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, Type, type,
                                 kInputDefaultType->tag)
 
 NS_IMETHODIMP
 HTMLInputElement::GetAutocomplete(nsAString& aValue)
 {
   aValue.Truncate(0);
   const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
-
-  mAutocompleteAttrState =
-    nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
-                                                   mAutocompleteAttrState);
+  if (!attributeVal ||
+      mAutocompleteAttrState == nsContentUtils::eAutocompleteAttrState_Invalid) {
+    return NS_OK;
+  }
+  if (mAutocompleteAttrState == nsContentUtils::eAutocompleteAttrState_Valid) {
+    uint32_t atomCount = attributeVal->GetAtomCount();
+    for (uint32_t i = 0; i < atomCount; i++) {
+      if (i != 0) {
+        aValue.Append(' ');
+      }
+      aValue.Append(nsDependentAtomString(attributeVal->AtomAt(i)));
+    }
+    nsContentUtils::ASCIIToLower(aValue);
+    return NS_OK;
+  }
+
+  mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLInputElement::SetAutocomplete(const nsAString& aValue)
 {
   return SetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, nullptr, aValue, true);
 }
--- a/content/html/content/src/HTMLSelectElement.cpp
+++ b/content/html/content/src/HTMLSelectElement.cpp
@@ -99,17 +99,16 @@ SafeOptionListMutation::~SafeOptionListM
 
 // construction, destruction
 
 
 HTMLSelectElement::HTMLSelectElement(already_AddRefed<nsINodeInfo>& aNodeInfo,
                                      FromParser aFromParser)
   : nsGenericHTMLFormElementWithState(aNodeInfo),
     mOptions(new HTMLOptionsCollection(MOZ_THIS_IN_INITIALIZER_LIST())),
-    mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown),
     mIsDoneAddingChildren(!aFromParser),
     mDisabledChanged(false),
     mMutating(false),
     mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
     mSelectionHasChanged(false),
     mDefaultSelectionSet(false),
     mCanShowInvalidUI(true),
     mCanShowValidUI(true),
@@ -173,26 +172,16 @@ HTMLSelectElement::SetCustomValidity(con
 {
   nsIConstraintValidation::SetCustomValidity(aError);
 
   UpdateState(true);
 
   return NS_OK;
 }
 
-void
-HTMLSelectElement::GetAutocomplete(DOMString& aValue)
-{
-  const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
-
-  mAutocompleteAttrState =
-    nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
-                                                   mAutocompleteAttrState);
-}
-
 NS_IMETHODIMP
 HTMLSelectElement::GetForm(nsIDOMHTMLFormElement** aForm)
 {
   return nsGenericHTMLFormElementWithState::GetForm(aForm);
 }
 
 nsresult
 HTMLSelectElement::InsertChildAt(nsIContent* aKid,
@@ -1339,19 +1328,16 @@ nsresult
 HTMLSelectElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::disabled) {
       UpdateBarredFromConstraintValidation();
     } else if (aName == nsGkAtoms::required) {
       UpdateValueMissingValidityState();
-    } else if (aName == nsGkAtoms::autocomplete) {
-      // Clear the cached @autocomplete attribute state
-      mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
     }
 
     UpdateState(aNotify);
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
                                                          aValue, aNotify);
 }
@@ -1429,23 +1415,18 @@ HTMLSelectElement::DoneAddingChildren(bo
 }
 
 bool
 HTMLSelectElement::ParseAttribute(int32_t aNamespaceID,
                                   nsIAtom* aAttribute,
                                   const nsAString& aValue,
                                   nsAttrValue& aResult)
 {
-  if (kNameSpaceID_None == aNamespaceID) {
-    if (aAttribute == nsGkAtoms::size) {
-      return aResult.ParsePositiveIntValue(aValue);
-    } else if (aAttribute == nsGkAtoms::autocomplete) {
-      aResult.ParseAtomArray(aValue);
-      return true;
-    }
+  if (aAttribute == nsGkAtoms::size && kNameSpaceID_None == aNamespaceID) {
+    return aResult.ParsePositiveIntValue(aValue);
   }
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 void
 HTMLSelectElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                          nsRuleData* aData)
--- a/content/html/content/src/HTMLSelectElement.h
+++ b/content/html/content/src/HTMLSelectElement.h
@@ -12,17 +12,16 @@
 
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/HTMLOptionsCollection.h"
 #include "mozilla/ErrorResult.h"
 #include "nsCheapSets.h"
 #include "nsCOMPtr.h"
 #include "nsError.h"
 #include "mozilla/dom/HTMLFormElement.h"
-#include "nsContentUtils.h"
 
 class nsContentList;
 class nsIDOMHTMLOptionElement;
 class nsIHTMLCollection;
 class nsISelectControlFrame;
 class nsPresState;
 
 namespace mozilla {
@@ -155,21 +154,16 @@ public:
   bool Autofocus() const
   {
     return GetBoolAttr(nsGkAtoms::autofocus);
   }
   void SetAutofocus(bool aVal, ErrorResult& aRv)
   {
     SetHTMLBoolAttr(nsGkAtoms::autofocus, aVal, aRv);
   }
-  void GetAutocomplete(DOMString& aValue);
-  void SetAutocomplete(const nsAString& aValue, ErrorResult& aRv)
-  {
-    SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv);
-  }
   bool Disabled() const
   {
     return GetBoolAttr(nsGkAtoms::disabled);
   }
   void SetDisabled(bool aVal, ErrorResult& aRv)
   {
     SetHTMLBoolAttr(nsGkAtoms::disabled, aVal, aRv);
   }
@@ -606,17 +600,16 @@ protected:
       return true;
     }
 
     return mSelectionHasChanged;
   }
 
   /** The options[] array */
   nsRefPtr<HTMLOptionsCollection> mOptions;
-  nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
   /** false if the parser is in the middle of adding children. */
   bool            mIsDoneAddingChildren;
   /** true if our disabled state has changed from the default **/
   bool            mDisabledChanged;
   /** true if child nodes are being added or removed.
    *  Used by SafeOptionListMutation.
    */
   bool            mMutating;
--- a/content/html/content/test/forms/test_input_autocomplete.html
+++ b/content/html/content/test/forms/test_input_autocomplete.html
@@ -8,18 +8,17 @@ Test @autocomplete on <input>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
 </head>
 
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
   <form>
-    <input id="input-field" />
-    <select id="select-field" />
+    <input id="field" />
   </form>
 </div>
 <pre id="test">
 <script>
 "use strict";
 
 var values = [
   // @autocomplete content attribute, expected IDL attribute value
@@ -65,41 +64,38 @@ var values = [
   // Four tokens (invalid)
   ["billing billing mobile tel", ""],
 
   // Five tokens (invalid)
   ["billing billing billing mobile tel", ""],
 ];
 
 var types = [undefined, "hidden", "text", "search"]; // Valid types for all non-multiline hints.
+var field = document.getElementById("field");
 
-function checkAutocompleteValues(field, type) {
+function checkAutocompleteValues(type) {
   for (var test of values) {
     if (typeof(test[0]) === "undefined")
       field.removeAttribute("autocomplete");
     else
       field.setAttribute("autocomplete", test[0]);
     ise(field.autocomplete, test[1], "Checking @autocomplete for @type=" + type + " of: " + test[0]);
     ise(field.autocomplete, test[1], "Checking cached @autocomplete for @type=" + type + " of: " + test[0]);
   }
 }
 
 function start() {
-  var inputField = document.getElementById("input-field");
   for (var type of types) {
     // Switch the input type
     if (typeof(type) === "undefined")
-      inputField.removeAttribute("type");
+      field.removeAttribute("type");
     else
-      inputField.type = type;
-    checkAutocompleteValues(inputField, type || "");
+      field.type = type;
+    checkAutocompleteValues(type || "");
   }
-
-  var selectField = document.getElementById("select-field");
-  checkAutocompleteValues(selectField, "select");
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({"set": [["dom.forms.autocomplete.experimental", true]]}, start);
 
 </script>
 </pre>
--- a/dom/webidl/HTMLSelectElement.webidl
+++ b/dom/webidl/HTMLSelectElement.webidl
@@ -5,18 +5,16 @@
  *
  * The origin of this IDL file is
  * http://www.whatwg.org/html/#the-select-element
  */
 
 interface HTMLSelectElement : HTMLElement {
   [SetterThrows, Pure]
            attribute boolean autofocus;
-  [Pref="dom.forms.autocomplete.experimental", SetterThrows, Pure]
-           attribute DOMString autocomplete;
   [SetterThrows, Pure]
            attribute boolean disabled;
   [Pure]
   readonly attribute HTMLFormElement? form;
   [SetterThrows, Pure]
            attribute boolean multiple;
   [SetterThrows, Pure]
            attribute DOMString name;
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -121,18 +121,16 @@ TiledContentClient::UseTiledLayerBuffer(
   // TiledLayerBufferComposite.
   buffer->ReadLock();
 
   mForwarder->UseTiledLayerBuffer(this, buffer->GetSurfaceDescriptorTiles());
   buffer->ClearPaintedRegion();
 }
 
 SharedFrameMetricsHelper::SharedFrameMetricsHelper()
-  : mLastProgressiveUpdateWasLowPrecision(false)
-  , mProgressiveUpdateWasInDanger(false)
 {
   MOZ_COUNT_CTOR(SharedFrameMetricsHelper);
 }
 
 SharedFrameMetricsHelper::~SharedFrameMetricsHelper()
 {
   MOZ_COUNT_DTOR(SharedFrameMetricsHelper);
 }
@@ -170,27 +168,16 @@ SharedFrameMetricsHelper::UpdateFromComp
                                                 compositorMetrics)) {
     FindFallbackContentFrameMetrics(aLayer, aCompositionBounds, aZoom);
     return false;
   }
 
   aCompositionBounds = ParentLayerRect(compositorMetrics.mCompositionBounds);
   aZoom = compositorMetrics.GetZoomToParent();
 
-  // Reset the checkerboard risk flag when switching to low precision
-  // rendering.
-  if (aLowPrecision && !mLastProgressiveUpdateWasLowPrecision) {
-    // Skip low precision rendering until we're at risk of checkerboarding.
-    if (!mProgressiveUpdateWasInDanger) {
-      return true;
-    }
-    mProgressiveUpdateWasInDanger = false;
-  }
-  mLastProgressiveUpdateWasLowPrecision = aLowPrecision;
-
   // Always abort updates if the resolution has changed. There's no use
   // in drawing at the incorrect resolution.
   if (!FuzzyEquals(compositorMetrics.GetZoom().scale, contentMetrics.GetZoom().scale)) {
     return true;
   }
 
   // Never abort drawing if we can't be sure we've sent a more recent
   // display-port. If we abort updating when we shouldn't, we can end up
@@ -200,23 +187,30 @@ SharedFrameMetricsHelper::UpdateFromComp
       fabsf(contentMetrics.GetScrollOffset().y - compositorMetrics.GetScrollOffset().y) <= 2 &&
       fabsf(contentMetrics.mDisplayPort.x - compositorMetrics.mDisplayPort.x) <= 2 &&
       fabsf(contentMetrics.mDisplayPort.y - compositorMetrics.mDisplayPort.y) <= 2 &&
       fabsf(contentMetrics.mDisplayPort.width - compositorMetrics.mDisplayPort.width) <= 2 &&
       fabsf(contentMetrics.mDisplayPort.height - compositorMetrics.mDisplayPort.height)) {
     return false;
   }
 
-  // When not a low precision pass and the page is in danger of checker boarding
-  // abort update.
-  if (!aLowPrecision && !mProgressiveUpdateWasInDanger) {
-    if (AboutToCheckerboard(contentMetrics, compositorMetrics)) {
-      mProgressiveUpdateWasInDanger = true;
-      return true;
-    }
+  bool scrollUpdatePending = contentMetrics.GetScrollOffsetUpdated() &&
+      contentMetrics.GetScrollGeneration() != compositorMetrics.GetScrollGeneration();
+  // If scrollUpdatePending is true, then that means the content-side
+  // metrics has a new scroll offset that is going to be forced into the
+  // compositor but it hasn't gotten there yet.
+  // Even though right now comparing the metrics might indicate we're
+  // about to checkerboard (and that's true), the checkerboarding will
+  // disappear as soon as the new scroll offset update is processed
+  // on the compositor side. To avoid leaving things in a low-precision
+  // paint, we need to detect and handle this case (bug 1026756).
+  if (!aLowPrecision && !scrollUpdatePending && AboutToCheckerboard(contentMetrics, compositorMetrics)) {
+    TILING_PRLOG_OBJ(("TILING: Checkerboard abort content %s\n", tmpstr.get()), contentMetrics);
+    TILING_PRLOG_OBJ(("TILING: Checkerboard abort compositor %s\n", tmpstr.get()), compositorMetrics);
+    return true;
   }
 
   // Abort drawing stale low-precision content if there's a more recent
   // display-port in the pipeline.
   if (aLowPrecision && !aHasPendingNewThebesContent) {
     return true;
   }
 
--- a/gfx/layers/client/TiledContentClient.h
+++ b/gfx/layers/client/TiledContentClient.h
@@ -331,19 +331,16 @@ public:
    * Determines if the compositor's upcoming composition bounds has fallen
    * outside of the contents display port. If it has then the compositor
    * will start to checker board. Checker boarding is when the compositor
    * tries to composite a tile and it is not available. Historically
    * a tile with a checker board pattern was used. Now a blank tile is used.
    */
   bool AboutToCheckerboard(const FrameMetrics& aContentMetrics,
                            const FrameMetrics& aCompositorMetrics);
-private:
-  bool mLastProgressiveUpdateWasLowPrecision;
-  bool mProgressiveUpdateWasInDanger;
 };
 
 /**
  * Provide an instance of TiledLayerBuffer backed by drawable TextureClients.
  * This buffer provides an implementation of ValidateTile using a
  * thebes callback and can support painting using a single paint buffer.
  * Whether a single paint buffer is used is controlled by
  * gfxPrefs::PerTileDrawing().
--- a/gfx/layers/composite/ThebesLayerComposite.cpp
+++ b/gfx/layers/composite/ThebesLayerComposite.cpp
@@ -128,17 +128,17 @@ ThebesLayerComposite::RenderLayer(const 
     }
   }
 #endif
 
   EffectChain effectChain(this);
   LayerManagerComposite::AutoAddMaskEffect autoMaskEffect(mMaskLayer, effectChain);
   AddBlendModeEffect(effectChain);
 
-  nsIntRegion visibleRegion = GetEffectiveVisibleRegion();
+  const nsIntRegion& visibleRegion = GetEffectiveVisibleRegion();
 
   TiledLayerProperties tiledLayerProps;
   if (mRequiresTiledProperties) {
     tiledLayerProps.mVisibleRegion = visibleRegion;
     tiledLayerProps.mEffectiveResolution = GetEffectiveResolution();
     tiledLayerProps.mValidRegion = mValidRegion;
   }
 
--- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp
+++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp
@@ -750,17 +750,18 @@ NativeRegExpMacroAssembler::CheckNotBack
         // Parameters are
         //   Address byte_offset1 - Address captured substring's start.
         //   Address byte_offset2 - Address of current character position.
         //   size_t byte_length - length of capture in bytes(!)
         masm.setupUnalignedABICall(3, temp0);
         masm.passABIArg(current_character);
         masm.passABIArg(current_position);
         masm.passABIArg(temp1);
-        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, CaseInsensitiveCompareStrings));
+        int (*fun)(const jschar*, const jschar*, size_t) = CaseInsensitiveCompareStrings;
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, fun));
         masm.storeCallResult(temp0);
 
         masm.PopRegsInMask(volatileRegs);
 
         // Check if function returned non-zero for success or zero for failure.
         masm.branchTest32(Assembler::Zero, temp0, temp0, BranchOrBacktrack(on_no_match));
 
         // On success, increment position by length of capture.
@@ -807,19 +808,22 @@ NativeRegExpMacroAssembler::CheckCharact
     masm.branch32(Assembler::Above, temp0, Imm32(to - from), BranchOrBacktrack(on_not_in_range));
 }
 
 void
 NativeRegExpMacroAssembler::CheckBitInTable(uint8_t *table, Label *on_bit_set)
 {
     IonSpew(SPEW_PREFIX "CheckBitInTable");
 
-    JS_ASSERT(mode_ != ASCII); // Ascii case not handled here.
+    masm.movePtr(ImmPtr(table), temp0);
 
-    masm.movePtr(ImmPtr(table), temp0);
+    // kTableMask is currently 127, so we need to mask even if the input is
+    // Latin1. V8 has the same issue.
+    static_assert(JSString::MAX_LATIN1_CHAR > kTableMask,
+                  "No need to mask if MAX_LATIN1_CHAR <= kTableMask");
     masm.move32(Imm32(kTableSize - 1), temp1);
     masm.and32(current_character, temp1);
 
     masm.load8ZeroExtend(BaseIndex(temp0, temp1, TimesOne), temp0);
     masm.branchTest32(Assembler::NonZero, temp0, temp0, BranchOrBacktrack(on_bit_set));
 }
 
 void
@@ -870,17 +874,25 @@ NativeRegExpMacroAssembler::LoadCurrentC
 }
 
 void
 NativeRegExpMacroAssembler::LoadCurrentCharacterUnchecked(int cp_offset, int characters)
 {
     IonSpew(SPEW_PREFIX "LoadCurrentCharacterUnchecked(%d, %d)", cp_offset, characters);
 
     if (mode_ == ASCII) {
-        MOZ_ASSUME_UNREACHABLE("Ascii loading not implemented");
+        BaseIndex address(input_end_pointer, current_position, TimesOne, cp_offset);
+        if (characters == 4) {
+            masm.load32(address, current_character);
+        } else if (characters == 2) {
+            masm.load16ZeroExtend(address, current_character);
+        } else {
+            JS_ASSERT(characters = 1);
+            masm.load8ZeroExtend(address, current_character);
+        }
     } else {
         JS_ASSERT(mode_ == JSCHAR);
         JS_ASSERT(characters <= 2);
         BaseIndex address(input_end_pointer, current_position, TimesOne, cp_offset * sizeof(jschar));
         if (characters == 2)
             masm.load32(address, current_character);
         else
             masm.load16ZeroExtend(address, current_character);
--- a/js/src/irregexp/NativeRegExpMacroAssembler.h
+++ b/js/src/irregexp/NativeRegExpMacroAssembler.h
@@ -35,29 +35,30 @@
 
 #include "irregexp/RegExpMacroAssembler.h"
 
 namespace js {
 namespace irregexp {
 
 struct InputOutputData
 {
-    const jschar *inputStart;
-    const jschar *inputEnd;
+    const void *inputStart;
+    const void *inputEnd;
 
     // Index into inputStart (in chars) at which to begin matching.
     size_t startIndex;
 
     MatchPairs *matches;
 
     // RegExpMacroAssembler::Result for non-global regexps, number of captures
     // for global regexps.
     int32_t result;
 
-    InputOutputData(const jschar *inputStart, const jschar *inputEnd,
+    template <typename CharT>
+    InputOutputData(const CharT *inputStart, const CharT *inputEnd,
                     size_t startIndex, MatchPairs *matches)
       : inputStart(inputStart),
         inputEnd(inputEnd),
         startIndex(startIndex),
         matches(matches),
         result(0)
     {}
 };
--- a/js/src/irregexp/RegExpEngine.cpp
+++ b/js/src/irregexp/RegExpEngine.cpp
@@ -1661,34 +1661,47 @@ irregexp::CompilePattern(JSContext *cx, 
         assembler->set_global_mode((data->tree->min_match() > 0)
                                    ? RegExpMacroAssembler::GLOBAL_NO_ZERO_LENGTH_CHECK
                                    : RegExpMacroAssembler::GLOBAL);
     }
 
     return compiler.Assemble(cx, assembler, node, data->capture_count);
 }
 
+template <typename CharT>
 RegExpRunStatus
-irregexp::ExecuteCode(JSContext *cx, jit::JitCode *codeBlock,
-                      const jschar *chars, size_t start, size_t length, MatchPairs *matches)
+irregexp::ExecuteCode(JSContext *cx, jit::JitCode *codeBlock, const CharT *chars, size_t start,
+                      size_t length, MatchPairs *matches)
 {
 #ifdef JS_ION
     typedef void (*RegExpCodeSignature)(InputOutputData *);
 
     InputOutputData data(chars, chars + length, start, matches);
 
     RegExpCodeSignature function = reinterpret_cast<RegExpCodeSignature>(codeBlock->raw());
-    CALL_GENERATED_REGEXP(function, &data);
+
+    {
+        JS::AutoSuppressGCAnalysis nogc;
+        CALL_GENERATED_REGEXP(function, &data);
+    }
 
     return (RegExpRunStatus) data.result;
 #else
     MOZ_CRASH();
 #endif
 }
 
+template RegExpRunStatus
+irregexp::ExecuteCode(JSContext *cx, jit::JitCode *codeBlock, const Latin1Char *chars, size_t start,
+                      size_t length, MatchPairs *matches);
+
+template RegExpRunStatus
+irregexp::ExecuteCode(JSContext *cx, jit::JitCode *codeBlock, const jschar *chars, size_t start,
+                      size_t length, MatchPairs *matches);
+
 // -------------------------------------------------------------------
 // Tree to graph conversion
 
 RegExpNode *
 RegExpAtom::ToNode(RegExpCompiler* compiler, RegExpNode* on_success)
 {
     TextElementVector *elms =
         compiler->alloc()->newInfallible<TextElementVector>(*compiler->alloc());
--- a/js/src/irregexp/RegExpEngine.h
+++ b/js/src/irregexp/RegExpEngine.h
@@ -103,23 +103,25 @@ struct RegExpCode
 
 RegExpCode
 CompilePattern(JSContext *cx, RegExpShared *shared, RegExpCompileData *data,
                HandleLinearString sample,  bool is_global, bool ignore_case = false,
                bool is_ascii = false);
 
 // Note: this may return RegExpRunStatus_Error if an interrupt was requested
 // while the code was executing.
+template <typename CharT>
 RegExpRunStatus
-ExecuteCode(JSContext *cx, jit::JitCode *codeBlock,
-            const jschar *chars, size_t start, size_t length, MatchPairs *matches);
+ExecuteCode(JSContext *cx, jit::JitCode *codeBlock, const CharT *chars, size_t start,
+            size_t length, MatchPairs *matches);
 
+template <typename CharT>
 RegExpRunStatus
-InterpretCode(JSContext *cx, const uint8_t *byteCode,
-              const jschar *chars, size_t start, size_t length, MatchPairs *matches);
+InterpretCode(JSContext *cx, const uint8_t *byteCode, const CharT *chars, size_t start,
+              size_t length, MatchPairs *matches);
 
 #define FOR_EACH_NODE_TYPE(VISIT)                                    \
   VISIT(End)                                                         \
   VISIT(Action)                                                      \
   VISIT(Choice)                                                      \
   VISIT(BackReference)                                               \
   VISIT(Assertion)                                                   \
   VISIT(Text)
--- a/js/src/irregexp/RegExpInterpreter.cpp
+++ b/js/src/irregexp/RegExpInterpreter.cpp
@@ -99,23 +99,24 @@ static int32_t
 Load16Aligned(const uint8_t* pc)
 {
     JS_ASSERT((reinterpret_cast<uintptr_t>(pc) & 1) == 0);
     return *reinterpret_cast<const uint16_t *>(pc);
 }
 
 #define BYTECODE(name)  case BC_##name:
 
+template <typename CharT>
 RegExpRunStatus
-irregexp::InterpretCode(JSContext *cx, const uint8_t *byteCode,
-                        const jschar *chars, size_t current, size_t length, MatchPairs *matches)
+irregexp::InterpretCode(JSContext *cx, const uint8_t *byteCode, const CharT *chars, size_t current,
+                        size_t length, MatchPairs *matches)
 {
     const uint8_t* pc = byteCode;
 
-    jschar current_char = current ? chars[current - 1] : '\n';
+    uint32_t current_char = current ? chars[current - 1] : '\n';
 
     RegExpStackCursor stack(cx);
 
     int32_t numRegisters = Load32Aligned(pc);
     pc += 4;
 
     Vector<int32_t, 0, SystemAllocPolicy> registers;
     if (!registers.growByUninitialized(numRegisters))
@@ -221,18 +222,18 @@ irregexp::InterpretCode(JSContext *cx, c
             pc += BC_LOAD_CURRENT_CHAR_UNCHECKED_LENGTH;
             break;
           }
           BYTECODE(LOAD_2_CURRENT_CHARS) {
             size_t pos = current + (insn >> BYTECODE_SHIFT);
             if (pos + 2 > length) {
                 pc = byteCode + Load32Aligned(pc + 4);
             } else {
-                jschar next = chars[pos + 1];
-                current_char = (chars[pos] | (next << (kBitsPerByte * sizeof(jschar))));
+                CharT next = chars[pos + 1];
+                current_char = (chars[pos] | (next << (kBitsPerByte * sizeof(CharT))));
                 pc += BC_LOAD_2_CURRENT_CHARS_LENGTH;
             }
             break;
           }
           BYTECODE(LOAD_2_CURRENT_CHARS_UNCHECKED) {
             int pos = current + (insn >> BYTECODE_SHIFT);
             jschar next = chars[pos + 1];
             current_char = (chars[pos] | (next << (kBitsPerByte * sizeof(jschar))));
@@ -416,17 +417,17 @@ irregexp::InterpretCode(JSContext *cx, c
             if (from < 0 || len <= 0) {
                 pc += BC_CHECK_NOT_BACK_REF_NO_CASE_LENGTH;
                 break;
             }
             if (current + len > length) {
                 pc = byteCode + Load32Aligned(pc + 4);
                 break;
             }
-            if (CaseInsensitiveCompareStrings(chars + from, chars + current, len * 2)) {
+            if (CaseInsensitiveCompareStrings(chars + from, chars + current, len * sizeof(CharT))) {
                 current += len;
                 pc += BC_CHECK_NOT_BACK_REF_NO_CASE_LENGTH;
             } else {
                 pc = byteCode + Load32Aligned(pc + 4);
             }
             break;
           }
           BYTECODE(CHECK_AT_START)
@@ -451,8 +452,16 @@ irregexp::InterpretCode(JSContext *cx, c
             break;
           }
           default:
             MOZ_ASSUME_UNREACHABLE("Bad bytecode");
             break;
         }
     }
 }
+
+template RegExpRunStatus
+irregexp::InterpretCode(JSContext *cx, const uint8_t *byteCode, const Latin1Char *chars, size_t current,
+                        size_t length, MatchPairs *matches);
+
+template RegExpRunStatus
+irregexp::InterpretCode(JSContext *cx, const uint8_t *byteCode, const jschar *chars, size_t current,
+                        size_t length, MatchPairs *matches);
--- a/js/src/irregexp/RegExpMacroAssembler.cpp
+++ b/js/src/irregexp/RegExpMacroAssembler.cpp
@@ -30,37 +30,46 @@
 
 #include "irregexp/RegExpMacroAssembler.h"
 
 #include "irregexp/RegExpBytecode.h"
 
 using namespace js;
 using namespace js::irregexp;
 
+template <typename CharT>
 int
-irregexp::CaseInsensitiveCompareStrings(const jschar *substring1, const jschar *substring2,
+irregexp::CaseInsensitiveCompareStrings(const CharT *substring1, const CharT *substring2,
 					size_t byteLength)
 {
-    JS_ASSERT(byteLength % 2 == 0);
-    size_t length = byteLength >> 1;
+    JS_ASSERT(byteLength % sizeof(CharT) == 0);
+    size_t length = byteLength / sizeof(CharT);
 
     for (size_t i = 0; i < length; i++) {
         jschar c1 = substring1[i];
         jschar c2 = substring2[i];
         if (c1 != c2) {
             c1 = unicode::ToLowerCase(c1);
             c2 = unicode::ToLowerCase(c2);
             if (c1 != c2)
                 return 0;
         }
     }
 
     return 1;
 }
 
+template int
+irregexp::CaseInsensitiveCompareStrings(const Latin1Char *substring1, const Latin1Char *substring2,
+					size_t byteLength);
+
+template int
+irregexp::CaseInsensitiveCompareStrings(const jschar *substring1, const jschar *substring2,
+					size_t byteLength);
+
 InterpretedRegExpMacroAssembler::InterpretedRegExpMacroAssembler(LifoAlloc *alloc, RegExpShared *shared,
                                                                  size_t numSavedRegisters)
   : RegExpMacroAssembler(*alloc, shared, numSavedRegisters),
     pc_(0),
     advance_current_start_(0),
     advance_current_offset_(0),
     advance_current_end_(kInvalidPC),
     buffer_(nullptr),
--- a/js/src/irregexp/RegExpMacroAssembler.h
+++ b/js/src/irregexp/RegExpMacroAssembler.h
@@ -212,18 +212,19 @@ class MOZ_STACK_CLASS RegExpMacroAssembl
         if (num_registers_ <= reg)
             num_registers_ = reg + 1;
     }
 
   public:
     RegExpShared *shared;
 };
 
+template <typename CharT>
 int
-CaseInsensitiveCompareStrings(const jschar *substring1, const jschar *substring2, size_t byteLength);
+CaseInsensitiveCompareStrings(const CharT *substring1, const CharT *substring2, size_t byteLength);
 
 class MOZ_STACK_CLASS InterpretedRegExpMacroAssembler : public RegExpMacroAssembler
 {
   public:
     InterpretedRegExpMacroAssembler(LifoAlloc *alloc, RegExpShared *shared, size_t numSavedRegisters);
     ~InterpretedRegExpMacroAssembler();
 
     // Inherited virtual methods.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/Debugger-debuggees-28.js
@@ -0,0 +1,109 @@
+// Test that on->off->on and off->on->off toggles don't crash.
+
+function addRemove(dbg, g) {
+  dbg.addDebuggee(g);
+  var f = dbg.getNewestFrame();
+  while (f)
+    f = f.older;
+  dbg.removeDebuggee(g);
+}
+
+function removeAdd(dbg, g) {
+  dbg.removeDebuggee(g);
+  dbg.addDebuggee(g);
+  var f = dbg.getNewestFrame();
+  while (f)
+    f = f.older;
+}
+
+function newGlobalDebuggerPair(toggleSeq) {
+  var g = newGlobal();
+  var dbg = new Debugger;
+
+  if (toggleSeq == removeAdd)
+    dbg.addDebuggee(g);
+
+  g.eval("" + function f() { return g(); });
+  g.eval("" + function g() { return h(); });
+  g.eval("line0 = Error().lineNumber;");
+  g.eval("" + function h() {
+    for (var i = 0; i < 100; i++)
+      interruptIf(i == 95);
+    debugger;
+    return i;
+  });
+
+  setInterruptCallback(function () { return true; });
+
+  return [g, dbg];
+}
+
+function testInterrupt(toggleSeq) {
+  var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+  setInterruptCallback(function () {
+    toggleSeq(dbg, g);
+    return true;
+  });
+
+  assertEq(g.f(), 100);
+}
+
+function testPrologue(toggleSeq) {
+  var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+  dbg.onEnterFrame = function (f) {
+    if (f.callee && f.callee.name == "h")
+      toggleSeq(dbg, g);
+  };
+
+  assertEq(g.f(), 100);
+}
+
+function testEpilogue(toggleSeq) {
+  var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+  dbg.onEnterFrame = function (f) {
+    if (f.callee && f.callee.name == "h") {
+      f.onPop = function () {
+        toggleSeq(dbg, g);
+      };
+    }
+  };
+
+  assertEq(g.f(), 100);
+}
+
+function testTrap(toggleSeq) {
+  var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+  dbg.onEnterFrame = function (f) {
+    if (f.callee && f.callee.name == "h") {
+      var offs = f.script.getLineOffsets(g.line0 + 2);
+      assertEq(offs.length > 0, true);
+      f.script.setBreakpoint(offs[0], { hit: function () {
+        toggleSeq(dbg, g);
+      }});
+    }
+  };
+
+  assertEq(g.f(), 100);
+}
+
+function testDebugger(toggleSeq) {
+ var [g, dbg] = newGlobalDebuggerPair(toggleSeq);
+
+  dbg.onDebuggerStatement = function () {
+    toggleSeq(dbg, g);
+  };
+
+  assertEq(g.f(), 100);
+}
+
+testInterrupt(addRemove);
+testInterrupt(removeAdd);
+
+testPrologue(removeAdd);
+testEpilogue(removeAdd);
+testTrap(removeAdd);
+testDebugger(removeAdd);
--- a/js/src/jit-test/tests/latin1/regexp.js
+++ b/js/src/jit-test/tests/latin1/regexp.js
@@ -8,8 +8,26 @@ assertEq(re.sticky, false);
 
 // TwoByte
 re = new RegExp("foo[bB]a\\r\u1200", "im");
 assertEq(isLatin1(re.source), false);
 assertEq(re.source, "foo[bB]a\\r\u1200");
 assertEq(re.multiline, true);
 assertEq(re.ignoreCase, true);
 assertEq(re.sticky, false);
+
+re = /b[aA]r/;
+
+// Latin1
+assertEq(toLatin1("foobAr1234").search(re), 3);
+assertEq(toLatin1("bar1234").search(re), 0);
+assertEq(toLatin1("foobbr1234").search(re), -1);
+
+// TwoByte
+assertEq("foobAr1234\u1200".search(re), 3);
+assertEq("bar1234\u1200".search(re), 0);
+assertEq("foobbr1234\u1200".search(re), -1);
+
+re = /abcdefghijklm[0-5]/;
+assertEq(toLatin1("1abcdefghijklm4").search(re), 1);
+assertEq("\u12001abcdefghijklm0".search(re), 2);
+assertEq(toLatin1("1abcdefghijklm8").search(re), -1);
+assertEq("\u12001abcdefghijklm8".search(re), -1);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/latin1/replace.js
@@ -0,0 +1,126 @@
+function testDollarReplacement() {
+    // Latin1 input, pat and replacement
+    var s = toLatin1("Foobarbaz123");
+    var pat = toLatin1("bar");
+    assertEq(s.replace(pat, toLatin1("AA")), "FooAAbaz123");
+    assertEq(s.replace(pat, toLatin1("A$$A")), "FooA$Abaz123");
+    assertEq(s.replace(pat, toLatin1("A$`A")), "FooAFooAbaz123");
+    assertEq(s.replace(pat, toLatin1("A$&A")), "FooAbarAbaz123");
+    assertEq(s.replace(pat, toLatin1("A$'A")), "FooAbaz123Abaz123");
+
+    // Latin1 input and pat, TwoByte replacement
+    assertEq(s.replace(pat, "A\u1200"), "FooA\u1200baz123");
+    assertEq(s.replace(pat, "A$$\u1200"), "FooA$\u1200baz123");
+    assertEq(s.replace(pat, "A$`\u1200"), "FooAFoo\u1200baz123");
+    assertEq(s.replace(pat, "A$&\u1200"), "FooAbar\u1200baz123");
+    assertEq(s.replace(pat, "A$'\u1200"), "FooAbaz123\u1200baz123");
+
+    // TwoByte input, Latin1 pat and replacement
+    s = "Foobarbaz123\u1200";
+    assertEq(s.replace(pat, toLatin1("A")), "FooAbaz123\u1200");
+    assertEq(s.replace(pat, toLatin1("A$$")), "FooA$baz123\u1200");
+    assertEq(s.replace(pat, toLatin1("A$`")), "FooAFoobaz123\u1200");
+    assertEq(s.replace(pat, toLatin1("A$&")), "FooAbarbaz123\u1200");
+    assertEq(s.replace(pat, toLatin1("A$'")), "FooAbaz123\u1200baz123\u1200");
+
+    // TwoByte input and pat, Latin1 replacement
+    s = "Foobar\u1200baz123";
+    pat += "\u1200";
+    assertEq(s.replace(pat, toLatin1("AB")), "FooABbaz123");
+    assertEq(s.replace(pat, toLatin1("A$$B")), "FooA$Bbaz123");
+    assertEq(s.replace(pat, toLatin1("A$`B")), "FooAFooBbaz123");
+    assertEq(s.replace(pat, toLatin1("A$&B")), "FooAbar\u1200Bbaz123");
+    assertEq(s.replace(pat, toLatin1("A$'B")), "FooAbaz123Bbaz123");
+
+    // TwoByte input, pat and replacement
+    assertEq(s.replace(pat, "A\u1300"), "FooA\u1300baz123");
+    assertEq(s.replace(pat, "A$$\u1300"), "FooA$\u1300baz123");
+    assertEq(s.replace(pat, "A$`\u1300"), "FooAFoo\u1300baz123");
+    assertEq(s.replace(pat, "A$&\u1300"), "FooAbar\u1200\u1300baz123");
+    assertEq(s.replace(pat, "A$'\u1300"), "FooAbaz123\u1300baz123");
+}
+testDollarReplacement();
+
+function testRegExp() {
+    var s = toLatin1("Foobar123bar234");
+    assertEq(s.replace(/bar\d\d/, "456"), "Foo4563bar234");
+
+    // Latin1 input and replacement
+    var re1 = /bar\d\d/;
+    var re2 = /bar\d\d/g;
+    assertEq(s.replace(re1, toLatin1("789")), "Foo7893bar234");
+    assertEq(s.replace(re2, toLatin1("789\u00ff")), "Foo789\u00ff3789\u00ff4");
+
+    // Latin1 input, TwoByte replacement
+    assertEq(s.replace(re1, "789\u1200"), "Foo789\u12003bar234");
+    assertEq(s.replace(re2, "789\u1200"), "Foo789\u12003789\u12004");
+
+    // TwoByte input, Latin1 replacement
+    s += "\u1200";
+    assertEq(s.replace(re1, toLatin1("7890")), "Foo78903bar234\u1200");
+    assertEq(s.replace(re2, toLatin1("7890\u00ff")), "Foo7890\u00ff37890\u00ff4\u1200");
+
+    // TwoByte input and replacement
+    assertEq(s.replace(re1, "789\u1200"), "Foo789\u12003bar234\u1200");
+    assertEq(s.replace(re2, "789\u1200"), "Foo789\u12003789\u12004\u1200");
+}
+testRegExp();
+
+function testRegExpDollar() {
+    var s = toLatin1("Foobar123bar2345");
+
+    // Latin1 input and replacement
+    var re1 = /bar\d\d/;
+    var re2 = /bar(\d\d)/g;
+    assertEq(s.replace(re1, toLatin1("--$&--")), "Foo--bar12--3bar2345");
+    assertEq(s.replace(re2, toLatin1("--$'\u00ff--")), "Foo--3bar2345\xFF--3--45\xFF--45");
+    assertEq(s.replace(re2, toLatin1("--$`--")), "Foo--Foo--3--Foobar123--45");
+
+    // Latin1 input, TwoByte replacement
+    assertEq(s.replace(re1, "\u1200$$"), "Foo\u1200$3bar2345");
+    assertEq(s.replace(re2, "\u1200$1"), "Foo\u1200123\u12002345");
+    assertEq(s.replace(re2, "\u1200$'"), "Foo\u12003bar23453\u12004545");
+
+    // TwoByte input, Latin1 replacement
+    s += "\u1200";
+    assertEq(s.replace(re1, toLatin1("**$&**")), "Foo**bar12**3bar2345\u1200");
+    assertEq(s.replace(re2, toLatin1("**$1**")), "Foo**12**3**23**45\u1200");
+    assertEq(s.replace(re2, toLatin1("**$`**")), "Foo**Foo**3**Foobar123**45\u1200");
+    assertEq(s.replace(re2, toLatin1("**$'$$**")), "Foo**3bar2345\u1200$**3**45\u1200$**45\u1200");
+
+    // TwoByte input and replacement
+    assertEq(s.replace(re1, "**$&**\ueeee"), "Foo**bar12**\ueeee3bar2345\u1200");
+    assertEq(s.replace(re2, "**$1**\ueeee"), "Foo**12**\ueeee3**23**\ueeee45\u1200");
+    assertEq(s.replace(re2, "\ueeee**$`**"), "Foo\ueeee**Foo**3\ueeee**Foobar123**45\u1200");
+    assertEq(s.replace(re2, "\ueeee**$'$$**"), "Foo\ueeee**3bar2345\u1200$**3\ueeee**45\u1200$**45\u1200");
+}
+testRegExpDollar();
+
+function testFlattenPattern() {
+    var s = "abcdef[g]abc";
+
+    // Latin1 pattern
+    assertEq(s.replace(toLatin1("def[g]"), "--$&--", "gi"), "abc--def[g]--abc");
+
+    // TwoByte pattern
+    s = "abcdef[g]\u1200abc";
+    assertEq(s.replace("def[g]\u1200", "++$&++", "gi"), "abc++def[g]\u1200++abc");
+}
+testFlattenPattern();
+
+function testReplaceEmpty() {
+    // Latin1
+    var s = toLatin1("--abcdefghijkl--abcdefghijkl--abcdefghijkl--abcdefghijkl");
+    assertEq(s.replace(/abcd[eE]/g, ""), "--fghijkl--fghijkl--fghijkl--fghijkl");
+
+    s = "--abcdEf--";
+    assertEq(s.replace(/abcd[eE]/g, ""), "--f--");
+
+    // TwoByte
+    s = "--abcdefghijkl--abcdefghijkl--abcdefghijkl--abcdefghijkl\u1200";
+    assertEq(s.replace(/abcd[eE]/g, ""), "--fghijkl--fghijkl--fghijkl--fghijkl\u1200");
+
+    s = "--abcdEf--\u1200";
+    assertEq(s.replace(/abcd[eE]/g, ""), "--f--\u1200");
+}
+testReplaceEmpty();
--- a/js/src/jit/BaselineDebugModeOSR.cpp
+++ b/js/src/jit/BaselineDebugModeOSR.cpp
@@ -45,33 +45,31 @@ struct DebugModeOSREntry
         newStub(nullptr),
         recompInfo(nullptr),
         pcOffset(icEntry.pcOffset()),
         frameKind(icEntry.kind())
     {
 #ifdef DEBUG
         MOZ_ASSERT(pcOffset == icEntry.pcOffset());
         MOZ_ASSERT(frameKind == icEntry.kind());
+#endif
+    }
 
-        // Assert that if we have a NonOp ICEntry, that there are no unsynced
-        // slots, since such a recompile could have only been triggered from
-        // either an interrupt check or a debug trap handler.
-        //
-        // If triggered from an interrupt check, the stack should be fully
-        // synced.
-        //
-        // If triggered from a debug trap handler, we must be recompiling for
-        // toggling debug mode on->off, in which case the old baseline script
-        // should have fully synced stack at every bytecode.
-        if (frameKind == ICEntry::Kind_NonOp) {
-            PCMappingSlotInfo slotInfo;
-            jsbytecode *pc = script->offsetToPC(pcOffset);
-            oldBaselineScript->nativeCodeForPC(script, pc, &slotInfo);
-            MOZ_ASSERT(slotInfo.numUnsynced() == 0);
-        }
+    DebugModeOSREntry(JSScript *script, BaselineDebugModeOSRInfo *info)
+      : script(script),
+        oldBaselineScript(script->baselineScript()),
+        oldStub(nullptr),
+        newStub(nullptr),
+        recompInfo(nullptr),
+        pcOffset(script->pcToOffset(info->pc)),
+        frameKind(info->frameKind)
+    {
+#ifdef DEBUG
+        MOZ_ASSERT(pcOffset == script->pcToOffset(info->pc));
+        MOZ_ASSERT(frameKind == info->frameKind);
 #endif
     }
 
     DebugModeOSREntry(DebugModeOSREntry &&other)
       : script(other.script),
         oldBaselineScript(other.oldBaselineScript),
         oldStub(other.oldStub),
         newStub(other.newStub),
@@ -94,17 +92,17 @@ struct DebugModeOSREntry
                 frameKind == ICEntry::Kind_DebugEpilogue);
     }
 
     bool recompiled() const {
         return oldBaselineScript != script->baselineScript();
     }
 
     BaselineDebugModeOSRInfo *takeRecompInfo() {
-        MOZ_ASSERT(recompInfo);
+        MOZ_ASSERT(needsRecompileInfo() && recompInfo);
         BaselineDebugModeOSRInfo *tmp = recompInfo;
         recompInfo = nullptr;
         return tmp;
     }
 
     bool allocateRecompileInfo(JSContext *cx) {
         MOZ_ASSERT(needsRecompileInfo());
 
@@ -170,21 +168,34 @@ CollectOnStackScripts(JSContext *cx, con
                       DebugModeOSREntryVector &entries)
 {
     ICStub *prevFrameStubPtr = nullptr;
     bool needsRecompileHandler = false;
     for (JitFrameIterator iter(activation); !iter.done(); ++iter) {
         switch (iter.type()) {
           case JitFrame_BaselineJS: {
             JSScript *script = iter.script();
-            uint8_t *retAddr = iter.returnAddressToFp();
-            ICEntry &entry = script->baselineScript()->icEntryFromReturnAddress(retAddr);
 
-            if (!entries.append(DebugModeOSREntry(script, entry)))
-                return false;
+            if (BaselineDebugModeOSRInfo *info = iter.baselineFrame()->getDebugModeOSRInfo()) {
+                // If patching a previously patched yet unpopped frame, we can
+                // use the BaselineDebugModeOSRInfo on the frame directly to
+                // patch. Indeed, we cannot use iter.returnAddressToFp(), as
+                // it points into the debug mode OSR handler and cannot be
+                // used to look up a corresponding ICEntry.
+                //
+                // See cases F and G in PatchBaselineFrameForDebugMode.
+                if (!entries.append(DebugModeOSREntry(script, info)))
+                    return false;
+            } else {
+                // Otherwise, use the return address to look up the ICEntry.
+                uint8_t *retAddr = iter.returnAddressToFp();
+                ICEntry &entry = script->baselineScript()->icEntryFromReturnAddress(retAddr);
+                if (!entries.append(DebugModeOSREntry(script, entry)))
+                    return false;
+            }
 
             if (entries.back().needsRecompileInfo()) {
                 if (!entries.back().allocateRecompileInfo(cx))
                     return false;
 
                 needsRecompileHandler |= true;
             }
 
@@ -279,24 +290,31 @@ PatchBaselineFramesForDebugMode(JSContex
     //  B. From a VM call (interrupt handler, debugger statement handler).
     //
     // On to Off:
     //  - All the ways above.
     //  C. From the debug trap handler.
     //  D. From the debug prologue.
     //  E. From the debug epilogue.
     //
+    // Off to On to Off:
+    //  F. Undo case B above on previously patched yet unpopped frames.
+    //
+    // On to Off to On:
+    //  G. Undo cases B, C, D, or E above on previously patched yet unpopped
+    //     frames.
+    //
     // In general, we patch the return address from the VM call to return to a
     // "continuation fixer" to fix up machine state (registers and stack
     // state). Specifics on what need to be done are documented below.
     //
 
     IonCommonFrameLayout *prev = nullptr;
     size_t entryIndex = *start;
-    DebugOnly<bool> expectedDebugMode = cx->compartment()->debugMode();
+    bool expectedDebugMode = cx->compartment()->debugMode();
 
     for (JitFrameIterator iter(activation); !iter.done(); ++iter) {
         DebugModeOSREntry &entry = entries[entryIndex];
 
         switch (iter.type()) {
           case JitFrame_BaselineJS: {
             // If the script wasn't recompiled, there's nothing to patch.
             if (!entry.recompiled()) {
@@ -327,22 +345,43 @@ PatchBaselineFramesForDebugMode(JSContex
                 // directly to the IC resume address.
                 uint8_t *retAddr = bl->returnAddressForIC(bl->icEntryFromPCOffset(pcOffset));
                 SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind, pc);
                 prev->setReturnAddress(retAddr);
                 entryIndex++;
                 break;
             }
 
-            bool popFrameReg;
+            // Cases F and G above.
+            //
+            // We undo a previous recompile by handling cases B, C, D, and E
+            // like normal, except that we retrieved the pc information via
+            // the previous OSR debug info stashed on the frame.
+            if (BaselineDebugModeOSRInfo *info = iter.baselineFrame()->getDebugModeOSRInfo()) {
+                MOZ_ASSERT(info->pc == pc);
+                MOZ_ASSERT(info->frameKind == kind);
+
+                // Case G, might need to undo B, C, D, or E.
+                MOZ_ASSERT_IF(expectedDebugMode, (kind == ICEntry::Kind_CallVM ||
+                                                  kind == ICEntry::Kind_DebugTrap ||
+                                                  kind == ICEntry::Kind_DebugPrologue ||
+                                                  kind == ICEntry::Kind_DebugEpilogue));
+                // Case F, should only need to undo case B.
+                MOZ_ASSERT_IF(!expectedDebugMode, kind == ICEntry::Kind_CallVM);
+
+                // We will have allocated a new recompile info, so delete the
+                // existing one.
+                iter.baselineFrame()->deleteDebugModeOSRInfo();
+            }
 
             // The RecompileInfo must already be allocated so that this
             // function may be infallible.
             BaselineDebugModeOSRInfo *recompInfo = entry.takeRecompInfo();
 
+            bool popFrameReg;
             switch (kind) {
               case ICEntry::Kind_CallVM:
                 // Case B above.
                 //
                 // Patching returns from an interrupt handler or the debugger
                 // statement handler is similar in that we can resume at the
                 // next op.
                 pc += GetBytecodeLength(pc);
--- a/js/src/jit/IonFrames.cpp
+++ b/js/src/jit/IonFrames.cpp
@@ -202,37 +202,42 @@ JitFrameIterator::script() const
     JS_ASSERT(isScripted());
     if (isBaselineJS())
         return baselineFrame()->script();
     JSScript *script = ScriptFromCalleeToken(calleeToken());
     JS_ASSERT(script);
     return script;
 }
 
+uint8_t *
+JitFrameIterator::resumeAddressToFp() const
+{
+    // If we are settled on a patched BaselineFrame due to debug mode OSR, get
+    // the real return address via the stashed DebugModeOSRInfo.
+    if (isBaselineJS() && baselineFrame()->getDebugModeOSRInfo())
+        return baselineFrame()->debugModeOSRInfo()->resumeAddr;
+    return returnAddressToFp();
+}
+
 void
 JitFrameIterator::baselineScriptAndPc(JSScript **scriptRes, jsbytecode **pcRes) const
 {
     JS_ASSERT(isBaselineJS());
     JSScript *script = this->script();
     if (scriptRes)
         *scriptRes = script;
-    uint8_t *retAddr = returnAddressToFp();
+    uint8_t *retAddr = resumeAddressToFp();
 
     // If we have unwound the scope due to exception handling to a different
     // pc, the frame should behave as if it were settled on that pc.
     if (jsbytecode *overridePc = baselineFrame()->getUnwoundScopeOverridePc()) {
         *pcRes = overridePc;
         return;
     }
 
-    // If we are in the middle of a recompile handler, get the real return
-    // address as stashed in the RecompileInfo.
-    if (BaselineDebugModeOSRInfo *info = baselineFrame()->getDebugModeOSRInfo())
-        retAddr = info->resumeAddr;
-
     if (pcRes) {
         // If the return address is into the prologue entry address or just
         // after the debug prologue, then assume start of script.
         if (retAddr == script->baselineScript()->prologueEntryAddr() ||
             retAddr == script->baselineScript()->postDebugPrologueAddr())
         {
             *pcRes = script->code();
             return;
--- a/js/src/jit/JitFrameIterator.h
+++ b/js/src/jit/JitFrameIterator.h
@@ -186,16 +186,20 @@ class JitFrameIterator
     Value *actualArgs() const;
 
     // Returns the return address of the frame above this one (that is, the
     // return address that returns back to the current frame).
     uint8_t *returnAddressToFp() const {
         return returnAddressToFp_;
     }
 
+    // Returns the resume address. As above, except taking
+    // BaselineDebugModeOSRInfo into account, if present.
+    uint8_t *resumeAddressToFp() const;
+
     // Previous frame information extracted from the current frame.
     inline size_t prevFrameLocalSize() const;
     inline FrameType prevType() const;
     uint8_t *prevFp() const;
 
     // Returns the stack space used by the current frame, in bytes. This does
     // not include the size of its fixed header.
     size_t frameSize() const {
--- a/js/src/jit/arm/Simulator-arm.cpp
+++ b/js/src/jit/arm/Simulator-arm.cpp
@@ -1486,18 +1486,18 @@ Simulator::setCallResult(int64_t res)
 {
     set_register(r0, static_cast<int32_t>(res));
     set_register(r1, static_cast<int32_t>(res >> 32));
 }
 
 int
 Simulator::readW(int32_t addr, SimInstruction *instr)
 {
-    // The regexp engines emit unaligned loads, so we don't check for them here
-    // like the other methods below.
+    // The regexp engine emits unaligned loads, so we don't check for them here
+    // like most of the other methods do.
     intptr_t *ptr = reinterpret_cast<intptr_t*>(addr);
     return *ptr;
 }
 
 void
 Simulator::writeW(int32_t addr, int value, SimInstruction *instr)
 {
     if ((addr & 3) == 0) {
@@ -1507,23 +1507,20 @@ Simulator::writeW(int32_t addr, int valu
         printf("Unaligned write at 0x%08x, pc=%p\n", addr, instr);
         MOZ_CRASH();
     }
 }
 
 uint16_t
 Simulator::readHU(int32_t addr, SimInstruction *instr)
 {
-    if ((addr & 1) == 0) {
-        uint16_t *ptr = reinterpret_cast<uint16_t*>(addr);
-        return *ptr;
-    }
-    printf("Unaligned unsigned halfword read at 0x%08x, pc=%p\n", addr, instr);
-    MOZ_CRASH();
-    return 0;
+    // The regexp engine emits unaligned loads, so we don't check for them here
+    // like most of the other methods do.
+    uint16_t *ptr = reinterpret_cast<uint16_t*>(addr);
+    return *ptr;
 }
 
 int16_t
 Simulator::readH(int32_t addr, SimInstruction *instr)
 {
     if ((addr & 1) == 0) {
         int16_t *ptr = reinterpret_cast<int16_t*>(addr);
         return *ptr;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1759,29 +1759,32 @@ extern JS_PUBLIC_API(bool)
 JS_GetClassObject(JSContext *cx, JSProtoKey key, JS::MutableHandle<JSObject*> objp);
 
 extern JS_PUBLIC_API(bool)
 JS_GetClassPrototype(JSContext *cx, JSProtoKey key, JS::MutableHandle<JSObject*> objp);
 
 namespace JS {
 
 /*
- * Determine if the given object is an instance or prototype for a standard
+ * Determine if the given object is an instance/prototype/constructor for a standard
  * class. If so, return the associated JSProtoKey. If not, return JSProto_Null.
  */
 
 extern JS_PUBLIC_API(JSProtoKey)
 IdentifyStandardInstance(JSObject *obj);
 
 extern JS_PUBLIC_API(JSProtoKey)
 IdentifyStandardPrototype(JSObject *obj);
 
 extern JS_PUBLIC_API(JSProtoKey)
 IdentifyStandardInstanceOrPrototype(JSObject *obj);
 
+extern JS_PUBLIC_API(JSProtoKey)
+IdentifyStandardConstructor(JSObject *obj);
+
 } /* namespace JS */
 
 extern JS_PUBLIC_API(JSProtoKey)
 JS_IdToProtoKey(JSContext *cx, JS::HandleId id);
 
 /*
  * Returns the original value of |Function.prototype| from the global object in
  * which |forObj| was created.
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3434,16 +3434,36 @@ JS::IdentifyStandardPrototype(JSObject *
 }
 
 JSProtoKey
 JS::IdentifyStandardInstanceOrPrototype(JSObject *obj)
 {
     return JSCLASS_CACHED_PROTO_KEY(obj->getClass());
 }
 
+JSProtoKey
+JS::IdentifyStandardConstructor(JSObject *obj)
+{
+    // Note that NATIVE_CTOR does not imply that we are a standard constructor,
+    // but the converse is true (at least until we start having self-hosted
+    // constructors for standard classes). This lets us avoid a costly loop for
+    // many functions (which, depending on the call site, may be the common case).
+    if (!obj->is<JSFunction>() || !(obj->as<JSFunction>().flags() & JSFunction::NATIVE_CTOR))
+        return JSProto_Null;
+
+    GlobalObject &global = obj->global();
+    for (size_t k = 0; k < JSProto_LIMIT; ++k) {
+        JSProtoKey key = static_cast<JSProtoKey>(k);
+        if (global.getConstructor(key) == ObjectValue(*obj))
+            return key;
+    }
+
+    return JSProto_Null;
+}
+
 bool
 js::FindClassObject(ExclusiveContext *cx, MutableHandleObject protop, const Class *clasp)
 {
     JSProtoKey protoKey = GetClassProtoKey(clasp);
     if (protoKey != JSProto_Null) {
         JS_ASSERT(JSProto_Null < protoKey);
         JS_ASSERT(protoKey < JSProto_LIMIT);
         return GetBuiltinConstructor(cx, protoKey, protop);
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2015,35 +2015,50 @@ class MOZ_STACK_CLASS StringRegExpGuard
     RootedObject obj_;
 
     /*
      * Upper bound on the number of characters we are willing to potentially
      * waste on searching for RegExp meta-characters.
      */
     static const size_t MAX_FLAT_PAT_LEN = 256;
 
+    template <typename CharT>
+    static bool
+    flattenPattern(StringBuffer &sb, const CharT *chars, size_t len)
+    {
+        static const char ESCAPE_CHAR = '\\';
+        for (const CharT *it = chars; it < chars + len; ++it) {
+            if (IsRegExpMetaChar(*it)) {
+                if (!sb.append(ESCAPE_CHAR) || !sb.append(*it))
+                    return false;
+            } else {
+                if (!sb.append(*it))
+                    return false;
+            }
+        }
+        return true;
+    }
+
     static JSAtom *
-    flattenPattern(JSContext *cx, JSAtom *patstr)
+    flattenPattern(JSContext *cx, JSAtom *pat)
     {
         StringBuffer sb(cx);
-        if (!sb.reserve(patstr->length()))
+        if (!sb.reserve(pat->length()))
             return nullptr;
 
-        static const jschar ESCAPE_CHAR = '\\';
-        const jschar *chars = patstr->chars();
-        size_t len = patstr->length();
-        for (const jschar *it = chars; it != chars + len; ++it) {
-            if (IsRegExpMetaChar(*it)) {
-                if (!sb.append(ESCAPE_CHAR) || !sb.append(*it))
-                    return nullptr;
-            } else {
-                if (!sb.append(*it))
-                    return nullptr;
-            }
+        if (pat->hasLatin1Chars()) {
+            AutoCheckCannotGC nogc;
+            if (!flattenPattern(sb, pat->latin1Chars(nogc), pat->length()))
+                return nullptr;
+        } else {
+            AutoCheckCannotGC nogc;
+            if (!flattenPattern(sb, pat->twoByteChars(nogc), pat->length()))
+                return nullptr;
         }
+
         return sb.finishAtom();
     }
 
   public:
     explicit StringRegExpGuard(JSContext *cx)
       : re_(cx), fm(cx), obj_(cx)
     { }
 
@@ -2450,36 +2465,45 @@ class RopeBuilder {
 
     inline JSString *result() {
         return res;
     }
 };
 
 namespace {
 
+template <typename CharT>
+static uint32_t
+FindDollarIndex(const CharT *chars, size_t length)
+{
+    if (const CharT *p = js_strchr_limit(chars, '$', chars + length)) {
+        uint32_t dollarIndex = p - chars;
+        MOZ_ASSERT(dollarIndex < length);
+        return dollarIndex;
+    }
+    return UINT32_MAX;
+}
+
 struct ReplaceData
 {
     explicit ReplaceData(JSContext *cx)
       : str(cx), g(cx), lambda(cx), elembase(cx), repstr(cx),
         fig(cx, NullValue()), sb(cx)
     {}
 
     inline void setReplacementString(JSLinearString *string) {
         JS_ASSERT(string);
         lambda = nullptr;
         elembase = nullptr;
         repstr = string;
 
-        const jschar *chars = repstr->chars();
-        if (const jschar *p = js_strchr_limit(chars, '$', chars + repstr->length())) {
-            dollarIndex = p - chars;
-            MOZ_ASSERT(dollarIndex < repstr->length());
-        } else {
-            dollarIndex = UINT32_MAX;
-        }
+        AutoCheckCannotGC nogc;
+        dollarIndex = string->hasLatin1Chars()
+                      ? FindDollarIndex(string->latin1Chars(nogc), string->length())
+                      : FindDollarIndex(string->twoByteChars(nogc), string->length());
     }
 
     inline void setReplacementFunction(JSObject *func) {
         JS_ASSERT(func);
         lambda = func;
         elembase = nullptr;
         repstr = nullptr;
         dollarIndex = UINT32_MAX;
@@ -2545,35 +2569,36 @@ DoMatchForReplaceGlobal(JSContext *cx, R
             return false;
         if (!res->matched())
             ++i;
     }
 
     return true;
 }
 
+template <typename CharT>
 static bool
-InterpretDollar(RegExpStatics *res, const jschar *dp, const jschar *ep,
+InterpretDollar(RegExpStatics *res, const CharT *bp, const CharT *dp, const CharT *ep,
                 ReplaceData &rdata, JSSubString *out, size_t *skip)
 {
     JS_ASSERT(*dp == '$');
 
     /* If there is only a dollar, bail now */
     if (dp + 1 >= ep)
         return false;
 
     /* Interpret all Perl match-induced dollar variables. */
     jschar dc = dp[1];
     if (JS7_ISDEC(dc)) {
         /* ECMA-262 Edition 3: 1-9 or 01-99 */
         unsigned num = JS7_UNDEC(dc);
         if (num > res->getMatches().parenCount())
             return false;
 
-        const jschar *cp = dp + 2;
+        const CharT *cp = dp + 2;
         if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) {
             unsigned tmp = 10 * num + JS7_UNDEC(dc);
             if (tmp <= res->getMatches().parenCount()) {
                 cp++;
                 num = tmp;
             }
         }
         if (num == 0)
@@ -2589,17 +2614,17 @@ InterpretDollar(RegExpStatics *res, cons
          */
         res->getParen(num, out);
         return true;
     }
 
     *skip = 2;
     switch (dc) {
       case '$':
-        out->init(rdata.repstr, dp - rdata.repstr->chars(), 1);
+        out->init(rdata.repstr, dp - bp, 1);
         return true;
       case '&':
         res->getLastMatch(out);
         return true;
       case '+':
         res->getLastParen(out);
         return true;
       case '`':
@@ -2607,16 +2632,55 @@ InterpretDollar(RegExpStatics *res, cons
         return true;
       case '\'':
         res->getRightContext(out);
         return true;
     }
     return false;
 }
 
+template <typename CharT>
+static bool
+FindReplaceLengthString(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep)
+{
+    JSLinearString *repstr = rdata.repstr;
+    CheckedInt<uint32_t> replen = repstr->length();
+
+    if (rdata.dollarIndex != UINT32_MAX) {
+        AutoCheckCannotGC nogc;
+        MOZ_ASSERT(rdata.dollarIndex < repstr->length());
+        const CharT *bp = repstr->chars<CharT>(nogc);
+        const CharT *dp = bp + rdata.dollarIndex;
+        const CharT *ep = bp + repstr->length();
+        do {
+            JSSubString sub;
+            size_t skip;
+            if (InterpretDollar(res, bp, dp, ep, rdata, &sub, &skip)) {
+                if (sub.length > skip)
+                    replen += sub.length - skip;
+                else
+                    replen -= skip - sub.length;
+                dp += skip;
+            } else {
+                dp++;
+            }
+
+            dp = js_strchr_limit(dp, '$', ep);
+        } while (dp);
+    }
+
+    if (!replen.isValid()) {
+        js_ReportAllocationOverflow(cx);
+        return false;
+    }
+
+    *sizep = replen.value();
+    return true;
+}
+
 static bool
 FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep)
 {
     if (rdata.elembase) {
         /*
          * The base object is used when replace was passed a lambda which looks like
          * 'function(a) { return b[a]; }' for the base object b.  b will not change
          * in the course of the replace unless we end up making a scripted call due
@@ -2696,73 +2760,48 @@ FindReplaceLength(JSContext *cx, RegExpS
             return false;
         rdata.repstr = repstr->ensureLinear(cx);
         if (!rdata.repstr)
             return false;
         *sizep = rdata.repstr->length();
         return true;
     }
 
-    JSLinearString *repstr = rdata.repstr;
-    CheckedInt<uint32_t> replen = repstr->length();
-    if (rdata.dollarIndex != UINT32_MAX) {
-        MOZ_ASSERT(rdata.dollarIndex < repstr->length());
-        const jschar *dp = repstr->chars() + rdata.dollarIndex;
-        const jschar *ep = repstr->chars() + repstr->length();
-        do {
-            JSSubString sub;
-            size_t skip;
-            if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) {
-                if (sub.length > skip)
-                    replen += sub.length - skip;
-                else
-                    replen -= skip - sub.length;
-                dp += skip;
-            } else {
-                dp++;
-            }
-
-            dp = js_strchr_limit(dp, '$', ep);
-        } while (dp);
-    }
-
-    if (!replen.isValid()) {
-        js_ReportAllocationOverflow(cx);
-        return false;
-    }
-
-    *sizep = replen.value();
-    return true;
+    return rdata.repstr->hasLatin1Chars()
+           ? FindReplaceLengthString<Latin1Char>(cx, res, rdata, sizep)
+           : FindReplaceLengthString<jschar>(cx, res, rdata, sizep);
 }
 
 /*
  * Precondition: |rdata.sb| already has necessary growth space reserved (as
  * derived from FindReplaceLength), and has been inflated to TwoByte if
  * necessary.
  */
+template <typename CharT>
 static void
 DoReplace(RegExpStatics *res, ReplaceData &rdata)
 {
+    AutoCheckCannotGC nogc;
     JSLinearString *repstr = rdata.repstr;
-    const jschar *bp = repstr->chars();
-    const jschar *cp = bp;
+    const CharT *bp = repstr->chars<CharT>(nogc);
+    const CharT *cp = bp;
 
     if (rdata.dollarIndex != UINT32_MAX) {
         MOZ_ASSERT(rdata.dollarIndex < repstr->length());
-        const jschar *dp = bp + rdata.dollarIndex;
-        const jschar *ep = bp + repstr->length();
+        const CharT *dp = bp + rdata.dollarIndex;
+        const CharT *ep = bp + repstr->length();
         do {
             /* Move one of the constant portions of the replacement value. */
             size_t len = dp - cp;
             rdata.sb.infallibleAppend(cp, len);
             cp = dp;
 
             JSSubString sub;
             size_t skip;
-            if (InterpretDollar(res, dp, ep, rdata, &sub, &skip)) {
+            if (InterpretDollar(res, bp, dp, ep, rdata, &sub, &skip)) {
                 rdata.sb.infallibleAppendSubstring(sub.base, sub.offset, sub.length);
                 cp += skip;
                 dp += skip;
             } else {
                 dp++;
             }
 
             dp = js_strchr_limit(dp, '$', ep);
@@ -2805,20 +2844,22 @@ ReplaceRegExp(JSContext *cx, RegExpStati
         if (!rdata.sb.ensureTwoByteChars())
             return false;
     }
 
     if (!rdata.sb.reserve(newlen.value()))
         return false;
 
     /* Append skipped-over portion of the search value. */
-    const jschar *left = str.chars() + leftoff;
-    rdata.sb.infallibleAppend(left, leftlen);
-
-    DoReplace(res, rdata);
+    rdata.sb.infallibleAppendSubstring(&str, leftoff, leftlen);
+
+    if (rdata.repstr->hasLatin1Chars())
+        DoReplace<Latin1Char>(res, rdata);
+    else
+        DoReplace<jschar>(res, rdata);
     return true;
 }
 
 static bool
 BuildFlatReplacement(JSContext *cx, HandleString textstr, HandleString repstr,
                      const FlatMatch &fm, MutableHandleValue rval)
 {
     RopeBuilder builder(cx);
@@ -2890,16 +2931,67 @@ BuildFlatReplacement(JSContext *cx, Hand
             return false;
         }
     }
 
     rval.setString(builder.result());
     return true;
 }
 
+template <typename CharT>
+static bool
+AppendDollarReplacement(StringBuffer &newReplaceChars, size_t firstDollarIndex,
+                        const FlatMatch &fm, JSLinearString *text,
+                        const CharT *repChars, size_t repLength)
+{
+    JS_ASSERT(firstDollarIndex < repLength);
+
+    size_t matchStart = fm.match();
+    size_t matchLimit = matchStart + fm.patternLength();
+
+    /* Move the pre-dollar chunk in bulk. */
+    newReplaceChars.infallibleAppend(repChars, firstDollarIndex);
+
+    /* Move the rest char-by-char, interpreting dollars as we encounter them. */
+    const CharT *repLimit = repChars + repLength;
+    for (const CharT *it = repChars + firstDollarIndex; it < repLimit; ++it) {
+        if (*it != '$' || it == repLimit - 1) {
+            if (!newReplaceChars.append(*it))
+                return false;
+            continue;
+        }
+
+        switch (*(it + 1)) {
+          case '$': /* Eat one of the dollars. */
+            if (!newReplaceChars.append(*it))
+                return false;
+            break;
+          case '&':
+            if (!newReplaceChars.appendSubstring(text, matchStart, matchLimit - matchStart))
+                return false;
+            break;
+          case '`':
+            if (!newReplaceChars.appendSubstring(text, 0, matchStart))
+                return false;
+            break;
+          case '\'':
+            if (!newReplaceChars.appendSubstring(text, matchLimit, text->length() - matchLimit))
+                return false;
+            break;
+          default: /* The dollar we saw was not special (no matter what its mother told it). */
+            if (!newReplaceChars.append(*it))
+                return false;
+            continue;
+        }
+        ++it; /* We always eat an extra char in the above switch. */
+    }
+
+    return true;
+}
+
 /*
  * Perform a linear-scan dollar substitution on the replacement text,
  * constructing a result string that looks like:
  *
  *      newstring = string[:matchStart] + dollarSub(replaceValue) + string[matchLimit:]
  */
 static inline bool
 BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *repstr,
@@ -2921,55 +3013,28 @@ BuildDollarReplacement(JSContext *cx, JS
      */
     StringBuffer newReplaceChars(cx);
     if (repstr->hasTwoByteChars() && !newReplaceChars.ensureTwoByteChars())
         return false;
 
     if (!newReplaceChars.reserve(textstr->length() - fm.patternLength() + repstr->length()))
         return false;
 
-    JS_ASSERT(firstDollarIndex < repstr->length());
-
-    /* Move the pre-dollar chunk in bulk. */
-    newReplaceChars.infallibleAppend(repstr->chars(), firstDollarIndex);
-
-    /* Move the rest char-by-char, interpreting dollars as we encounter them. */
-    const jschar *textchars = textstr->chars();
-    const jschar *repstrLimit = repstr->chars() + repstr->length();
-    for (const jschar *it = repstr->chars() + firstDollarIndex; it < repstrLimit; ++it) {
-        if (*it != '$' || it == repstrLimit - 1) {
-            if (!newReplaceChars.append(*it))
-                return false;
-            continue;
-        }
-
-        switch (*(it + 1)) {
-          case '$': /* Eat one of the dollars. */
-            if (!newReplaceChars.append(*it))
-                return false;
-            break;
-          case '&':
-            if (!newReplaceChars.append(textchars + matchStart, textchars + matchLimit))
-                return false;
-            break;
-          case '`':
-            if (!newReplaceChars.append(textchars, textchars + matchStart))
-                return false;
-            break;
-          case '\'':
-            if (!newReplaceChars.append(textchars + matchLimit, textchars + textstr->length()))
-                return false;
-            break;
-          default: /* The dollar we saw was not special (no matter what its mother told it). */
-            if (!newReplaceChars.append(*it))
-                return false;
-            continue;
-        }
-        ++it; /* We always eat an extra char in the above switch. */
+    bool res;
+    if (repstr->hasLatin1Chars()) {
+        AutoCheckCannotGC nogc;
+        res = AppendDollarReplacement(newReplaceChars, firstDollarIndex, fm, textstr,
+                                      repstr->latin1Chars(nogc), repstr->length());
+    } else {
+        AutoCheckCannotGC nogc;
+        res = AppendDollarReplacement(newReplaceChars, firstDollarIndex, fm, textstr,
+                                      repstr->twoByteChars(nogc), repstr->length());
     }
+    if (!res)
+        return false;
 
     RootedString leftSide(cx, js_NewDependentString(cx, textstr, 0, matchStart));
     if (!leftSide)
         return false;
 
     RootedString newReplace(cx, newReplaceChars.finishString());
     if (!newReplace)
         return false;
@@ -2993,74 +3058,85 @@ struct StringRange
     size_t start;
     size_t length;
 
     StringRange(size_t s, size_t l)
       : start(s), length(l)
     { }
 };
 
+template <typename CharT>
+static void
+CopySubstringsToFatInline(JSFatInlineString *dest, const CharT *src, const StringRange *ranges,
+                          size_t rangesLen, size_t outputLen)
+{
+    CharT *buf = dest->init<CharT>(outputLen);
+    size_t pos = 0;
+    for (size_t i = 0; i < rangesLen; i++) {
+        PodCopy(buf + pos, src + ranges[i].start, ranges[i].length);
+        pos += ranges[i].length;
+    }
+
+    MOZ_ASSERT(pos == outputLen);
+    buf[outputLen] = 0;
+}
+
 static inline JSFatInlineString *
-FlattenSubstrings(JSContext *cx, const jschar *chars,
-                  const StringRange *ranges, size_t rangesLen, size_t outputLen)
+FlattenSubstrings(JSContext *cx, Handle<JSFlatString*> flatStr, const StringRange *ranges,
+                  size_t rangesLen, size_t outputLen)
 {
-    JS_ASSERT(JSFatInlineString::twoByteLengthFits(outputLen));
-
     JSFatInlineString *str = js_NewGCFatInlineString<CanGC>(cx);
     if (!str)
         return nullptr;
 
-    jschar *buf = str->initTwoByte(outputLen);
-    size_t pos = 0;
-    for (size_t i = 0; i < rangesLen; i++) {
-        PodCopy(buf + pos, chars + ranges[i].start, ranges[i].length);
-        pos += ranges[i].length;
-    }
-    JS_ASSERT(pos == outputLen);
-
-    buf[outputLen] = 0;
+    AutoCheckCannotGC nogc;
+    if (flatStr->hasLatin1Chars())
+        CopySubstringsToFatInline(str, flatStr->latin1Chars(nogc), ranges, rangesLen, outputLen);
+    else
+        CopySubstringsToFatInline(str, flatStr->twoByteChars(nogc), ranges, rangesLen, outputLen);
     return str;
 }
 
 static JSString *
 AppendSubstrings(JSContext *cx, Handle<JSFlatString*> flatStr,
                  const StringRange *ranges, size_t rangesLen)
 {
     JS_ASSERT(rangesLen);
 
     /* For single substrings, construct a dependent string. */
     if (rangesLen == 1)
         return js_NewDependentString(cx, flatStr, ranges[0].start, ranges[0].length);
 
-    const jschar *chars = flatStr->getChars(cx);
-    if (!chars)
-        return nullptr;
+    bool isLatin1 = flatStr->hasLatin1Chars();
+    uint32_t fatInlineMaxLength = isLatin1
+                                  ? JSFatInlineString::MAX_LENGTH_LATIN1
+                                  : JSFatInlineString::MAX_LENGTH_TWO_BYTE;
 
     /* Collect substrings into a rope */
     size_t i = 0;
     RopeBuilder rope(cx);
     RootedString part(cx, nullptr);
     while (i < rangesLen) {
 
         /* Find maximum range that fits in JSFatInlineString */
         size_t substrLen = 0;
         size_t end = i;
         for (; end < rangesLen; end++) {
-            if (substrLen + ranges[end].length > JSFatInlineString::MAX_LENGTH_TWO_BYTE)
+            if (substrLen + ranges[end].length > fatInlineMaxLength)
                 break;
             substrLen += ranges[end].length;
         }
 
         if (i == end) {
             /* Not even one range fits JSFatInlineString, use DependentString */
             const StringRange &sr = ranges[i++];
             part = js_NewDependentString(cx, flatStr, sr.start, sr.length);
         } else {
             /* Copy the ranges (linearly) into a JSFatInlineString */
-            part = FlattenSubstrings(cx, chars, ranges + i, end - i, substrLen);
+            part = FlattenSubstrings(cx, flatStr, ranges + i, end - i, substrLen);
             i = end;
         }
 
         if (!part)
             return nullptr;
 
         /* Appending to the rope permanently roots the substring. */
         if (!rope.append(part))
@@ -4618,27 +4694,34 @@ js_strdup(js::ThreadSafeContext *cx, con
     jschar *ret = cx->pod_malloc<jschar>(n + 1);
     if (!ret)
         return nullptr;
     js_strncpy(ret, s, n);
     ret[n] = '\0';
     return ret;
 }
 
-jschar *
-js_strchr_limit(const jschar *s, jschar c, const jschar *limit)
+template <typename CharT>
+const CharT *
+js_strchr_limit(const CharT *s, jschar c, const CharT *limit)
 {
     while (s < limit) {
         if (*s == c)
-            return (jschar *)s;
+            return s;
         s++;
     }
     return nullptr;
 }
 
+template const Latin1Char *
+js_strchr_limit(const Latin1Char *s, jschar c, const Latin1Char *limit);
+
+template const jschar *
+js_strchr_limit(const jschar *s, jschar c, const jschar *limit);
+
 jschar *
 js::InflateString(ThreadSafeContext *cx, const char *bytes, size_t *lengthp)
 {
     size_t nchars;
     jschar *chars;
     size_t nbytes = *lengthp;
 
     nchars = nbytes;
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -226,18 +226,19 @@ StringHasRegExpMetaChars(const jschar *c
 } /* namespace js */
 
 extern size_t
 js_strlen(const jschar *s);
 
 extern int32_t
 js_strcmp(const jschar *lhs, const jschar *rhs);
 
-extern jschar *
-js_strchr_limit(const jschar *s, jschar c, const jschar *limit);
+template <typename CharT>
+extern const CharT *
+js_strchr_limit(const CharT *s, jschar c, const CharT *limit);
 
 static MOZ_ALWAYS_INLINE void
 js_strncpy(jschar *dst, const jschar *src, size_t nelem)
 {
     return mozilla::PodCopy(dst, src, nelem);
 }
 
 extern jschar *
--- a/js/src/tests/browser.js
+++ b/js/src/tests/browser.js
@@ -338,20 +338,16 @@ function jsTestDriverBrowserInit()
     else if (properties.test.match(/^js1_7/))
     {
       properties.version = '1.7';
     }
     else if (properties.test.match(/^js1_8/))
     {
       properties.version = '1.8';
     }
-    else if (properties.test.match(/^ecma_6\/LexicalEnvironment/))
-    {
-      properties.version = '1.8';
-    }
   }
 
   // default to language=type;text/javascript. required for
   // reftest style manifests.
   if (!properties.language)
   {
     properties.language = 'type';
     properties.mimetype = 'text/javascript';
deleted file mode 100644
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/shell.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// NOTE: This only turns on 1.8.5 in shell builds.  The browser requires the
-//       futzing in js/src/tests/browser.js (which only turns on 1.8, the most
-//       the browser supports).
-if (typeof version != 'undefined')
-  version(185);
deleted file mode 100644
--- a/js/src/tests/ecma_6/LexicalEnvironment/with-global-ignores-global-let-variables.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// |reftest| fails-if(Function("try{Function('let\x20x=5;');return(1,eval)('let\x20x=3;\\'x\\'\x20in\x20this');}catch(e){return(true);}")()) -- needs bug 589199 fix (top-level let not same as var)
-// Any copyright is dedicated to the Public Domain.
-// http://creativecommons.org/licenses/publicdomain/
-
-let v = "global-v";
-
-function f(v, global)
-{
-  with (global)
-    return v;
-}
-
-assertEq(f("argument-v", this), "argument-v");
-
-if (typeof reportCompare === "function")
-  reportCompare(true, true);
-
-print("Tests complete");
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -23,16 +23,18 @@
 #include "vm/Shape-inl.h"
 
 using namespace js;
 
 using mozilla::DebugOnly;
 using mozilla::Maybe;
 using js::frontend::TokenStream;
 
+using JS::AutoCheckCannotGC;
+
 JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
 JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
 JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
 JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
 
 /* RegExpObjectBuilder */
 
 RegExpObjectBuilder::RegExpObjectBuilder(ExclusiveContext *cx, RegExpObject *reobj)
@@ -587,34 +589,51 @@ RegExpShared::execute(JSContext *cx, Han
 
         matches.checkAgainst(origLength);
         *lastIndex = matches[0].limit;
         return RegExpRunStatus_Success;
     }
 
     if (uint8_t *byteCode = maybeByteCode(input->hasLatin1Chars())) {
         AutoTraceLog logInterpreter(logger, TraceLogger::IrregexpExecute);
-        const jschar *chars = input->chars() + charsOffset;
-        RegExpRunStatus result =
-            irregexp::InterpretCode(cx, byteCode, chars, start, length, &matches);
+
+        AutoStableStringChars inputChars(cx, input);
+        if (!inputChars.init())
+            return RegExpRunStatus_Error;
+
+        RegExpRunStatus result;
+        if (inputChars.isLatin1()) {
+            const Latin1Char *chars = inputChars.latin1Range().start().get() + charsOffset;
+            result = irregexp::InterpretCode(cx, byteCode, chars, start, length, &matches);
+        } else {
+            const jschar *chars = inputChars.twoByteRange().start().get() + charsOffset;
+            result = irregexp::InterpretCode(cx, byteCode, chars, start, length, &matches);
+        }
+
         if (result == RegExpRunStatus_Success) {
             matches.displace(displacement);
             matches.checkAgainst(origLength);
             *lastIndex = matches[0].limit;
         }
         return result;
     }
 
 #ifdef JS_ION
     while (true) {
         RegExpRunStatus result;
         {
             AutoTraceLog logJIT(logger, TraceLogger::IrregexpExecute);
-            const jschar *chars = input->chars() + charsOffset;
-            result = irregexp::ExecuteCode(cx, jitCodeTwoByte, chars, start, length, &matches);
+            AutoCheckCannotGC nogc;
+            if (input->hasLatin1Chars()) {
+                const Latin1Char *chars = input->latin1Chars(nogc) + charsOffset;
+                result = irregexp::ExecuteCode(cx, jitCodeLatin1, chars, start, length, &matches);
+            } else {
+                const jschar *chars = input->twoByteChars(nogc) + charsOffset;
+                result = irregexp::ExecuteCode(cx, jitCodeTwoByte, chars, start, length, &matches);
+            }
         }
 
         if (result == RegExpRunStatus_Error) {
             // The RegExp engine might exit with an exception if an interrupt
             // was requested. Check this case and retry until a clean result is
             // obtained.
             bool interrupted;
             {
@@ -880,20 +899,20 @@ js::ParseRegExpFlags(JSContext *cx, JSSt
     if (!linear)
         return false;
 
     size_t len = linear->length();
 
     bool ok;
     jschar lastParsed;
     if (linear->hasLatin1Chars()) {
-        JS::AutoCheckCannotGC nogc;
+        AutoCheckCannotGC nogc;
         ok = ::ParseRegExpFlags(linear->latin1Chars(nogc), len, flagsOut, &lastParsed);
     } else {
-        JS::AutoCheckCannotGC nogc;
+        AutoCheckCannotGC nogc;
         ok = ::ParseRegExpFlags(linear->twoByteChars(nogc), len, flagsOut, &lastParsed);
     }
 
     if (!ok) {
         char charBuf[2];
         charBuf[0] = char(lastParsed);
         charBuf[1] = '\0';
         JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, nullptr,
--- a/js/src/vm/StringBuffer.h
+++ b/js/src/vm/StringBuffer.h
@@ -250,17 +250,17 @@ StringBuffer::append(JSLinearString *str
            ? twoByteChars().append(str->latin1Chars(nogc), str->length())
            : twoByteChars().append(str->twoByteChars(nogc), str->length());
 }
 
 inline void
 StringBuffer::infallibleAppendSubstring(JSLinearString *base, size_t off, size_t len)
 {
     MOZ_ASSERT(off + len <= base->length());
-    MOZ_ASSERT(base->hasLatin1Chars() == isLatin1());
+    MOZ_ASSERT_IF(base->hasTwoByteChars(), isTwoByte());
 
     JS::AutoCheckCannotGC nogc;
     if (base->hasLatin1Chars())
         infallibleAppend(base->latin1Chars(nogc) + off, len);
     else
         infallibleAppend(base->twoByteChars(nogc) + off, len);
 }
 
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -75,18 +75,20 @@ const char* const XPCJSRuntime::mStrings
     "Function",             // IDX_FUNCTION
     "prototype",            // IDX_PROTOTYPE
     "createInstance",       // IDX_CREATE_INSTANCE
     "item",                 // IDX_ITEM
     "__proto__",            // IDX_PROTO
     "__iterator__",         // IDX_ITERATOR
     "__exposedProps__",     // IDX_EXPOSEDPROPS
     "eval",                 // IDX_EVAL
-    "controllers",           // IDX_CONTROLLERS
+    "controllers",          // IDX_CONTROLLERS
     "realFrameElement",     // IDX_REALFRAMEELEMENT
+    "length",               // IDX_LENGTH
+    "name",                 // IDX_NAME
 };
 
 /***************************************************************************/
 
 static mozilla::Atomic<bool> sDiscardSystemSource(false);
 
 bool
 xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; }
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -475,16 +475,18 @@ public:
         IDX_CREATE_INSTANCE         ,
         IDX_ITEM                    ,
         IDX_PROTO                   ,
         IDX_ITERATOR                ,
         IDX_EXPOSEDPROPS            ,
         IDX_EVAL                    ,
         IDX_CONTROLLERS             ,
         IDX_REALFRAMEELEMENT        ,
+        IDX_LENGTH                  ,
+        IDX_NAME                    ,
         IDX_TOTAL_COUNT // just a count of the above
     };
 
     JS::HandleId GetStringID(unsigned index) const
     {
         MOZ_ASSERT(index < IDX_TOTAL_COUNT, "index out of range");
         // fromMarkedLocation() is safe because the string is interned.
         return JS::HandleId::fromMarkedLocation(&mStrIDs[index]);
--- a/js/xpconnect/tests/chrome/test_bug448587.xul
+++ b/js/xpconnect/tests/chrome/test_bug448587.xul
@@ -24,13 +24,14 @@ https://bugzilla.mozilla.org/show_bug.cg
   ok(!SpecialPowers.Services.prefs.prefHasUserValue('testing.some_arbitrary_pref'),
      "Pref shouldn't carry over from previous test!");
 
 
   /** Test for Bug 448587 **/
   const Cu = Components.utils;
   var sandbox = new Cu.Sandbox("about:blank");
   var fwrapper = Cu.evalInSandbox("function f() {} f", sandbox);
-  is(fwrapper.prototype, Cu.evalInSandbox("f.prototype", sandbox),
-     "we don't censor .prototype through .wrappedJSObject");
+  is(Cu.unwaiveXrays(Cu.waiveXrays(fwrapper).prototype), Cu.evalInSandbox("f.prototype", sandbox),
+     ".prototype visible through .wrappedJSObject");
+  is(fwrapper.prototype, undefined, ".prototype invisible through Xrays");
   ]]>
   </script>
 </window>
--- a/js/xpconnect/tests/chrome/test_bug812415.xul
+++ b/js/xpconnect/tests/chrome/test_bug812415.xul
@@ -51,17 +51,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     checkThrows('expFun.bar = 0', regular, "Regular shouldn't write properties");
 
     // Check functions.
     is(Cu.evalInSandbox('regFun()', expanded), 42, "Expanded can call regular function");
     checkThrows('expFun()', regular, "Regular cannot call expanded function");
     is(Cu.evalInSandbox('regFun.name', expanded), 'reg', "Expanded can see regular function's name");
     checkThrows('expFun.name', regular, "Regular can't see expanded function's name");
     Cu.evalInSandbox('regFun.expando = 30', expanded);
-    is(expanded.regFun.expando, 30, "Expanded can set expandos");
+    is(Cu.evalInSandbox('regFun.expando', expanded), 30, "Expanded can set expandos");
     checkThrows('expFun.expando = 29', regular, "Regular can't set expandos");
 
     // Check __proto__ stuff.
     is(Cu.evalInSandbox('regFun.__proto__', expanded), regular.Function.prototype, "expanded can get __proto__");
     checkThrows('expFun.__proto__', regular, "regular can't use __proto__");
     checkThrows('expFun.__proto__ = {}', regular, "regular can't mutate __proto__");
   }
 
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -48,29 +48,28 @@ https://bugzilla.mozilla.org/show_bug.cg
 
     // Test constructors that can be instantiated with zero arguments.
     for (var c of simpleConstructors) {
       ok(iwin[c], "Constructors appear: " + c);
       is(iwin[c], Cu.unwaiveXrays(iwin.wrappedJSObject[c]),
          "we end up with the appropriate constructor: " + c);
       is(Cu.unwaiveXrays(Cu.waiveXrays(new iwin[c]).constructor), iwin[c],
          "constructor property is set up right: " + c);
-      is(Object.getPrototypeOf(new iwin[c]),
-         Cu.unwaiveXrays(Cu.waiveXrays(iwin[c]).prototype),
+      is(Object.getPrototypeOf(new iwin[c]), iwin[c].prototype,
          "prototype is correct: " + c);
       is(global(new iwin[c]), iwin, "Got the right global: " + c);
     }
 
     // Test Object in more detail.
     var num = new iwin.Object(4);
     is(num.valueOf(), 4, "primitive object construction works");
     is(global(num), iwin, "correct global for num");
     var obj = new iwin.Object();
     obj.foo = 2;
-    var withProto = iwin.Object.create(obj);
+    var withProto = Cu.unwaiveXrays(Cu.waiveXrays(iwin).Object.create(obj));
     is(global(withProto), iwin, "correct global for withProto");
     is(Cu.waiveXrays(withProto).foo, 2, "Inherits properly");
 
     // Test Function.
     var primitiveFun = new iwin.Function('return 2');
     is(global(primitiveFun), iwin, "function construction works");
     is(primitiveFun(), 2, "basic function works");
     var doSetFoo = new iwin.Function('arg', 'arg.foo = 2;');
@@ -80,16 +79,35 @@ https://bugzilla.mozilla.org/show_bug.cg
       ok(false, "should have thrown while setting property on object");
     } catch (e) {
       ok(!!/denied/.test(e), "Threw correctly: " + e);
     }
     var factoryFun = new iwin.Function('return {foo: 32}');
     is(global(factoryFun), iwin, "proper global for factoryFun");
     is(factoryFun().foo, 32, "factoryFun invokable");
     is(global(factoryFun()), iwin, "minted objects live in the content scope");
+    testXray('Function', factoryFun, new iwin.Function(), ['caller', 'arguments', 'length', 'name']);
+    var echoThis = new iwin.Function('return this;');
+    echoThis.wrappedJSObject.bind = 42;
+    var boundEchoThis = echoThis.bind(document);
+    is(boundEchoThis(), document, "bind() works correctly over Xrays");
+    is(global(boundEchoThis), window, "bound functions live in the caller's scope");
+    ok(/return this/.test(echoThis.toSource()), 'toSource works: ' + echoThis.toSource());
+    ok(/return this/.test(echoThis.toString()), 'toString works: ' + echoThis.toString());
+    is(iwin.Function.prototype, Object.getPrototypeOf(echoThis), "Function.prototype works for standard classes");
+    is(echoThis.prototype, undefined, "Function.prototype not visible for non standard constructors");
+    iwin.eval('var foopyFunction = function namedFoopyFunction(a, b, c) {}');
+    var foopyFunction = Cu.unwaiveXrays(Cu.waiveXrays(iwin).foopyFunction);
+    ok(Cu.isXrayWrapper(foopyFunction), "Should be Xrays");
+    is(foopyFunction.name, "namedFoopyFunction", ".name works over Xrays");
+    is(foopyFunction.length, 3, ".length works over Xrays");
+    ok(Object.getOwnPropertyNames(foopyFunction).indexOf('length') >= 0, "Should list length");
+    ok(Object.getOwnPropertyNames(foopyFunction).indexOf('name') >= 0, "Should list name");
+    ok(Object.getOwnPropertyNames(foopyFunction).indexOf('prototype') == -1, "Should not list prototype");
+    ok(Object.getOwnPropertyNames(iwin.Array).indexOf('prototype') >= 0, "Should list prototype for standard constructor");
 
     // Test interface objects that don't actually construct things.
     is(iwin.Math.tan(4.5), Math.tan(4.5), "Math.tan works");
     is(iwin.Math.E, Math.E, "Math.E works");
     var json = JSON.stringify({a: 2, b: 'hi', c: {d: 'there'}});
     is(global(iwin.JSON.parse(json)), iwin, "JSON rehydrated into the right context");
     is(iwin.JSON.stringify(iwin.JSON.parse(json)), json, "JSON composition identity holds");
 
@@ -159,16 +177,19 @@ https://bugzilla.mozilla.org/show_bug.cg
       "reducePar", "scanPar", "scatterPar", "filterPar", "find", "findIndex", "copyWithin",
       "fill", "@@iterator", "entries", "keys", "constructor"];
   for (var c of typedArrayClasses) {
     gPrototypeProperties[c] = ["constructor", "BYTES_PER_ELEMENT", "length", "buffer",
                                "byteLength", "byteOffset", "@@iterator", "subarray", "set"];
     if (!isReleaseBuild)
       gPrototypeProperties[c].push("move");
   }
+  gPrototypeProperties['Function'] =
+    ["constructor", "toSource", "toString", "apply", "call", "bind",
+     "isGenerator", "length", "name", "arguments", "caller"];
 
   function filterOut(array, props) {
     return array.filter(p => props.indexOf(p) == -1);
   }
 
   function testXray(classname, xray, xray2, propsToSkip) {
     propsToSkip = propsToSkip || [];
     let xrayProto = Object.getPrototypeOf(xray);
@@ -237,17 +258,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     var d = new iwin.Date();
     isnot(d.toLocaleString, Cu.unwaiveXrays(d.wrappedJSObject.toLocaleString), "Different function identities");
     is(Cu.getGlobalForObject(d.toLocaleString), window, "Xray global is correct");
     is(Cu.getGlobalForObject(d.wrappedJSObject.toLocaleString), iwin, "Underlying global is correct");
     is(d.toLocaleString('de-DE'), d.wrappedJSObject.toLocaleString('de-DE'), "Results match");
   }
 
   function testObject() {
-    testXray('Object', iwin.Object.create(new iwin.Object()), new iwin.Object(), []);
+    testXray('Object', Cu.unwaiveXrays(Cu.waiveXrays(iwin).Object.create(new iwin.Object())),
+             new iwin.Object(), []);
 
     // Construct an object full of tricky things.
     var trickyObject =
       iwin.eval('new Object({ \
                    primitiveProp: 42, objectProp: { foo: 2 }, \
                    xoProp: top.location, hasOwnProperty: 10, \
                    get getterProp() { return 2; }, \
                    set setterProp(x) { }, \
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -123,19 +123,19 @@ WrapperFactory::DoubleWrap(JSContext *cx
 // In general, we're trying to deprecate COWs incrementally as we introduce
 // Xrays to the corresponding object types. But switching off COWs for Object
 // and Array instances would be too tumultuous at present, so we punt on that
 // for later.
 static bool
 ForceCOWBehavior(JSObject *obj)
 {
     JSProtoKey key = IdentifyStandardInstanceOrPrototype(obj);
-    if (key == JSProto_Object || key == JSProto_Array) {
+    if (key == JSProto_Object || key == JSProto_Array || key == JSProto_Function) {
         MOZ_ASSERT(GetXrayType(obj) == XrayForJSObject,
-                   "We should use XrayWrappers for standard ES Object and Array "
+                   "We should use XrayWrappers for standard ES Object, Array, and Function "
                    "instances modulo this hack");
         return true;
     }
     return false;
 }
 
 JSObject *
 WrapperFactory::PrepareForWrapping(JSContext *cx, HandleObject scope,
@@ -355,23 +355,16 @@ SelectWrapper(bool securityWrapper, bool
         return &WaiveXrayWrapper::singleton;
     }
 
     // If we don't want or can't use Xrays, select a wrapper that's either
     // entirely transparent or entirely opaque.
     if (!wantXrays || xrayType == NotXray) {
         if (!securityWrapper)
             return &CrossCompartmentWrapper::singleton;
-        // In general, we don't want opaque function wrappers to be callable.
-        // But in the case of XBL, we rely on content being able to invoke
-        // functions exposed from the XBL scope. We could remove this exception,
-        // if needed, by using ExportFunction to generate the content-side
-        // representations of XBL methods.
-        else if (originIsXBLScope)
-            return &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
         return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
     }
 
     // Ok, we're using Xray. If this isn't a security wrapper, use the permissive
     // version and skip the filter.
     if (!securityWrapper) {
         if (xrayType == XrayForWrappedNative)
             return &PermissiveXrayXPCWN::singleton;
@@ -385,17 +378,25 @@ SelectWrapper(bool securityWrapper, bool
     if (xrayType == XrayForWrappedNative)
         return &FilteringWrapper<SecurityXrayXPCWN,
                                  CrossOriginAccessiblePropertiesOnly>::singleton;
     else if (xrayType == XrayForDOMObject)
         return &FilteringWrapper<SecurityXrayDOM,
                                  CrossOriginAccessiblePropertiesOnly>::singleton;
     // There's never any reason to expose pure JS objects to non-subsuming actors.
     // Just use an opaque wrapper in this case.
+    //
+    // In general, we don't want opaque function wrappers to be callable.
+    // But in the case of XBL, we rely on content being able to invoke
+    // functions exposed from the XBL scope. We could remove this exception,
+    // if needed, by using ExportFunction to generate the content-side
+    // representations of XBL methods.
     MOZ_ASSERT(xrayType == XrayForJSObject);
+    if (originIsXBLScope)
+        return &FilteringWrapper<CrossCompartmentSecurityWrapper, OpaqueWithCall>::singleton;
     return &FilteringWrapper<CrossCompartmentSecurityWrapper, Opaque>::singleton;
 }
 
 JSObject *
 WrapperFactory::Rewrap(JSContext *cx, HandleObject existing, HandleObject obj,
                        HandleObject parent, unsigned flags)
 {
     MOZ_ASSERT(!IsWrapper(obj) ||
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -66,16 +66,17 @@ static bool
 IsJSXraySupported(JSProtoKey key)
 {
     if (IsTypedArrayKey(key))
         return true;
     switch (key) {
       case JSProto_Date:
       case JSProto_Object:
       case JSProto_Array:
+      case JSProto_Function:
         return true;
       default:
         return false;
     }
 }
 
 XrayType
 GetXrayType(JSObject *obj)
@@ -166,16 +167,18 @@ public:
     ResolvingIdDummy(JSContext *cx, HandleObject wrapper, HandleId id)
     {
     }
 };
 
 class XrayTraits
 {
 public:
+    XrayTraits() {}
+
     static JSObject* getTargetObject(JSObject *wrapper) {
         return js::UncheckedUnwrap(wrapper, /* stopAtOuter = */ false);
     }
 
     virtual bool resolveNativeProperty(JSContext *cx, HandleObject wrapper,
                                        HandleObject holder, HandleId id,
                                        MutableHandle<JSPropertyDescriptor> desc) = 0;
     // NB: resolveOwnProperty may decide whether or not to cache what it finds
@@ -219,16 +222,19 @@ private:
                                       nsIPrincipal *consumerOrigin,
                                       HandleObject exclusiveGlobal);
     JSObject* getExpandoObjectInternal(JSContext *cx, HandleObject target,
                                        nsIPrincipal *origin,
                                        JSObject *exclusiveGlobal);
     JSObject* attachExpandoObject(JSContext *cx, HandleObject target,
                                   nsIPrincipal *origin,
                                   HandleObject exclusiveGlobal);
+
+    XrayTraits(XrayTraits &) MOZ_DELETE;
+    const XrayTraits& operator=(XrayTraits &) MOZ_DELETE;
 };
 
 class XPCWrappedNativeXrayTraits : public XrayTraits
 {
 public:
     enum {
         HasPrototype = 0
     };
@@ -344,26 +350,34 @@ public:
                         Handle<JSPropertyDescriptor> existingDesc, bool *defined);
 
     virtual bool enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags,
                                 AutoIdVector &props);
 
     static bool call(JSContext *cx, HandleObject wrapper,
                      const JS::CallArgs &args, js::Wrapper& baseInstance)
     {
-        // We'll handle this when we start supporting Functions.
+        JSXrayTraits &self = JSXrayTraits::singleton;
+        RootedObject holder(cx, self.ensureHolder(cx, wrapper));
+        if (self.getProtoKey(holder) == JSProto_Function)
+            return baseInstance.call(cx, wrapper, args);
+
         RootedValue v(cx, ObjectValue(*wrapper));
         js_ReportIsNotFunction(cx, v);
         return false;
     }
 
     static bool construct(JSContext *cx, HandleObject wrapper,
                           const JS::CallArgs &args, js::Wrapper& baseInstance)
     {
-        // We'll handle this when we start supporting Functions.
+        JSXrayTraits &self = JSXrayTraits::singleton;
+        RootedObject holder(cx, self.ensureHolder(cx, wrapper));
+        if (self.getProtoKey(holder) == JSProto_Function)
+            return baseInstance.construct(cx, wrapper, args);
+
         RootedValue v(cx, ObjectValue(*wrapper));
         js_ReportIsNotFunction(cx, v);
         return false;
     }
 
     static bool isResolving(JSContext *cx, JSObject *holder, jsid id)
     {
         return false;
@@ -398,29 +412,35 @@ public:
         // the target is the canonical representation of state. If it gets
         // collected, then expandos and such should be collected too. So there's
         // nothing to do here.
     }
 
     enum {
         SLOT_PROTOKEY = 0,
         SLOT_ISPROTOTYPE,
+        SLOT_CONSTRUCTOR_FOR,
         SLOT_COUNT
     };
     virtual JSObject* createHolder(JSContext *cx, JSObject *wrapper) MOZ_OVERRIDE;
 
     static JSProtoKey getProtoKey(JSObject *holder) {
         int32_t key = js::GetReservedSlot(holder, SLOT_PROTOKEY).toInt32();
         return static_cast<JSProtoKey>(key);
     }
 
     static bool isPrototype(JSObject *holder) {
         return js::GetReservedSlot(holder, SLOT_ISPROTOTYPE).toBoolean();
     }
 
+    static JSProtoKey constructorFor(JSObject *holder) {
+        int32_t key = js::GetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR).toInt32();
+        return static_cast<JSProtoKey>(key);
+    }
+
     static bool getOwnPropertyFromTargetIfSafe(JSContext *cx,
                                                HandleObject target,
                                                HandleObject wrapper,
                                                HandleId id,
                                                MutableHandle<JSPropertyDescriptor> desc);
 
     static const JSClass HolderClass;
     static JSXrayTraits singleton;
@@ -547,16 +567,42 @@ JSXrayTraits::resolveOwnProperty(JSConte
             return JS_WrapPropertyDescriptor(cx, desc);
         } else if (IsTypedArrayKey(key)) {
             if (IsArrayIndex(GetArrayIndexFromId(cx, id))) {
                 JS_ReportError(cx, "Accessing TypedArray data over Xrays is slow, and forbidden "
                                    "in order to encourage performant code. To copy TypedArrays "
                                    "across origin boundaries, consider using Components.utils.cloneInto().");
                 return false;
             }
+        } else if (key == JSProto_Function) {
+            if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_LENGTH)) {
+                FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY,
+                                       NumberValue(JS_GetFunctionArity(JS_GetObjectFunction(target))));
+                return true;
+            } else if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_NAME)) {
+                FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY,
+                                       StringValue(JS_GetFunctionId(JS_GetObjectFunction(target))));
+            } else if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_PROTOTYPE)) {
+                // Handle the 'prototype' property to make xrayedGlobal.StandardClass.prototype work.
+                JSProtoKey standardConstructor = constructorFor(holder);
+                if (standardConstructor != JSProto_Null) {
+                    RootedObject standardProto(cx);
+                    {
+                        JSAutoCompartment ac(cx, target);
+                        if (!JS_GetClassPrototype(cx, standardConstructor, &standardProto))
+                            return false;
+                        MOZ_ASSERT(standardProto);
+                    }
+                    if (!JS_WrapObject(cx, &standardProto))
+                        return false;
+                    FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY,
+                                           ObjectValue(*standardProto));
+                    return true;
+                }
+            }
         }
 
         // The rest of this function applies only to prototypes.
         return true;
     }
 
     // The non-HasPrototypes semantics implemented by traditional Xrays are kind
     // of broken with respect to |own|-ness and the holder. The common code
@@ -800,16 +846,26 @@ JSXrayTraits::enumerateNames(JSContext *
         } else if (IsTypedArrayKey(key)) {
             uint32_t length = JS_GetTypedArrayLength(target);
             // TypedArrays enumerate every indexed property in range, but
             // |length| is a getter that lives on the proto, like it should be.
             if (!props.reserve(length))
                 return false;
             for (int32_t i = 0; i <= int32_t(length - 1); ++i)
                 props.infallibleAppend(INT_TO_JSID(i));
+        } else if (key == JSProto_Function) {
+            if (!props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_LENGTH)))
+                return false;
+            if (!props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_NAME)))
+                return false;
+            // Handle the .prototype property on standard constructors.
+            if (constructorFor(holder) != JSProto_Null) {
+                if (!props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_PROTOTYPE)))
+                    return false;
+            }
         }
 
         // The rest of this function applies only to prototypes.
         return true;
     }
 
     // Grab the JSClass. We require all Xrayable classes to have a ClassSpec.
     const js::Class *clasp = js::GetObjectClass(target);
@@ -864,16 +920,23 @@ JSXrayTraits::createHolder(JSContext *cx
 
     // Store it on the holder.
     RootedValue v(cx);
     v.setNumber(static_cast<uint32_t>(key));
     js::SetReservedSlot(holder, SLOT_PROTOKEY, v);
     v.setBoolean(isPrototype);
     js::SetReservedSlot(holder, SLOT_ISPROTOTYPE, v);
 
+    // If this is a function, also compute whether it serves as a constructor
+    // for a standard class.
+    if (key == JSProto_Function) {
+        v.setNumber(static_cast<uint32_t>(IdentifyStandardConstructor(target)));
+        js::SetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR, v);
+    }
+
     return holder;
 }
 
 XPCWrappedNativeXrayTraits XPCWrappedNativeXrayTraits::singleton;
 DOMXrayTraits DOMXrayTraits::singleton;
 JSXrayTraits JSXrayTraits::singleton;
 
 XrayTraits*
--- a/layout/tools/reftest/b2g_desktop.py
+++ b/layout/tools/reftest/b2g_desktop.py
@@ -17,26 +17,29 @@ from marionette import Marionette
 from mozprocess import ProcessHandler
 from mozrunner import FirefoxRunner
 import mozinfo
 import mozlog
 
 log = mozlog.getLogger('REFTEST')
 
 class B2GDesktopReftest(RefTest):
-    def __init__(self, marionette):
+    marionette = None
+
+    def __init__(self, marionette_args):
         RefTest.__init__(self)
         self.last_test = os.path.basename(__file__)
-        self.marionette = marionette
+        self.marionette_args = marionette_args
         self.profile = None
         self.runner = None
         self.test_script = os.path.join(here, 'b2g_start_script.js')
         self.timeout = None
 
     def run_marionette_script(self):
+        self.marionette = Marionette(**self.marionette_args)
         assert(self.marionette.wait_for_port())
         self.marionette.start_session()
         self.marionette.set_context(self.marionette.CONTEXT_CHROME)
 
         if os.path.isfile(self.test_script):
             f = open(self.test_script, 'r')
             self.test_script = f.read()
             f.close()
@@ -66,18 +69,18 @@ class B2GDesktopReftest(RefTest):
         cmd, args = self.build_command_line(options.app,
                             ignore_window_size=options.ignoreWindowSize,
                             browser_arg=options.browser_arg)
         self.runner = FirefoxRunner(profile=self.profile,
                                     binary=cmd,
                                     cmdargs=args,
                                     env=env,
                                     process_class=ProcessHandler,
-                                    symbols_path=options.symbolsPath,
-                                    kp_kwargs=kp_kwargs)
+                                    process_args=kp_kwargs,
+                                    symbols_path=options.symbolsPath)
 
         status = 0
         try:
             self.runner.start(outputTimeout=self.timeout)
             log.info("%s | Application pid: %d",
                      os.path.basename(__file__),
                      self.runner.process_handler.pid)
 
@@ -146,24 +149,23 @@ class B2GDesktopReftest(RefTest):
         msg = "%s | application timed out after %s seconds with no output"
         log.testFail(msg % (self.last_test, self.timeout))
 
         # kill process to get a stack
         self.runner.stop(sig=signal.SIGABRT)
 
 
 def run_desktop_reftests(parser, options, args):
-    kwargs = {}
+    marionette_args = {}
     if options.marionette:
         host, port = options.marionette.split(':')
-        kwargs['host'] = host
-        kwargs['port'] = int(port)
-    marionette = Marionette.getMarionetteOrExit(**kwargs)
+        marionette_args['host'] = host
+        marionette_args['port'] = int(port)
 
-    reftest = B2GDesktopReftest(marionette)
+    reftest = B2GDesktopReftest(marionette_args)
 
     options = ReftestOptions.verifyCommonOptions(parser, options, reftest)
     if options == None:
         sys.exit(1)
 
     # add a -bin suffix if b2g-bin exists, but just b2g was specified
     if options.app[-4:] != '-bin':
         if os.path.isfile("%s-bin" % options.app):
--- a/layout/tools/reftest/mach_commands.py
+++ b/layout/tools/reftest/mach_commands.py
@@ -196,17 +196,17 @@ class ReftestRunner(MozbuildObject):
 
         try:
             which.which('adb')
         except which.WhichError:
             # TODO Find adb automatically if it isn't on the path
             raise Exception(ADB_NOT_FOUND % ('%s-remote' % suite, b2g_home))
 
         options.b2gPath = b2g_home
-        options.logcat_dir = self.reftest_dir
+        options.logdir = self.reftest_dir
         options.httpdPath = os.path.join(self.topsrcdir, 'netwerk', 'test', 'httpserver')
         options.xrePath = xre_path
         options.ignoreWindowSize = True
 
         # Don't enable oop for crashtest until they run oop in automation
         if suite == 'reftest':
             options.oop = True
 
@@ -330,19 +330,19 @@ def ReftestCommand(func):
 
 def B2GCommand(func):
     """Decorator that adds shared command arguments to b2g mochitest commands."""
 
     busybox = CommandArgument('--busybox', default=None,
         help='Path to busybox binary to install on device')
     func = busybox(func)
 
-    logcatdir = CommandArgument('--logcat-dir', default=None,
-        help='directory to store logcat dump files')
-    func = logcatdir(func)
+    logdir = CommandArgument('--logdir', default=None,
+        help='directory to store log files')
+    func = logdir(func)
 
     sdcard = CommandArgument('--sdcard', default="10MB",
         help='Define size of sdcard: 1MB, 50MB...etc')
     func = sdcard(func)
 
     emulator_res = CommandArgument('--emulator-res', default='800x1000',
         help='Emulator resolution of the format \'<width>x<height>\'')
     func = emulator_res(func)
--- a/layout/tools/reftest/runreftestb2g.py
+++ b/layout/tools/reftest/runreftestb2g.py
@@ -53,19 +53,19 @@ class B2GOptions(ReftestOptions):
         defaults["emulator_res"] = None
 
         self.add_option("--no-window", action="store_true",
                     dest = "noWindow",
                     help = "Pass --no-window to the emulator")
         defaults["noWindow"] = False
 
         self.add_option("--adbpath", action="store",
-                    type = "string", dest = "adbPath",
+                    type = "string", dest = "adb_path",
                     help = "path to adb")
-        defaults["adbPath"] = "adb"
+        defaults["adb_path"] = "adb"
 
         self.add_option("--deviceIP", action="store",
                     type = "string", dest = "deviceIP",
                     help = "ip address of remote device to test")
         defaults["deviceIP"] = None
 
         self.add_option("--devicePort", action="store",
                     type = "string", dest = "devicePort",
@@ -96,20 +96,20 @@ class B2GOptions(ReftestOptions):
                     type = "string", dest = "pidFile",
                     help = "name of the pidfile to generate")
         defaults["pidFile"] = ""
         self.add_option("--gecko-path", action="store",
                         type="string", dest="geckoPath",
                         help="the path to a gecko distribution that should "
                         "be installed on the emulator prior to test")
         defaults["geckoPath"] = None
-        self.add_option("--logcat-dir", action="store",
-                        type="string", dest="logcat_dir",
-                        help="directory to store logcat dump files")
-        defaults["logcat_dir"] = None
+        self.add_option("--logdir", action="store",
+                        type="string", dest="logdir",
+                        help="directory to store log files")
+        defaults["logdir"] = None
         self.add_option('--busybox', action='store',
                         type='string', dest='busybox',
                         help="Path to busybox binary to install on device")
         defaults['busybox'] = None
         self.add_option("--httpd-path", action = "store",
                     type = "string", dest = "httpdPath",
                     help = "path to the httpd.js file")
         defaults["httpdPath"] = None
@@ -161,18 +161,18 @@ class B2GOptions(ReftestOptions):
             options.httpPort = auto.DEFAULT_HTTP_PORT
 
         if not options.sslPort:
             options.sslPort = auto.DEFAULT_SSL_PORT
 
         if options.geckoPath and not options.emulator:
             self.error("You must specify --emulator if you specify --gecko-path")
 
-        if options.logcat_dir and not options.emulator:
-            self.error("You must specify --emulator if you specify --logcat-dir")
+        if options.logdir and not options.emulator:
+            self.error("You must specify --emulator if you specify --logdir")
 
         #if not options.emulator and not options.deviceIP:
         #    print "ERROR: you must provide a device IP"
         #    return None
 
         if options.remoteLogFile == None:
             options.remoteLogFile = "reftest.log"
 
@@ -492,43 +492,45 @@ def run_remote_reftests(parser, options,
     kwargs = {}
     if options.emulator:
         kwargs['emulator'] = options.emulator
         auto.setEmulator(True)
         if options.noWindow:
             kwargs['noWindow'] = True
         if options.geckoPath:
             kwargs['gecko_path'] = options.geckoPath
-        if options.logcat_dir:
-            kwargs['logcat_dir'] = options.logcat_dir
+        if options.logdir:
+            kwargs['logdir'] = options.logdir
         if options.busybox:
             kwargs['busybox'] = options.busybox
         if options.symbolsPath:
             kwargs['symbols_path'] = options.symbolsPath
     if options.emulator_res:
         kwargs['emulator_res'] = options.emulator_res
     if options.b2gPath:
         kwargs['homedir'] = options.b2gPath
     if options.marionette:
         host,port = options.marionette.split(':')
         kwargs['host'] = host
         kwargs['port'] = int(port)
-    marionette = Marionette.getMarionetteOrExit(**kwargs)
+    if options.adb_path:
+        kwargs['adb_path'] = options.adb_path
+    marionette = Marionette(**kwargs)
     auto.marionette = marionette
 
     if options.emulator:
         dm = marionette.emulator.dm
     else:
         # create the DeviceManager
-        kwargs = {'adbPath': options.adbPath,
+        kwargs = {'adbPath': options.adb_path,
                   'deviceRoot': options.remoteTestRoot}
         if options.deviceIP:
             kwargs.update({'host': options.deviceIP,
                            'port': options.devicePort})
-        dm = DeviagerADB(**kwargs)
+        dm = DeviceManagerADB(**kwargs)
     auto.setDeviceManager(dm)
 
     options = parser.verifyRemoteOptions(options, auto)
 
     if (options == None):
         print "ERROR: Invalid options specified, use --help for a list of valid options"
         sys.exit(1)
 
--- a/media/libstagefright/moz.build
+++ b/media/libstagefright/moz.build
@@ -105,16 +105,17 @@ if CONFIG['_MSC_VER']:
     ]
     CXXFLAGS += [
         '-wd4018', # '<' : signed/unsigned mismatch
         '-wd4275', # non dll-interface class used as base for dll-interface class
         '-wd4305', # '=' : truncation from 'double' to 'float'
         '-wd4309', # '=' : truncation of constant value
         '-wd4355', # 'this' : used in base member initializer list
         '-wd4804', # '>' : unsafe use of type 'bool' in operation
+        '-wd4099', # mismatched class/struct tags
     ]
 elif CONFIG['GNU_CXX']:
     CFLAGS += [
         '-Wno-comment',
         '-Wno-declaration-after-statement',
         '-Wno-sign-compare'
     ]
     CXXFLAGS += [
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -2701,30 +2701,34 @@ public class GeckoAppShell
     }
 
     @WrapElementForJNI(allowMultithread = true)
     static InputStream createInputStream(URLConnection connection) throws IOException {
         return connection.getInputStream();
     }
 
     @WrapElementForJNI(allowMultithread = true, narrowChars = true)
-    static URLConnection getConnection(String url) throws MalformedURLException, IOException {
-        String spec;
-        if (url.startsWith("android://")) {
-            spec = url.substring(10);
-        } else {
-            spec = url.substring(8);
+    static URLConnection getConnection(String url) {
+        try {
+            String spec;
+            if (url.startsWith("android://")) {
+                spec = url.substring(10);
+            } else {
+                spec = url.substring(8);
+            }
+
+            // if the colon got stripped, put it back
+            int colon = spec.indexOf(':');
+            if (colon == -1 || colon > spec.indexOf('/')) {
+                spec = spec.replaceFirst("/", ":/");
+            }
+        } catch(Exception ex) {
+            return null;
         }
-
-        // if the colon got stripped, put it back
-        int colon = spec.indexOf(':');
-        if (colon == -1 || colon > spec.indexOf('/')) {
-            spec = spec.replaceFirst("/", ":/");
-        }
-        return new URL(spec).openConnection();
+        return null;
     }
 
     @WrapElementForJNI(allowMultithread = true, narrowChars = true)
     static String connectionGetMimeType(URLConnection connection) {
         return connection.getContentType();
     }
 
 }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -251,16 +251,17 @@ size. -->
 <!ENTITY save_as_pdf "Save as PDF">
 <!ENTITY find_in_page "Find in Page">
 <!ENTITY desktop_mode "Request Desktop Site">
 <!ENTITY page "Page">
 <!ENTITY tools "Tools">
 <!ENTITY new_tab "New Tab">
 <!ENTITY new_private_tab "New Private Tab">
 <!ENTITY close_all_tabs "Close All Tabs">
+<!ENTITY close_private_tabs "Close Private Tabs">
 <!ENTITY tabs_normal "Tabs">
 <!ENTITY tabs_private "Private">
 <!ENTITY tabs_synced "Synced">
 <!ENTITY set_image_fail "Unable to set image">
 <!ENTITY set_image_chooser_title "Set Image As">
 
 <!-- Localization note (find_text, find_prev, find_next, find_close) : These strings are used
      as alternate text for accessibility. They are not visible in the UI. -->
--- a/mobile/android/base/menu/MenuPopup.java
+++ b/mobile/android/base/menu/MenuPopup.java
@@ -15,28 +15,30 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
 
 /**
  * A popup to show the inflated MenuPanel.
  */
 public class MenuPopup extends PopupWindow {
-    private LinearLayout mPanel;
+    private final LinearLayout mPanel;
 
-    private int mYOffset;
-    private int mPopupWidth;
+    private final int mYOffset;
+    private final int mPopupWidth;
+    private final int mPopupMinHeight;
 
     public MenuPopup(Context context) {
         super(context);
 
         setFocusable(true);
 
         mYOffset = context.getResources().getDimensionPixelSize(R.dimen.menu_popup_offset);
         mPopupWidth = context.getResources().getDimensionPixelSize(R.dimen.menu_popup_width);
+        mPopupMinHeight = context.getResources().getDimensionPixelSize(R.dimen.menu_item_row_height);
 
         // Setting a null background makes the popup to not close on touching outside.
         setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
         setWindowLayoutMode(View.MeasureSpec.makeMeasureSpec(mPopupWidth, View.MeasureSpec.AT_MOST),
                             ViewGroup.LayoutParams.WRAP_CONTENT);
 
         LayoutInflater inflater = LayoutInflater.from(context);
         mPanel = (LinearLayout) inflater.inflate(R.layout.menu_popup, null);
@@ -58,11 +60,17 @@ public class MenuPopup extends PopupWind
         mPanel.addView(view);
     }
 
     /**
      * A small little offset.
      */
     @Override
     public void showAsDropDown(View anchor) {
-        showAsDropDown(anchor, 0, -mYOffset);
+        // Set a height, so that the popup will not be displayed below the bottom of the screen.
+        setHeight(mPopupMinHeight);
+
+        // Attempt to align the center of the popup with the center of the anchor. If the anchor is
+        // near the edge of the screen, the popup will just align with the edge of the screen.
+        final int xOffset = anchor.getWidth()/2 - mPopupWidth/2;
+        showAsDropDown(anchor, xOffset, -mYOffset);
     }
 }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..af08dc45169a1e4e9a7184783882eae006c0d70b
GIT binary patch
literal 136
zc%17D@N?(olHy`uVBq!ia0vp^Dj>|r3?y%#<GKx`SkfJR9T^xl_H+M9WCijWi-X*q
z7}lMWc?smO1^9%x0_p$%|1Z5c|1OZlR1)MD%rNu6!?PPeo{*=DV+hCf<b(q(XX-ta
c*%xy%h))xL`TUOTB%myVr>mdKI;Vst0O8#xv;Y7A
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..af08dc45169a1e4e9a7184783882eae006c0d70b
GIT binary patch
literal 136
zc%17D@N?(olHy`uVBq!ia0vp^Dj>|r3?y%#<GKx`SkfJR9T^xl_H+M9WCijWi-X*q
z7}lMWc?smO1^9%x0_p$%|1Z5c|1OZlR1)MD%rNu6!?PPeo{*=DV+hCf<b(q(XX-ta
c*%xy%h))xL`TUOTB%myVr>mdKI;Vst0O8#xv;Y7A
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b0210e7cf2c17d485427671a7139536c115f2105
GIT binary patch
literal 136
zc%17D@N?(olHy`uVBq!ia0vp^5+KaT3?y&uT)!Jgv7|ftIx;Y9?C1WI$O_~$76-XI
zF|0c$^AgBm3-AeX1=9cj|6h7@{#_u8sU*lRm|^CBhi5l{JRwgP#}JO|$q5aOQaUq&
cEZNu?vbVFlaul6g43uT?boFyt=akR{0Of-vjsO4v
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6fb5853084488393253ce3ade465eca2a33c79bf
GIT binary patch
literal 138
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDH3?y^UWFG-iEa{HEjtmSN`?>!lvI6;x#X;^)
z4C~IxyaaOC0(?STf%O0X|CipJe;3GNDhcunW|;Zk;n@u!PsG#3F+}2Wa>4<=C-ni%
eN=!V)-3<N{6*jA#xMv5HXYh3Ob6Mw<&;$S~d?yzG
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b0210e7cf2c17d485427671a7139536c115f2105
GIT binary patch
literal 136
zc%17D@N?(olHy`uVBq!ia0vp^5+KaT3?y&uT)!Jgv7|ftIx;Y9?C1WI$O_~$76-XI
zF|0c$^AgBm3-AeX1=9cj|6h7@{#_u8sU*lRm|^CBhi5l{JRwgP#}JO|$q5aOQaUq&
cEZNu?vbVFlaul6g43uT?boFyt=akR{0Of-vjsO4v
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6fb5853084488393253ce3ade465eca2a33c79bf
GIT binary patch
literal 138
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDH3?y^UWFG-iEa{HEjtmSN`?>!lvI6;x#X;^)
z4C~IxyaaOC0(?STf%O0X|CipJe;3GNDhcunW|;Zk;n@u!PsG#3F+}2Wa>4<=C-ni%
eN=!V)-3<N{6*jA#xMv5HXYh3Ob6Mw<&;$S~d?yzG
--- a/mobile/android/base/resources/layout-large-land-v11/tabs_panel_footer.xml
+++ b/mobile/android/base/resources/layout-large-land-v11/tabs_panel_footer.xml
@@ -1,17 +1,29 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <ImageButton android:id="@+id/add_tab"
-                 android:layout_width="fill_parent"
+                 android:layout_width="@dimen/browser_toolbar_height"
                  android:layout_height="@dimen/browser_toolbar_height"
                  android:padding="14dip"
                  android:src="@drawable/tab_new_level"
                  android:contentDescription="@string/new_tab"
-                 android:background="@drawable/action_bar_button_inverse"
-                 android:gravity="center"/>
+                 android:background="@drawable/action_bar_button_inverse"/>
+
+    <View android:layout_width="0dip"
+          android:layout_height="match_parent"
+          android:layout_weight="1.0"/>
+
+    <ImageButton android:id="@+id/menu"
+                 style="@style/UrlBar.ImageButton"
+                 android:layout_width="@dimen/browser_toolbar_height"
+                 android:layout_height="@dimen/browser_toolbar_height"
+                 android:padding="@dimen/browser_toolbar_button_padding"
+                 android:src="@drawable/menu_tabs"
+                 android:contentDescription="@string/menu"
+                 android:background="@drawable/action_bar_button"/>
 
 </merge>
--- a/mobile/android/base/resources/layout/tabs_panel_header.xml
+++ b/mobile/android/base/resources/layout/tabs_panel_header.xml
@@ -20,9 +20,18 @@
                  style="@style/UrlBar.ImageButton"
                  android:layout_width="@dimen/browser_toolbar_height"
                  android:layout_height="@dimen/browser_toolbar_height"
                  android:padding="@dimen/browser_toolbar_button_padding"
                  android:src="@drawable/tab_new_level"
                  android:contentDescription="@string/new_tab"
                  android:background="@drawable/action_bar_button_inverse"/>
 
+    <ImageButton android:id="@+id/menu"
+                 style="@style/UrlBar.ImageButton"
+                 android:layout_width="@dimen/browser_toolbar_height"
+                 android:layout_height="@dimen/browser_toolbar_height"
+                 android:padding="@dimen/browser_toolbar_button_padding"
+                 android:src="@drawable/menu_tabs"
+                 android:contentDescription="@string/menu"
+                 android:background="@drawable/action_bar_button"/>
+
 </merge>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/menu-v11/tabs_menu.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:id="@+id/new_tab"
+          android:title="@string/new_tab"/>
+
+    <item android:id="@+id/new_private_tab"
+          android:title="@string/new_private_tab"/>
+
+    <item android:id="@+id/close_all_tabs"
+          android:title="@string/close_all_tabs"/>
+
+    <item android:id="@+id/close_private_tabs"
+          android:title="@string/close_private_tabs"/>
+
+</menu>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/menu/tabs_menu.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:id="@+id/new_tab"
+          android:title="@string/new_tab"/>
+
+    <item android:id="@+id/new_private_tab"
+          android:title="@string/new_private_tab"/>
+
+    <item android:id="@+id/close_all_tabs"
+          android:title="@string/close_all_tabs"/>
+
+    <item android:id="@+id/close_private_tabs"
+          android:title="@string/close_private_tabs"/>
+
+</menu>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -241,16 +241,17 @@
   <string name="num_tabs">&num_tabs2;</string>
   <string name="addons">&addons;</string>
   <string name="downloads">&downloads;</string>
   <string name="apps">&apps;</string>
   <string name="char_encoding">&char_encoding;</string>
   <string name="new_tab">&new_tab;</string>
   <string name="new_private_tab">&new_private_tab;</string>
   <string name="close_all_tabs">&close_all_tabs;</string>
+  <string name="close_private_tabs">&close_private_tabs;</string>
   <string name="tabs_normal">&tabs_normal;</string>
   <string name="tabs_private">&tabs_private;</string>
   <string name="tabs_synced">&tabs_synced;</string>
   <string name="edit_mode_cancel">&edit_mode_cancel;</string>
 
   <string name="site_settings_title">&site_settings_title3;</string>
   <string name="site_settings_cancel">&site_settings_cancel;</string>
   <string name="site_settings_clear">&site_settings_clear;</string>
--- a/mobile/android/base/tabspanel/TabsPanel.java
+++ b/mobile/android/base/tabspanel/TabsPanel.java
@@ -8,36 +8,43 @@ package org.mozilla.gecko.tabspanel;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoAppShell.AppStateListener;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.LightweightTheme;
 import org.mozilla.gecko.LightweightThemeDrawable;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
+import org.mozilla.gecko.widget.GeckoPopupMenu;
 import org.mozilla.gecko.widget.IconTabWidget;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.widget.Button;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 
 public class TabsPanel extends LinearLayout
-                       implements LightweightTheme.OnChangeListener,
+                       implements GeckoPopupMenu.OnMenuItemClickListener,
+                                  LightweightTheme.OnChangeListener,
                                   IconTabWidget.OnTabChangedListener {
     @SuppressWarnings("unused")
     private static final String LOGTAG = "Gecko" + TabsPanel.class.getSimpleName();
 
     public static enum Panel {
         NORMAL_TABS,
         PRIVATE_TABS,
         REMOTE_TABS
@@ -45,16 +52,20 @@ public class TabsPanel extends LinearLay
 
     public static interface PanelView {
         public void setTabsPanel(TabsPanel panel);
         public void show();
         public void hide();
         public boolean shouldExpand();
     }
 
+    public static interface CloseAllPanelView {
+        public void closeAll();
+    }
+
     public static interface TabsLayoutChangeListener {
         public void onTabsLayoutChange(int width, int height);
     }
 
     private Context mContext;
     private final GeckoApp mActivity;
     private final LightweightTheme mTheme;
     private RelativeLayout mHeader;
@@ -63,39 +74,46 @@ public class TabsPanel extends LinearLay
     private PanelView mPanelNormal;
     private PanelView mPanelPrivate;
     private PanelView mPanelRemote;
     private RelativeLayout mFooter;
     private TabsLayoutChangeListener mLayoutChangeListener;
     private AppStateListener mAppStateListener;
 
     private IconTabWidget mTabWidget;
+    private static ImageButton mMenuButton;
     private static ImageButton mAddTab;
 
     private Panel mCurrentPanel;
     private boolean mIsSideBar;
     private boolean mVisible;
     private boolean mHeaderVisible;
 
+    private GeckoPopupMenu mPopupMenu;
+
     public TabsPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
         mActivity = (GeckoApp) context;
         mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
 
         setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                                                       LinearLayout.LayoutParams.MATCH_PARENT));
         setOrientation(LinearLayout.VERTICAL);
 
         mCurrentPanel = Panel.NORMAL_TABS;
         mVisible = false;
         mHeaderVisible = false;
 
         mIsSideBar = false;
 
+        mPopupMenu = new GeckoPopupMenu(context);
+        mPopupMenu.inflate(R.menu.tabs_menu);
+        mPopupMenu.setOnMenuItemClickListener(this);
+
         LayoutInflater.from(context).inflate(R.layout.tabs_panel, this);
         initialize();
 
         mAppStateListener = new AppStateListener() {
             @Override
             public void onResume() {
                 if (mPanel == mPanelRemote) {
                     // Refresh the remote panel.
@@ -144,39 +162,91 @@ public class TabsPanel extends LinearLay
         if (!GeckoProfile.get(mContext).inGuestMode()) {
             // The initial icon is not the animated icon, because on Android
             // 4.4.2, the animation starts immediately (and can start at other
             // unpredictable times). See Bug 1015974.
             mTabWidget.addTab(R.drawable.tabs_synced, R.string.tabs_synced);
         }
 
         mTabWidget.setTabSelectionListener(this);
+
+        mMenuButton = (ImageButton) findViewById(R.id.menu);
+        mMenuButton.setOnClickListener(new Button.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                final Menu menu = mPopupMenu.getMenu();
+                menu.findItem(R.id.close_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS);
+                menu.findItem(R.id.close_private_tabs).setVisible(mCurrentPanel == Panel.PRIVATE_TABS);
+
+                mPopupMenu.show();
+            }
+        });
+        mPopupMenu.setAnchor(mMenuButton);
     }
 
-    public void addTab() {
+    private void addTab() {
         if (mCurrentPanel == Panel.NORMAL_TABS) {
-           mActivity.addTab();
+            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "new_tab");
+            mActivity.addTab();
         } else {
-           mActivity.addPrivateTab();
+            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "new_private_tab");
+            mActivity.addPrivateTab();
         }
 
         mActivity.autoHideTabs();
     }
 
     @Override
     public void onTabChanged(int index) {
         if (index == 0) {
             show(Panel.NORMAL_TABS);
         } else if (index == 1) {
             show(Panel.PRIVATE_TABS);
         } else {
             show(Panel.REMOTE_TABS);
         }
     }
 
+    @Override
+    public boolean onMenuItemClick(MenuItem item) {
+        final int itemId = item.getItemId();
+
+        if (itemId == R.id.close_all_tabs) {
+            if (mCurrentPanel == Panel.NORMAL_TABS) {
+                final String extras = getResources().getResourceEntryName(itemId);
+                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, extras);
+
+                // Disable the menu button so that the menu won't interfere with the tab close animation.
+                mMenuButton.setEnabled(false);
+                ((CloseAllPanelView) mPanelNormal).closeAll();
+            } else {
+                Log.e(LOGTAG, "Close all tabs menu item should only be visible for normal tabs panel");
+            }
+            return true;
+        }
+
+        if (itemId == R.id.close_private_tabs) {
+            if (mCurrentPanel == Panel.PRIVATE_TABS) {
+                final String extras = getResources().getResourceEntryName(itemId);
+                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.MENU, extras);
+
+                ((CloseAllPanelView) mPanelPrivate).closeAll();
+            } else {
+                Log.e(LOGTAG, "Close private tabs menu item should only be visible for private tabs panel");
+            }
+            return true;
+        }
+
+        if (itemId == R.id.new_tab || itemId == R.id.new_private_tab) {
+            hide();
+        }
+
+        return mActivity.onOptionsItemSelected(item);
+    }
+
     private static int getTabContainerHeight(TabsListContainer listContainer) {
         Resources resources = listContainer.getContext().getResources();
 
         PanelView panelView = listContainer.getCurrentPanelView();
         if (panelView != null && !panelView.shouldExpand()) {
             return resources.getDimensionPixelSize(R.dimen.tabs_tray_horizontal_height);
         }
 
@@ -345,22 +415,27 @@ public class TabsPanel extends LinearLay
         }
         mPanel.show();
 
         if (mCurrentPanel == Panel.REMOTE_TABS) {
             if (mFooter != null)
                 mFooter.setVisibility(View.GONE);
 
             mAddTab.setVisibility(View.INVISIBLE);
+
+            mMenuButton.setVisibility(View.INVISIBLE);
         } else {
             if (mFooter != null)
                 mFooter.setVisibility(View.VISIBLE);
 
             mAddTab.setVisibility(View.VISIBLE);
             mAddTab.setImageLevel(index);
+
+            mMenuButton.setVisibility(View.VISIBLE);
+            mMenuButton.setEnabled(true);
         }
 
         if (isSideBar()) {
             if (showAnimation)
                 dispatchLayoutChange(getWidth(), getHeight());
         } else {
             int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height);
             int height = actionBarHeight + getTabContainerHeight(mTabsContainer);
@@ -369,16 +444,17 @@ public class TabsPanel extends LinearLay
         mHeaderVisible = true;
     }
 
     public void hide() {
         mHeaderVisible = false;
 
         if (mVisible) {
             mVisible = false;
+            mPopupMenu.dismiss();
             dispatchLayoutChange(0, 0);
         }
     }
 
     public void refresh() {
         removeAllViews();
 
         LayoutInflater.from(mContext).inflate(R.layout.tabs_panel, this);
--- a/mobile/android/base/tabspanel/TabsTray.java
+++ b/mobile/android/base/tabspanel/TabsTray.java
@@ -12,16 +12,17 @@ import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.Property;
 import org.mozilla.gecko.animation.ViewHelper;
+import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.TwoWayView;
 import org.mozilla.gecko.widget.TabThumbnailWrapper;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
@@ -33,48 +34,54 @@ import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 class TabsTray extends TwoWayView
-    implements TabsPanel.PanelView {
+               implements TabsPanel.PanelView,
+                          TabsPanel.CloseAllPanelView {
     private static final String LOGTAG = "Gecko" + TabsTray.class.getSimpleName();
 
     private Context mContext;
     private TabsPanel mTabsPanel;
 
+    final private boolean mIsPrivate;
+
     private TabsAdapter mTabsAdapter;
 
     private List<View> mPendingClosedTabs;
-    private int mCloseAnimationCount;
+    private int mCloseAnimationCount = 0;
+    private int mCloseAllAnimationCount = 0;
 
     private TabSwipeGestureListener mSwipeListener;
 
     // Time to animate non-flinged tabs of screen, in milliseconds
     private static final int ANIMATION_DURATION = 250;
 
+    // Time between starting successive tab animations in closeAllTabs.
+    private static final int ANIMATION_CASCADE_DELAY = 75;
+
     private int mOriginalSize = 0;
 
     public TabsTray(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
 
-        mCloseAnimationCount = 0;
         mPendingClosedTabs = new ArrayList<View>();
 
         setItemsCanFocus(true);
 
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsTray);
-        boolean isPrivate = (a.getInt(R.styleable.TabsTray_tabs, 0x0) == 1);
+        mIsPrivate = (a.getInt(R.styleable.TabsTray_tabs, 0x0) == 1);
         a.recycle();
 
-        mTabsAdapter = new TabsAdapter(mContext, isPrivate);
+        mTabsAdapter = new TabsAdapter(mContext);
         setAdapter(mTabsAdapter);
 
         mSwipeListener = new TabSwipeGestureListener();
         setOnTouchListener(mSwipeListener);
         setOnScrollListener(mSwipeListener.makeScrollListener());
 
         setRecyclerListener(new RecyclerListener() {
             @Override
@@ -132,25 +139,23 @@ class TabsTray extends TwoWayView
             close = (ImageButton) view.findViewById(R.id.close);
             thumbnailWrapper = (TabThumbnailWrapper) view.findViewById(R.id.wrapper);
         }
     }
 
     // Adapter to bind tabs into a list
     private class TabsAdapter extends BaseAdapter implements Tabs.OnTabsChangedListener {
         private Context mContext;
-        private boolean mIsPrivate;
         private ArrayList<Tab> mTabs;
         private LayoutInflater mInflater;
         private Button.OnClickListener mOnCloseClickListener;
 
-        public TabsAdapter(Context context, boolean isPrivate) {
+        public TabsAdapter(Context context) {
             mContext = context;
             mInflater = LayoutInflater.from(mContext);
-            mIsPrivate = isPrivate;
 
             mOnCloseClickListener = new Button.OnClickListener() {
                 @Override
                 public void onClick(View v) {
                     TabRow tab = (TabRow) v.getTag();
                     final int pos = (isVertical() ? tab.info.getWidth() : 0 - tab.info.getHeight());
                     animateClose(tab.info, pos);
                 }
@@ -276,26 +281,31 @@ class TabsTray extends TwoWayView
                 row.thumbnailWrapper.setRecording(tab.isRecording());
             }
             row.title.setText(tab.getDisplayTitle());
             row.close.setTag(row);
         }
 
         private void resetTransforms(View view) {
             ViewHelper.setAlpha(view, 1);
-            if (mOriginalSize == 0)
-                return;
 
             if (isVertical()) {
-                ViewHelper.setHeight(view, mOriginalSize);
                 ViewHelper.setTranslationX(view, 0);
             } else {
-                ViewHelper.setWidth(view, mOriginalSize);
                 ViewHelper.setTranslationY(view, 0);
             }
+
+            // We only need to reset the height or width after individual tab close animations.
+            if (mOriginalSize != 0) {
+                if (isVertical()) {
+                    ViewHelper.setHeight(view, mOriginalSize);
+                } else {
+                    ViewHelper.setWidth(view, mOriginalSize);
+                }
+            }
         }
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
             TabRow row;
 
             if (convertView == null) {
                 convertView = mInflater.inflate(R.layout.tabs_row, null);
@@ -315,16 +325,85 @@ class TabsTray extends TwoWayView
             return convertView;
         }
     }
 
     private boolean isVertical() {
         return (getOrientation().compareTo(TwoWayView.Orientation.VERTICAL) == 0);
     }
 
+    @Override
+    public void closeAll() {
+        final int childCount = getChildCount();
+
+        // Just close the panel if there are no tabs to close.
+        if (childCount == 0) {
+            autoHidePanel();
+            return;
+        }
+
+        // Disable the view so that gestures won't interfere wth the tab close animation.
+        setEnabled(false);
+
+        // Delay starting each successive animation to create a cascade effect.
+        int cascadeDelay = 0;
+
+        for (int i = childCount - 1; i >= 0; i--) {
+            final View view = getChildAt(i);
+            final PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
+            animator.attach(view, Property.ALPHA, 0);
+
+            if (isVertical()) {
+                animator.attach(view, Property.TRANSLATION_X, view.getWidth());
+            } else {
+                animator.attach(view, Property.TRANSLATION_Y, view.getHeight());
+            }
+
+            mCloseAllAnimationCount++;
+
+            animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
+                @Override
+                public void onPropertyAnimationStart() { }
+
+                @Override
+                public void onPropertyAnimationEnd() {
+                    mCloseAllAnimationCount--;
+                    if (mCloseAllAnimationCount > 0) {
+                        return;
+                    }
+
+                    // Hide the panel after the animation is done.
+                    autoHidePanel();
+
+                    // Re-enable the view after the animation is done.
+                    TabsTray.this.setEnabled(true);
+
+                    // Then actually close all the tabs.
+                    final Iterable<Tab> tabs = Tabs.getInstance().getTabsInOrder();
+                    for (Tab tab : tabs) {
+                        // In the normal panel we want to close all tabs (both private and normal),
+                        // but in the private panel we only want to close private tabs.
+                        if (!mIsPrivate || tab.isPrivate()) {
+                            Tabs.getInstance().closeTab(tab, false);
+                        }
+                    }
+                }
+            });
+
+            ThreadUtils.getUiHandler().postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    animator.start();
+                }
+            }, cascadeDelay);
+
+            cascadeDelay += ANIMATION_CASCADE_DELAY;
+        }
+    }
+
     private void animateClose(final View view, int pos) {
         PropertyAnimator animator = new PropertyAnimator(ANIMATION_DURATION);
         animator.attach(view, Property.ALPHA, 0);
 
         if (isVertical())
             animator.attach(view, Property.TRANSLATION_X, pos);
         else
             animator.attach(view, Property.TRANSLATION_Y, pos);
@@ -560,17 +639,17 @@ class TabsTray extends TwoWayView
                     mSwipeStartX = 0;
                     mSwipeStartY = 0;
                     mSwiping = false;
 
                     break;
                 }
 
                 case MotionEvent.ACTION_MOVE: {
-                    if (mSwipeView == null)
+                    if (mSwipeView == null || mVelocityTracker == null)
                         break;
 
                     mVelocityTracker.addMovement(e);
 
                     final boolean isVertical = isVertical();
 
                     float deltaX = e.getRawX() - mSwipeStartX;
                     float deltaY = e.getRawY() - mSwipeStartY;
--- a/security/manager/ssl/src/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/src/SSLServerCertVerification.cpp
@@ -132,18 +132,16 @@
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gPIPNSSLog;
 #endif
 
 namespace mozilla { namespace psm {
 
 namespace {
 
-NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
-
 NSSCleanupAutoPtrClass(CERTCertificate, CERT_DestroyCertificate)
 NSSCleanupAutoPtrClass_WithParam(PLArenaPool, PORT_FreeArena, FalseParam, false)
 
 // do not use a nsCOMPtr to avoid static initializer/destructor
 nsIThreadPool* gCertVerificationThreadPool = nullptr;
 
 // We avoid using a mutex for the success case to avoid lock-related
 // performance issues. However, we do use a lock in the error case to simplify
--- a/security/manager/ssl/src/TransportSecurityInfo.cpp
+++ b/security/manager/ssl/src/TransportSecurityInfo.cpp
@@ -15,16 +15,17 @@
 #include "nsDateTimeFormatCID.h"
 #include "nsICertOverrideService.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsNSSCertHelper.h"
 #include "nsIProgrammingLanguage.h"
 #include "nsIArray.h"
 #include "nsComponentManagerUtils.h"
+#include "nsReadableUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "PSMRunnable.h"
 
 #include "secerr.h"
 
 //#define DEBUG_SSL_VERBOSE //Enable this define to get minimal 
                             //reports when doing SSL read/write
                             
@@ -619,22 +620,31 @@ GetSubjectAltNames(CERTCertificate *nssC
 
   SECITEM_FreeItem(&altNameExtension, false);
 
   CERTGeneralName *current = sanNameList;
   do {
     nsAutoString name;
     switch (current->type) {
       case certDNSName:
-        name.AssignASCII((char*)current->name.other.data, current->name.other.len);
-        if (!allNames.IsEmpty()) {
-          allNames.AppendLiteral(", ");
+        {
+          nsDependentCSubstring nameFromCert(reinterpret_cast<char*>
+                                              (current->name.other.data),
+                                              current->name.other.len);
+          // dNSName fields are defined as type IA5String and thus should
+          // be limited to ASCII characters.
+          if (IsASCII(nameFromCert)) {
+            name.Assign(NS_ConvertASCIItoUTF16(nameFromCert));
+            if (!allNames.IsEmpty()) {
+              allNames.AppendLiteral(", ");
+            }
+            ++nameCount;
+            allNames.Append(name);
+          }
         }
-        ++nameCount;
-        allNames.Append(name);
         break;
 
       case certIPAddress:
         {
           char buf[INET6_ADDRSTRLEN];
           PRNetAddr addr;
           if (current->name.other.len == 4) {
             addr.inet.family = PR_AF_INET;
@@ -704,18 +714,25 @@ AppendErrorTextMismatch(const nsString &
   bool useSAN = false;
 
   if (nssCert)
     useSAN = GetSubjectAltNames(nssCert.get(), component, allNames, nameCount);
 
   if (!useSAN) {
     char *certName = CERT_GetCommonName(&nssCert->subject);
     if (certName) {
-      ++nameCount;
-      allNames.Assign(NS_ConvertUTF8toUTF16(certName));
+      nsDependentCSubstring commonName(certName, strlen(certName));
+      if (IsUTF8(commonName)) {
+        // Bug 1024781
+        // We should actually check that the common name is a valid dns name or
+        // ip address and not any string value before adding it to the display
+        // list.
+        ++nameCount;
+        allNames.Assign(NS_ConvertUTF8toUTF16(commonName));
+      }
       PORT_Free(certName);
     }
   }
 
   if (nameCount > 1) {
     nsString message;
     rv = component->GetPIPNSSBundleString("certErrorMismatchMultiple", 
                                           message);
--- a/security/manager/ssl/src/nsKeygenHandler.cpp
+++ b/security/manager/ssl/src/nsKeygenHandler.cpp
@@ -62,19 +62,16 @@ DERTemplate CERTPublicKeyAndChallengeTem
 const SEC_ASN1Template SECKEY_PQGParamsTemplate[] = {
     { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(PQGParams) },
     { SEC_ASN1_INTEGER, offsetof(PQGParams,prime) },
     { SEC_ASN1_INTEGER, offsetof(PQGParams,subPrime) },
     { SEC_ASN1_INTEGER, offsetof(PQGParams,base) },
     { 0, }
 };
 
-
-static NS_DEFINE_IID(kIDOMHTMLSelectElementIID, NS_IDOMHTMLSELECTELEMENT_IID);
-
 static PQGParams *
 decode_pqg_params(char *aStr)
 {
     unsigned char *buf = nullptr;
     unsigned int len;
     PLArenaPool *arena = nullptr;
     PQGParams *params = nullptr;
     SECStatus status;
--- a/services/healthreport/docs/dataformat.rst
+++ b/services/healthreport/docs/dataformat.rst
@@ -1537,16 +1537,20 @@ FHR if telemetry is enabled.
 
 Version 1
 ^^^^^^^^^
 
 Daily counts are reported in the following properties:
 
 translationOpportunityCount
     Integer count of the number of opportunities there were to translate a page.
+missedTranslationOpportunityCount
+    Integer count of the number of missed opportunities there were to translate a page.
+    A missed opportunity is when the page language is not supported by the translation
+    provider.
 pageTranslatedCount
     Integer count of the number of pages translated.
 charactersTranslatedCount
     Integer count of the number of characters translated.
 detectedLanguageChangedBefore
     Integer count of the number of times the user manually adjusted the detected
     language before translating.
 detectedLanguageChangedAfter
@@ -1554,16 +1558,19 @@ detectedLanguageChangedAfter
     language after having first translated the page.
 
 Additional daily counts broken down by language are reported in the following
 properties:
 
 translationOpportunityCountsByLanguage
     A mapping from language to count of opportunities to translate that
     language.
+missedTranslationOpportunityCountsByLanguage
+    A mapping from language to count of missed opportunities to translate that
+    language.
 pageTranslatedCountsByLanguage
     A mapping from language to the counts of pages translated from that
     language. Each language entry will be an object containing a "total" member
     along with individual counts for each language translated to.
 
 Other properties:
 
 detectLanguageEnabled
--- a/testing/config/mozharness/b2g_emulator_config.py
+++ b/testing/config/mozharness/b2g_emulator_config.py
@@ -1,50 +1,50 @@
 # 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/.
 
 config = {
     "jsreftest_options": [
         "--adbpath=%(adbpath)s", "--b2gpath=%(b2gpath)s", "--emulator=%(emulator)s",
-        "--emulator-res=800x1000", "--logcat-dir=%(logcat_dir)s",
+        "--emulator-res=800x1000", "--logdir=%(logcat_dir)s",
         "--remote-webserver=%(remote_webserver)s", "--ignore-window-size",
         "--xre-path=%(xre_path)s", "--symbols-path=%(symbols_path)s", "--busybox=%(busybox)s",
         "--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s",
         "--extra-profile-file=jsreftest/tests/user.js",
         "%(test_manifest)s",
     ],
 
     "mochitest_options": [
         "--adbpath=%(adbpath)s", "--b2gpath=%(b2gpath)s", "--console-level=INFO",
-        "--emulator=%(emulator)s", "--logcat-dir=%(logcat_dir)s",
+        "--emulator=%(emulator)s", "--logdir=%(logcat_dir)s",
         "--remote-webserver=%(remote_webserver)s", "%(test_manifest)s",
         "--xre-path=%(xre_path)s", "--symbols-path=%(symbols_path)s", "--busybox=%(busybox)s",
         "--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s",
         "--quiet", "--certificate-path=%(certificate_path)s",
         "--test-path=%(test_path)s",
     ],
 
     "reftest_options": [
         "--adbpath=%(adbpath)s", "--b2gpath=%(b2gpath)s", "--emulator=%(emulator)s",
-        "--emulator-res=800x1000", "--logcat-dir=%(logcat_dir)s",
+        "--emulator-res=800x1000", "--logdir=%(logcat_dir)s",
         "--remote-webserver=%(remote_webserver)s", "--ignore-window-size",
         "--xre-path=%(xre_path)s", "--symbols-path=%(symbols_path)s", "--busybox=%(busybox)s",
         "--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s", "--enable-oop",
         "%(test_manifest)s",
     ],
 
     "crashtest_options": [
         "--adbpath=%(adbpath)s", "--b2gpath=%(b2gpath)s", "--emulator=%(emulator)s",
-        "--emulator-res=800x1000", "--logcat-dir=%(logcat_dir)s",
+        "--emulator-res=800x1000", "--logdir=%(logcat_dir)s",
         "--remote-webserver=%(remote_webserver)s", "--ignore-window-size",
         "--xre-path=%(xre_path)s", "--symbols-path=%(symbols_path)s", "--busybox=%(busybox)s",
         "--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s",
         "%(test_manifest)s",
     ],
 
     "xpcshell_options": [
         "--adbpath=%(adbpath)s", "--b2gpath=%(b2gpath)s", "--emulator=%(emulator)s",
-        "--logcat-dir=%(logcat_dir)s", "--manifest=%(test_manifest)s", "--use-device-libs",
+        "--logdir=%(logcat_dir)s", "--manifest=%(test_manifest)s", "--use-device-libs",
         "--testing-modules-dir=%(modules_dir)s", "--symbols-path=%(symbols_path)s",
         "--busybox=%(busybox)s", "--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s",
     ],
 }
--- a/testing/marionette/client/marionette/__init__.py
+++ b/testing/marionette/client/marionette/__init__.py
@@ -1,26 +1,54 @@
 # 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/.
 
 from gestures import smooth_scroll, pinch
 from by import By
 from marionette import Marionette, HTMLElement, Actions, MultiActions
 from marionette_test import MarionetteTestCase, MarionetteJSTestCase, CommonTestCase, expectedFailure, skip, SkipTest
-from emulator import Emulator
 from errors import (
-        ErrorCodes, MarionetteException, InstallGeckoError, TimeoutException, InvalidResponseException,
-        JavascriptException, NoSuchElementException, XPathLookupException, NoSuchWindowException,
-        StaleElementException, ScriptTimeoutException, ElementNotVisibleException,
-        NoSuchFrameException, InvalidElementStateException, NoAlertPresentException,
-        InvalidCookieDomainException, UnableToSetCookieException, InvalidSelectorException,
-        MoveTargetOutOfBoundsException, FrameSendNotInitializedError, FrameSendFailureError
-        )
+    ElementNotVisibleException,
+    ErrorCodes,
+    FrameSendFailureError,
+    FrameSendNotInitializedError,
+    InvalidCookieDomainException,
+    InvalidElementStateException,
+    InvalidResponseException,
+    InvalidSelectorException,
+    JavascriptException,
+    MarionetteException,
+    MoveTargetOutOfBoundsException,
+    NoAlertPresentException,
+    NoSuchElementException,
+    NoSuchFrameException,
+    NoSuchWindowException,
+    ScriptTimeoutException,
+    StaleElementException,
+    TimeoutException,
+    UnableToSetCookieException,
+    XPathLookupException,
+)
 from runner import (
-        B2GTestCaseMixin, B2GTestResultMixin, BaseMarionetteOptions, BaseMarionetteTestRunner, EnduranceOptionsMixin,
-        EnduranceTestCaseMixin, HTMLReportingOptionsMixin, HTMLReportingTestResultMixin, HTMLReportingTestRunnerMixin,
-        Marionette, MarionetteTest, MarionetteTestResult, MarionetteTextTestRunner, MemoryEnduranceTestCaseMixin,
-        MozHttpd, OptionParser, TestManifest, TestResult, TestResultCollection
-        )
+        B2GTestCaseMixin,
+        B2GTestResultMixin,
+        BaseMarionetteOptions,
+        BaseMarionetteTestRunner,
+        EnduranceOptionsMixin,
+        EnduranceTestCaseMixin,
+        HTMLReportingOptionsMixin,
+        HTMLReportingTestResultMixin,
+        HTMLReportingTestRunnerMixin,
+        Marionette,
+        MarionetteTest,
+        MarionetteTestResult,
+        MarionetteTextTestRunner,
+        MemoryEnduranceTestCaseMixin,
+        MozHttpd,
+        OptionParser,
+        TestManifest,
+        TestResult,
+        TestResultCollection
+)
 from wait import Wait
 from date_time_value import DateTimeValue
 import decorators
deleted file mode 100644
--- a/testing/marionette/client/marionette/b2gbuild.py
+++ /dev/null
@@ -1,98 +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/.
-
-import os
-import platform
-import subprocess
-import sys
-
-
-class B2GBuild(object):
-
-    @classmethod
-    def find_b2g_dir(cls):
-        for env_var in ('B2G_DIR', 'B2G_HOME'):
-            if env_var in os.environ:
-                env_dir = os.environ[env_var]
-                env_dir = cls.check_b2g_dir(env_dir)
-                if env_dir:
-                    return env_dir
-
-        cwd = os.getcwd()
-        cwd = cls.check_b2g_dir(cwd)
-        if cwd:
-            return cwd
-
-        return None
-
-    @classmethod
-    def check_adb(cls, homedir):
-        if 'ADB' in os.environ:
-            env_adb = os.environ['ADB']
-            if os.path.exists(env_adb):
-                return env_adb
-
-        return cls.check_host_binary(homedir, 'adb')
-
-    @classmethod
-    def check_b2g_dir(cls, dir):
-        if os.path.isfile(os.path.join(dir, 'load-config.sh')):
-            return dir
-
-        oldstyle_dir = os.path.join(dir, 'glue', 'gonk-ics')
-        if os.access(oldstyle_dir, os.F_OK):
-            return oldstyle_dir
-
-        return None
-
-    @classmethod
-    def check_fastboot(cls, homedir):
-        return cls.check_host_binary(homedir, 'fastboot')
-
-    @classmethod
-    def check_host_binary(cls, homedir, binary):
-        host_dir = "linux-x86"
-        if platform.system() == "Darwin":
-            host_dir = "darwin-x86"
-        binary_path = subprocess.Popen(['which', binary],
-                                       stdout=subprocess.PIPE,
-                                       stderr=subprocess.STDOUT)
-        if binary_path.wait() == 0:
-            return binary_path.stdout.read().strip()  # remove trailing newline
-        binary_paths = [os.path.join(homedir, 'glue', 'gonk', 'out', 'host',
-                                     host_dir, 'bin', binary),
-                        os.path.join(homedir, 'out', 'host', host_dir,
-                                     'bin', binary),
-                        os.path.join(homedir, 'bin', binary)]
-        for option in binary_paths:
-            if os.path.exists(option):
-                return option
-        raise Exception('%s not found!' % binary)
-
-    def __init__(self, homedir=None, emulator=False):
-        if not homedir:
-            homedir = self.find_b2g_dir()
-        else:
-            homedir = self.check_b2g_dir(homedir)
-
-        if not homedir:
-            raise EnvironmentError('Must define B2G_HOME or pass the homedir parameter')
-
-        self.homedir = homedir
-        self.adb_path = self.check_adb(self.homedir)
-        self.update_tools = os.path.join(self.homedir, 'tools', 'update-tools')
-        self.fastboot_path = None if emulator else self.check_fastboot(self.homedir)
-
-    def import_update_tools(self):
-        """Import the update_tools package from B2G"""
-        sys.path.append(self.update_tools)
-        import update_tools
-        sys.path.pop()
-        return update_tools
-
-    def check_file(self, filePath):
-        if not os.access(filePath, os.F_OK):
-            raise Exception(('File not found: %s; did you pass the B2G home '
-                             'directory as the homedir parameter, or set '
-                             'B2G_HOME correctly?') % filePath)
deleted file mode 100644
--- a/testing/marionette/client/marionette/b2ginstance.py
+++ /dev/null
@@ -1,70 +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/.
-
-from ConfigParser import ConfigParser
-import posixpath
-import shutil
-import tempfile
-import traceback
-
-from b2gbuild import B2GBuild
-from mozdevice import DeviceManagerADB
-import mozcrash
-
-
-class B2GInstance(B2GBuild):
-
-    def __init__(self, devicemanager=None, symbols_path=None, **kwargs):
-        B2GBuild.__init__(self, **kwargs)
-
-        self._dm = devicemanager
-        self._remote_profiles = None
-        self.symbols_path = symbols_path
-
-    @property
-    def dm(self):
-        if not self._dm:
-            self._dm = DeviceManagerADB(adbPath=self.adb_path)
-        return self._dm
-
-    @property
-    def remote_profiles(self):
-        if not self._remote_profiles:
-            self.check_remote_profiles()
-        return self._remote_profiles
-
-    def check_remote_profiles(self, remote_profiles_ini='/data/b2g/mozilla/profiles.ini'):
-        if not self.dm.fileExists(remote_profiles_ini):
-            raise Exception("Remote file '%s' not found" % remote_profiles_ini)
-
-        local_profiles_ini = tempfile.NamedTemporaryFile()
-        self.dm.getFile(remote_profiles_ini, local_profiles_ini.name)
-        cfg = ConfigParser()
-        cfg.read(local_profiles_ini.name)
-
-        remote_profiles = []
-        for section in cfg.sections():
-            if cfg.has_option(section, 'Path'):
-                if cfg.has_option(section, 'IsRelative') and cfg.getint(section, 'IsRelative'):
-                    remote_profiles.append(posixpath.join(posixpath.dirname(remote_profiles_ini), cfg.get(section, 'Path')))
-                else:
-                    remote_profiles.append(cfg.get(section, 'Path'))
-        self._remote_profiles = remote_profiles
-        return remote_profiles
-
-    def check_for_crashes(self):
-        remote_dump_dirs = [posixpath.join(p, 'minidumps') for p in self.remote_profiles]
-        crashed = False
-        for remote_dump_dir in remote_dump_dirs:
-            local_dump_dir = tempfile.mkdtemp()
-            self.dm.getDirectory(remote_dump_dir, local_dump_dir)
-            try:
-                if mozcrash.check_for_crashes(local_dump_dir, self.symbols_path):
-                    crashed = True
-            except:
-                traceback.print_exc()
-            finally:
-                shutil.rmtree(local_dump_dir)
-                self.dm.removeDir(remote_dump_dir)
-        return crashed
deleted file mode 100644
--- a/testing/marionette/client/marionette/emulator.py
+++ /dev/null
@@ -1,539 +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/.
-
-from b2ginstance import B2GInstance
-import datetime
-from mozdevice import devicemanagerADB, DMError
-from mozprocess import ProcessHandlerMixin
-import os
-import re
-import platform
-import shutil
-import socket
-import subprocess
-from telnetlib import Telnet
-import tempfile
-import time
-import traceback
-
-from emulator_battery import EmulatorBattery
-from emulator_geo import EmulatorGeo
-from emulator_screen import EmulatorScreen
-from decorators import uses_marionette
-
-from errors import (
-    InstallGeckoError,
-    InvalidResponseException,
-    MarionetteException,
-    ScriptTimeoutException,
-    TimeoutException
-)
-
-
-class LogOutputProc(ProcessHandlerMixin):
-    """
-    Process handler for processes which save all output to a logfile.
-    If no logfile is specified, output will still be consumed to prevent
-    the output pipe's from overflowing.
-    """
-
-    def __init__(self, cmd, logfile=None,  **kwargs):
-        self.logfile = logfile
-        kwargs.setdefault('processOutputLine', []).append(self.log_output)
-        ProcessHandlerMixin.__init__(self, cmd, **kwargs)
-
-    def log_output(self, line):
-        if not self.logfile:
-            return
-
-        f = open(self.logfile, 'a')
-        f.write(line + "\n")
-        f.flush()
-
-
-class Emulator(object):
-
-    deviceRe = re.compile(r"^emulator-(\d+)(\s*)(.*)$")
-    _default_res = '320x480'
-    prefs = {'app.update.enabled': False,
-             'app.update.staging.enabled': False,
-             'app.update.service.enabled': False}
-    env = {'MOZ_CRASHREPORTER': '1',
-           'MOZ_CRASHREPORTER_NO_REPORT': '1',
-           'MOZ_CRASHREPORTER_SHUTDOWN': '1'}
-
-    def __init__(self, homedir=None, noWindow=False, logcat_dir=None,
-                 arch="x86", emulatorBinary=None, res=None, sdcard=None,
-                 symbols_path=None, userdata=None):
-        self.port = None
-        self.dm = None
-        self._emulator_launched = False
-        self.proc = None
-        self.marionette_port = None
-        self.telnet = None
-        self._tmp_sdcard = None
-        self._tmp_userdata = None
-        self._adb_started = False
-        self.logcat_dir = logcat_dir
-        self.logcat_proc = None
-        self.arch = arch
-        self.binary = emulatorBinary
-        self.res = res or self._default_res
-        self.battery = EmulatorBattery(self)
-        self.geo = EmulatorGeo(self)
-        self.screen = EmulatorScreen(self)
-        self.homedir = homedir
-        self.sdcard = sdcard
-        self.symbols_path = symbols_path
-        self.noWindow = noWindow
-        if self.homedir is not None:
-            self.homedir = os.path.expanduser(homedir)
-        self.dataImg = userdata
-        self.copy_userdata = self.dataImg is None
-
-    def _check_for_b2g(self):
-        self.b2g = B2GInstance(homedir=self.homedir, emulator=True,
-                               symbols_path=self.symbols_path)
-        self.adb = self.b2g.adb_path
-        self.homedir = self.b2g.homedir
-
-        if self.arch not in ("x86", "arm"):
-            raise Exception("Emulator architecture must be one of x86, arm, got: %s" %
-                            self.arch)
-
-        host_dir = "linux-x86"
-        if platform.system() == "Darwin":
-            host_dir = "darwin-x86"
-
-        host_bin_dir = os.path.join("out", "host", host_dir, "bin")
-
-        if self.arch == "x86":
-            binary = os.path.join(host_bin_dir, "emulator-x86")
-            kernel = "prebuilts/qemu-kernel/x86/kernel-qemu"
-            sysdir = "out/target/product/generic_x86"
-            self.tail_args = []
-        else:
-            binary = os.path.join(host_bin_dir, "emulator")
-            kernel = "prebuilts/qemu-kernel/arm/kernel-qemu-armv7"
-            sysdir = "out/target/product/generic"
-            self.tail_args = ["-cpu", "cortex-a8"]
-
-        if(self.sdcard):
-            self.mksdcard = os.path.join(self.homedir, host_bin_dir, "mksdcard")
-            self.create_sdcard(self.sdcard)
-
-        if not self.binary:
-            self.binary = os.path.join(self.homedir, binary)
-
-        self.b2g.check_file(self.binary)
-
-        self.kernelImg = os.path.join(self.homedir, kernel)
-        self.b2g.check_file(self.kernelImg)
-
-        self.sysDir = os.path.join(self.homedir, sysdir)
-        self.b2g.check_file(self.sysDir)
-
-        if not self.dataImg:
-            self.dataImg = os.path.join(self.sysDir, 'userdata.img')
-        self.b2g.check_file(self.dataImg)
-
-    def __del__(self):
-        if self.telnet:
-            self.telnet.write('exit\n')
-            self.telnet.read_all()
-
-    @property
-    def args(self):
-        qemuArgs = [self.binary,
-                    '-kernel', self.kernelImg,
-                    '-sysdir', self.sysDir,
-                    '-data', self.dataImg]
-        if self._tmp_sdcard:
-            qemuArgs.extend(['-sdcard', self._tmp_sdcard])
-        if self.noWindow:
-            qemuArgs.append('-no-window')
-        qemuArgs.extend(['-memory', '512',
-                         '-partition-size', '512',
-                         '-verbose',
-                         '-skin', self.res,
-                         '-gpu', 'on',
-                         '-qemu'] + self.tail_args)
-        return qemuArgs
-
-    @property
-    def is_running(self):
-        if self._emulator_launched:
-            return self.proc is not None and self.proc.poll() is None
-        else:
-            return self.port is not None
-
-    def check_for_crash(self):
-        """
-        Checks if the emulator has crashed or not.  Always returns False if
-        we've connected to an already-running emulator, since we can't track
-        the emulator's pid in that case.  Otherwise, returns True iff
-        self.proc is not None (meaning the emulator hasn't been explicitly
-        closed), and self.proc.poll() is also not None (meaning the emulator
-        process has terminated).
-        """
-        return self._emulator_launched and self.proc is not None \
-                                       and self.proc.poll() is not None
-
-    def check_for_minidumps(self):
-        return self.b2g.check_for_crashes()
-
-    def create_sdcard(self, sdcard):
-        self._tmp_sdcard = tempfile.mktemp(prefix='sdcard')
-        sdargs = [self.mksdcard, "-l", "mySdCard", sdcard, self._tmp_sdcard]
-        sd = subprocess.Popen(sdargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        retcode = sd.wait()
-        if retcode:
-            raise Exception('unable to create sdcard : exit code %d: %s'
-                            % (retcode, sd.stdout.read()))
-        return None
-
-    def _run_adb(self, args):
-        args.insert(0, self.adb)
-        if self.port:
-            args.insert(1, '-s')
-            args.insert(2, 'emulator-%d' % self.port)
-        adb = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        retcode = adb.wait()
-        if retcode:
-            raise Exception('adb terminated with exit code %d: %s'
-                            % (retcode, adb.stdout.read()))
-        return adb.stdout.read()
-
-    def _get_telnet_response(self, command=None):
-        output = []
-        assert(self.telnet)
-        if command is not None:
-            self.telnet.write('%s\n' % command)
-        while True:
-            line = self.telnet.read_until('\n')
-            output.append(line.rstrip())
-            if line.startswith('OK'):
-                return output
-            elif line.startswith('KO:'):
-                raise Exception('bad telnet response: %s' % line)
-
-    def _run_telnet(self, command):
-        if not self.telnet:
-            self.telnet = Telnet('localhost', self.port)
-            self._get_telnet_response()
-        return self._get_telnet_response(command)
-
-    def _run_shell(self, args):
-        args.insert(0, 'shell')
-        return self._run_adb(args).split('\r\n')
-
-    def close(self):
-        if self.is_running and self._emulator_launched:
-            self.proc.kill()
-        if self._adb_started:
-            self._run_adb(['kill-server'])
-            self._adb_started = False
-        if self.proc:
-            retcode = self.proc.poll()
-            self.proc = None
-            if self._tmp_userdata:
-                os.remove(self._tmp_userdata)
-                self._tmp_userdata = None
-            if self._tmp_sdcard:
-                os.remove(self._tmp_sdcard)
-                self._tmp_sdcard = None
-            return retcode
-        if self.logcat_proc and self.logcat_proc.proc.poll() is None:
-            self.logcat_proc.kill()
-        return 0
-
-    def _get_adb_devices(self):
-        offline = set()
-        online = set()
-        output = self._run_adb(['devices'])
-        for line in output.split('\n'):
-            m = self.deviceRe.match(line)
-            if m:
-                if m.group(3) == 'offline':
-                    offline.add(m.group(1))
-                else:
-                    online.add(m.group(1))
-        return (online, offline)
-
-    def start_adb(self):
-        result = self._run_adb(['start-server'])
-        # We keep track of whether we've started adb or not, so we know
-        # if we need to kill it.
-        if 'daemon started successfully' in result:
-            self._adb_started = True
-        else:
-            self._adb_started = False
-
-    @uses_marionette
-    def wait_for_system_message(self, marionette):
-        marionette.set_script_timeout(45000)
-        # Telephony API's won't be available immediately upon emulator
-        # boot; we have to wait for the syste-message-listener-ready
-        # message before we'll be able to use them successfully.  See
-        # bug 792647.
-        print 'waiting for system-message-listener-ready...'
-        try:
-            marionette.execute_async_script("""
-waitFor(
-    function() { marionetteScriptFinished(true); },
-    function() { return isSystemMessageListenerReady(); }
-);
-            """)
-        except ScriptTimeoutException:
-            print 'timed out'
-            # We silently ignore the timeout if it occurs, since
-            # isSystemMessageListenerReady() isn't available on
-            # older emulators.  45s *should* be enough of a delay
-            # to allow telephony API's to work.
-            pass
-        except (InvalidResponseException, IOError):
-            self.check_for_minidumps()
-            raise
-        print '...done'
-
-    def connect(self):
-        self.adb = B2GInstance.check_adb(self.homedir, emulator=True)
-        self.start_adb()
-
-        online, offline = self._get_adb_devices()
-        now = datetime.datetime.now()
-        while online == set([]):
-            time.sleep(1)
-            if datetime.datetime.now() - now > datetime.timedelta(seconds=60):
-                raise Exception('timed out waiting for emulator to be available')
-            online, offline = self._get_adb_devices()
-        self.port = int(list(online)[0])
-
-        self.dm = devicemanagerADB.DeviceManagerADB(adbPath=self.adb,
-                                                    deviceSerial='emulator-%d' % self.port)
-
-    def start(self):
-        self._check_for_b2g()
-        self.start_adb()
-
-        qemu_args = self.args[:]
-        if self.copy_userdata:
-            # Make a copy of the userdata.img for this instance of the emulator to use.
-            self._tmp_userdata = tempfile.mktemp(prefix='marionette')
-            shutil.copyfile(self.dataImg, self._tmp_userdata)
-            qemu_args[qemu_args.index('-data') + 1] = self._tmp_userdata
-
-        original_online, original_offline = self._get_adb_devices()
-
-        filename = None
-        if self.logcat_dir:
-            filename = os.path.join(self.logcat_dir, 'qemu.log')
-            if os.path.isfile(filename):
-                self.rotate_log(filename)
-
-        self.proc = LogOutputProc(qemu_args, filename)
-        self.proc.run()
-
-        online, offline = self._get_adb_devices()
-        now = datetime.datetime.now()
-        while online - original_online == set([]):
-            time.sleep(1)
-            if datetime.datetime.now() - now > datetime.timedelta(seconds=60):
-                raise TimeoutException('timed out waiting for emulator to start')
-            online, offline = self._get_adb_devices()
-        self.port = int(list(online - original_online)[0])
-        self._emulator_launched = True
-
-        self.dm = devicemanagerADB.DeviceManagerADB(adbPath=self.adb,
-                                                    deviceSerial='emulator-%d' % self.port)
-
-        # bug 802877
-        time.sleep(10)
-        self.geo.set_default_location()
-        self.screen.initialize()
-
-        if self.logcat_dir:
-            self.save_logcat()
-
-        # setup DNS fix for networking
-        self._run_adb(['shell', 'setprop', 'net.dns1', '10.0.2.3'])
-
-    @uses_marionette
-    def wait_for_homescreen(self, marionette):
-        print 'waiting for homescreen...'
-
-        marionette.set_context(marionette.CONTEXT_CONTENT)
-        marionette.execute_async_script("""
-log('waiting for mozbrowserloadend');
-window.addEventListener('mozbrowserloadend', function loaded(aEvent) {
-  log('received mozbrowserloadend for ' + aEvent.target.src);
-  if (aEvent.target.src.indexOf('ftu') != -1 || aEvent.target.src.indexOf('homescreen') != -1) {
-    window.removeEventListener('mozbrowserloadend', loaded);
-    marionetteScriptFinished();
-  }
-});""", script_timeout=120000)
-        print '...done'
-
-    def setup(self, marionette, gecko_path=None, busybox=None):
-        self.set_environment(marionette)
-        if busybox:
-            self.install_busybox(busybox)
-
-        if gecko_path:
-            self.install_gecko(gecko_path, marionette)
-
-        self.wait_for_system_message(marionette)
-        self.set_prefs(marionette)
-
-    @uses_marionette
-    def set_environment(self, marionette):
-        for k, v in self.env.iteritems():
-            marionette.execute_script("""
-            let env = Cc["@mozilla.org/process/environment;1"].
-                      getService(Ci.nsIEnvironment);
-            env.set("%s", "%s");
-            """ % (k, v))
-
-    @uses_marionette
-    def set_prefs(self, marionette):
-        for pref in self.prefs:
-            marionette.execute_script("""
-            Components.utils.import("resource://gre/modules/Services.jsm");
-            let argtype = typeof(arguments[1]);
-            switch(argtype) {
-                case 'boolean':
-                    Services.prefs.setBoolPref(arguments[0], arguments[1]);
-                    break;
-                case 'number':
-                    Services.prefs.setIntPref(arguments[0], arguments[1]);
-                    break;
-                default:
-                    Services.prefs.setCharPref(arguments[0], arguments[1]);
-            }
-            """, [pref, self.prefs[pref]])
-
-    def restart_b2g(self):
-        print 'restarting B2G'
-        self.dm.shellCheckOutput(['stop', 'b2g'])
-        time.sleep(10)
-        self.dm.shellCheckOutput(['start', 'b2g'])
-
-        if not self.wait_for_port():
-            raise TimeoutException("Timeout waiting for marionette on port '%s'" % self.marionette_port)
-
-    def install_gecko(self, gecko_path, marionette):
-        """
-        Install gecko into the emulator using adb push.  Restart b2g after the
-        installation.
-        """
-        # See bug 800102.  We use this particular method of installing
-        # gecko in order to avoid an adb bug in which adb will sometimes
-        # hang indefinitely while copying large files to the system
-        # partition.
-        print 'installing gecko binaries...'
-
-        # see bug 809437 for the path that lead to this madness
-        try:
-            # need to remount so we can write to /system/b2g
-            self._run_adb(['remount'])
-            self.dm.removeDir('/data/local/b2g')
-            self.dm.mkDir('/data/local/b2g')
-            self.dm.pushDir(gecko_path, '/data/local/b2g', retryLimit=10)
-
-            self.dm.shellCheckOutput(['stop', 'b2g'])
-
-            for root, dirs, files in os.walk(gecko_path):
-                for filename in files:
-                    rel_path = os.path.relpath(os.path.join(root, filename), gecko_path)
-                    data_local_file = os.path.join('/data/local/b2g', rel_path)
-                    system_b2g_file = os.path.join('/system/b2g', rel_path)
-
-                    print 'copying', data_local_file, 'to', system_b2g_file
-                    self.dm.shellCheckOutput(['dd',
-                                              'if=%s' % data_local_file,
-                                              'of=%s' % system_b2g_file])
-            self.restart_b2g()
-
-        except (DMError, MarionetteException):
-            # Bug 812395 - raise a single exception type for these so we can
-            # explicitly catch them elsewhere.
-
-            # print exception, but hide from mozharness error detection
-            exc = traceback.format_exc()
-            exc = exc.replace('Traceback', '_traceback')
-            print exc
-
-            raise InstallGeckoError("unable to restart B2G after installing gecko")
-
-    def install_busybox(self, busybox):
-        self._run_adb(['remount'])
-
-        remote_file = "/system/bin/busybox"
-        print 'pushing %s' % remote_file
-        self.dm.pushFile(busybox, remote_file, retryLimit=10)
-        self._run_adb(['shell', 'cd /system/bin; chmod 555 busybox; for x in `./busybox --list`; do ln -s ./busybox $x; done'])
-        self.dm._verifyZip()
-
-    def rotate_log(self, srclog, index=1):
-        """ Rotate a logfile, by recursively rotating logs further in the sequence,
-            deleting the last file if necessary.
-        """
-        basename = os.path.basename(srclog)
-        basename = basename[:-len('.log')]
-        if index > 1:
-            basename = basename[:-len('.1')]
-        basename = '%s.%d.log' % (basename, index)
-
-        destlog = os.path.join(self.logcat_dir, basename)
-        if os.path.isfile(destlog):
-            if index == 3:
-                os.remove(destlog)
-            else:
-                self.rotate_log(destlog, index+1)
-        shutil.move(srclog, destlog)
-
-    def save_logcat(self):
-        """ Save the output of logcat to a file.
-        """
-        filename = os.path.join(self.logcat_dir, "emulator-%d.log" % self.port)
-        if os.path.isfile(filename):
-            self.rotate_log(filename)
-        cmd = [self.adb, '-s', 'emulator-%d' % self.port, 'logcat', '-v', 'threadtime']
-
-        self.logcat_proc = LogOutputProc(cmd, filename)
-        self.logcat_proc.run()
-
-    def setup_port_forwarding(self, remote_port):
-        """ Set up TCP port forwarding to the specified port on the device,
-            using any availble local port, and return the local port.
-        """
-        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        s.bind(("",0))
-        local_port = s.getsockname()[1]
-        s.close()
-
-        self._run_adb(['forward',
-                       'tcp:%d' % local_port,
-                       'tcp:%d' % remote_port])
-
-        self.marionette_port = local_port
-
-        return local_port
-
-    def wait_for_port(self, timeout=300):
-        assert(self.marionette_port)
-        starttime = datetime.datetime.now()
-        while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
-            try:
-                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-                sock.connect(('localhost', self.marionette_port))
-                data = sock.recv(16)
-                sock.close()
-                if ':' in data:
-                    return True
-            except:
-                import traceback
-                print traceback.format_exc()
-            time.sleep(1)
-        return False
deleted file mode 100644
--- a/testing/marionette/client/marionette/emulator_battery.py
+++ /dev/null
@@ -1,53 +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/.
-
-class EmulatorBattery(object):
-
-    def __init__(self, emulator):
-        self.emulator = emulator
-
-    def get_state(self):
-        status = {}
-        state = {}
-
-        response = self.emulator._run_telnet('power display')
-        for line in response:
-            if ':' in line:
-                field, value = line.split(':')
-                value = value.strip()
-                if value == 'true':
-                    value = True
-                elif value == 'false':
-                    value = False
-                elif field == 'capacity':
-                    value = float(value)
-                status[field] = value
-
-        state['level'] = status.get('capacity', 0.0) / 100
-        if status.get('AC') == 'online':
-            state['charging'] = True
-        else:
-            state['charging'] = False
-
-        return state
-
-    def get_charging(self):
-        return self.get_state()['charging']
-
-    def get_level(self):
-        return self.get_state()['level']
-
-    def set_level(self, level):
-        self.emulator._run_telnet('power capacity %d' % (level * 100))
-
-    def set_charging(self, charging):
-        if charging:
-            cmd = 'power ac on'
-        else:
-            cmd = 'power ac off'
-        self.emulator._run_telnet(cmd)
-
-    charging = property(get_charging, set_charging)
-    level = property(get_level, set_level)
-
deleted file mode 100644
--- a/testing/marionette/client/marionette/emulator_geo.py
+++ /dev/null
@@ -1,17 +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/.
-
-class EmulatorGeo(object):
-
-    def __init__(self, emulator):
-        self.emulator = emulator
-
-    def set_default_location(self):
-        self.lon = -122.08769
-        self.lat = 37.41857
-        self.set_location(self.lon, self.lat)
-
-    def set_location(self, lon, lat):
-        self.emulator._run_telnet('geo fix %0.5f %0.5f' % (self.lon, self.lat))
-
deleted file mode 100644
--- a/testing/marionette/client/marionette/emulator_screen.py
+++ /dev/null
@@ -1,78 +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/.
-
-class EmulatorScreen(object):
-    """Class for screen related emulator commands."""
-
-    SO_PORTRAIT_PRIMARY = 'portrait-primary'
-    SO_PORTRAIT_SECONDARY = 'portrait-secondary'
-    SO_LANDSCAPE_PRIMARY = 'landscape-primary'
-    SO_LANDSCAPE_SECONDARY = 'landscape-secondary'
-
-    def __init__(self, emulator):
-        self.emulator = emulator
-
-    def initialize(self):
-        self.orientation = self.SO_PORTRAIT_PRIMARY
-
-    def _get_raw_orientation(self):
-        """Get the raw value of the current device orientation."""
-        response = self.emulator._run_telnet('sensor get orientation')
-
-        return response[0].split('=')[1].strip()
-
-    def _set_raw_orientation(self, data):
-        """Set the raw value of the specified device orientation."""
-        self.emulator._run_telnet('sensor set orientation %s' % data)
-
-    def get_orientation(self):
-        """Get the current device orientation.
-
-        Returns;
-            orientation -- Orientation of the device. One of:
-                            SO_PORTRAIT_PRIMARY - system buttons at the bottom
-                            SO_PORTRIAT_SECONDARY - system buttons at the top
-                            SO_LANDSCAPE_PRIMARY - system buttons at the right
-                            SO_LANDSCAPE_SECONDARY - system buttons at the left
-
-        """
-        data = self._get_raw_orientation()
-
-        if data == '0:-90:0':
-            orientation = self.SO_PORTRAIT_PRIMARY
-        elif data == '0:90:0':
-            orientation = self.SO_PORTRAIT_SECONDARY
-        elif data == '0:0:90':
-            orientation = self.SO_LANDSCAPE_PRIMARY
-        elif data == '0:0:-90':
-            orientation = self.SO_LANDSCAPE_SECONDARY
-        else:
-            raise ValueError('Unknown orientation sensor value: %s.' % data)
-
-        return orientation
-
-    def set_orientation(self, orientation):
-        """Set the specified device orientation.
-
-        Args
-            orientation -- Orientation of the device. One of:
-                            SO_PORTRAIT_PRIMARY - system buttons at the bottom
-                            SO_PORTRIAT_SECONDARY - system buttons at the top
-                            SO_LANDSCAPE_PRIMARY - system buttons at the right
-                            SO_LANDSCAPE_SECONDARY - system buttons at the left
-        """
-        if orientation == self.SO_PORTRAIT_PRIMARY:
-            data = '0:-90:0'
-        elif orientation == self.SO_PORTRAIT_SECONDARY:
-            data = '0:90:0'
-        elif orientation == self.SO_LANDSCAPE_PRIMARY:
-            data = '0:0:90'
-        elif orientation == self.SO_LANDSCAPE_SECONDARY:
-            data = '0:0:-90'
-        else:
-            raise ValueError('Invalid orientation: %s' % orientation)
-
-        self._set_raw_orientation(data)
-
-    orientation = property(get_orientation, set_orientation)
--- a/testing/marionette/client/marionette/geckoinstance.py
+++ b/testing/marionette/client/marionette/geckoinstance.py
@@ -20,31 +20,30 @@ class GeckoInstance(object):
                       "browser.sessionstore.resume_from_crash": False,
                       "browser.warnOnQuit": False}
 
     def __init__(self, host, port, bin, profile, app_args=None, symbols_path=None,
                   gecko_log=None):
         self.marionette_host = host
         self.marionette_port = port
         self.bin = bin
-        self.profile = profile
+        self.profile_path = profile
         self.app_args = app_args or []
         self.runner = None
         self.symbols_path = symbols_path
         self.gecko_log = gecko_log
 
     def start(self):
-        profile_path = self.profile
         profile_args = {"preferences": self.required_prefs}
-        if not profile_path:
-            runner_class = Runner
+        if not self.profile_path:
             profile_args["restore"] = False
+            profile = Profile(**profile_args)
         else:
-            runner_class = CloneRunner
-            profile_args["path_from"] = profile_path
+            profile_args["path_from"] = self.profile_path
+            profile = Profile.clone(**profile_args)
 
         if self.gecko_log is None:
             self.gecko_log = 'gecko.log'
         elif os.path.isdir(self.gecko_log):
             fname = "gecko-%d.log" % time.time()
             self.gecko_log = os.path.join(self.gecko_log, fname)
 
         self.gecko_log = os.path.realpath(self.gecko_log)
@@ -52,44 +51,39 @@ class GeckoInstance(object):
             os.remove(self.gecko_log)
 
         env = os.environ.copy()
 
         # environment variables needed for crashreporting
         # https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
         env.update({ 'MOZ_CRASHREPORTER': '1',
                      'MOZ_CRASHREPORTER_NO_REPORT': '1', })
-        self.runner = runner_class.create(
+        self.runner = Runner(
             binary=self.bin,
-            profile_args=profile_args,
+            profile=profile,
             cmdargs=['-no-remote', '-marionette'] + self.app_args,
             env=env,
             symbols_path=self.symbols_path,
-            kp_kwargs={
+            process_args={
                 'processOutputLine': [NullOutput()],
                 'logfile': self.gecko_log})
         self.runner.start()
 
     def check_for_crashes(self):
         return self.runner.check_for_crashes()
 
     def close(self):
-        self.runner.stop()
-        self.runner.cleanup()
+        if self.runner:
+            self.runner.stop()
+            self.runner.cleanup()
 
 
 class B2GDesktopInstance(GeckoInstance):
+    required_prefs = {"focusmanager.testmode": True}
 
-    required_prefs = {"focusmanager.testmode": True}
+
+class NullOutput(object):
+    def __call__(self, line):
+        pass
+
 
 apps = {'b2g': B2GDesktopInstance,
         'b2gdesktop': B2GDesktopInstance}
-
-
-class CloneRunner(Runner):
-
-    profile_class = Profile.clone
-
-
-class NullOutput(object):
-
-    def __call__(self, line):
-        pass
--- a/testing/marionette/client/marionette/marionette.py
+++ b/testing/marionette/client/marionette/marionette.py
@@ -1,37 +1,30 @@
 # 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/.
 
 import ConfigParser
 import datetime
 import os
 import socket
-import sys
+import StringIO
 import time
 import traceback
 import base64
 
 from application_cache import ApplicationCache
 from decorators import do_crash_check
-from emulator import Emulator
-from emulator_screen import EmulatorScreen
-from errors import (
-        ErrorCodes, MarionetteException, InstallGeckoError, TimeoutException, InvalidResponseException,
-        JavascriptException, NoSuchElementException, XPathLookupException, NoSuchWindowException,
-        StaleElementException, ScriptTimeoutException, ElementNotVisibleException,
-        NoSuchFrameException, InvalidElementStateException, NoAlertPresentException,
-        InvalidCookieDomainException, UnableToSetCookieException, InvalidSelectorException,
-        MoveTargetOutOfBoundsException, FrameSendNotInitializedError, FrameSendFailureError
-        )
 from keys import Keys
 from marionette_transport import MarionetteTransport
 
+from mozrunner import B2GEmulatorRunner
+
 import geckoinstance
+import errors
 
 class HTMLElement(object):
     """
     Represents a DOM Element.
     """
 
     CLASS = "class name"
     SELECTOR = "css selector"
@@ -446,51 +439,43 @@ class Marionette(object):
     Represents a Marionette connection to a browser or device.
     """
 
     CONTEXT_CHROME = 'chrome' # non-browser content: windows, dialogs, etc.
     CONTEXT_CONTENT = 'content' # browser content: iframes, divs, etc.
     TIMEOUT_SEARCH = 'implicit'
     TIMEOUT_SCRIPT = 'script'
     TIMEOUT_PAGE = 'page load'
-    SCREEN_ORIENTATIONS = {"portrait": EmulatorScreen.SO_PORTRAIT_PRIMARY,
-                           "landscape": EmulatorScreen.SO_LANDSCAPE_PRIMARY,
-                           "portrait-primary": EmulatorScreen.SO_PORTRAIT_PRIMARY,
-                           "landscape-primary": EmulatorScreen.SO_LANDSCAPE_PRIMARY,
-                           "portrait-secondary": EmulatorScreen.SO_PORTRAIT_SECONDARY,
-                           "landscape-secondary": EmulatorScreen.SO_LANDSCAPE_SECONDARY}
 
     def __init__(self, host='localhost', port=2828, app=None, app_args=None, bin=None,
-                 profile=None, emulator=None, sdcard=None, emulatorBinary=None,
-                 emulatorImg=None, emulator_res=None, gecko_path=None,
-                 connectToRunningEmulator=False, homedir=None, baseurl=None,
-                 noWindow=False, logcat_dir=None, busybox=None, symbols_path=None,
-                 timeout=None, device_serial=None, gecko_log=None):
+                 profile=None, emulator=None, sdcard=None, emulator_img=None,
+                 emulator_binary=None, emulator_res=None, connect_to_running_emulator=False,
+                 gecko_log=None, homedir=None, baseurl=None, no_window=False, logdir=None,
+                 busybox=None, symbols_path=None, timeout=None, device_serial=None,
+                 adb_path=None):
         self.host = host
         self.port = self.local_port = port
         self.bin = bin
         self.instance = None
-        self.profile = profile
         self.session = None
         self.window = None
+        self.runner = None
         self.emulator = None
         self.extra_emulators = []
-        self.homedir = homedir
         self.baseurl = baseurl
-        self.noWindow = noWindow
-        self.logcat_dir = logcat_dir
+        self.no_window = no_window
         self._test_name = None
         self.timeout = timeout
         self.device_serial = device_serial
 
         if bin:
             port = int(self.port)
             if not Marionette.is_port_available(port, host=self.host):
                 ex_msg = "%s:%d is unavailable." % (self.host, port)
-                raise MarionetteException(message=ex_msg)
+                raise errors.MarionetteException(message=ex_msg)
             if app:
                 # select instance class for the given app
                 try:
                     instance_class = geckoinstance.apps[app]
                 except KeyError:
                     msg = 'Application "%s" unknown (should be one of %s)'
                     raise NotImplementedError(msg % (app, geckoinstance.apps.keys()))
             else:
@@ -499,62 +484,66 @@ class Marionette(object):
                     config.read(os.path.join(os.path.dirname(bin), 'application.ini'))
                     app = config.get('App', 'Name')
                     instance_class = geckoinstance.apps[app.lower()]
                 except (ConfigParser.NoOptionError,
                         ConfigParser.NoSectionError,
                         KeyError):
                     instance_class = geckoinstance.GeckoInstance
             self.instance = instance_class(host=self.host, port=self.port,
-                                           bin=self.bin, profile=self.profile,
+                                           bin=self.bin, profile=profile,
                                            app_args=app_args, symbols_path=symbols_path,
                                            gecko_log=gecko_log)
             self.instance.start()
             assert(self.wait_for_port()), "Timed out waiting for port!"
 
         if emulator:
-            self.emulator = Emulator(homedir=homedir,
-                                     noWindow=self.noWindow,
-                                     logcat_dir=self.logcat_dir,
-                                     arch=emulator,
-                                     sdcard=sdcard,
-                                     symbols_path=symbols_path,
-                                     emulatorBinary=emulatorBinary,
-                                     userdata=emulatorImg,
-                                     res=emulator_res)
+            self.runner = B2GEmulatorRunner(b2g_home=homedir,
+                                            no_window=self.no_window,
+                                            logdir=logdir,
+                                            arch=emulator,
+                                            sdcard=sdcard,
+                                            symbols_path=symbols_path,
+                                            binary=emulator_binary,
+                                            userdata=emulator_img,
+                                            resolution=emulator_res,
+                                            profile=profile,
+                                            adb_path=adb_path)
+            self.emulator = self.runner.device
             self.emulator.start()
             self.port = self.emulator.setup_port_forwarding(self.port)
-            assert(self.emulator.wait_for_port()), "Timed out waiting for port!"
+            assert(self.emulator.wait_for_port(self.port)), "Timed out waiting for port!"
 
-        if connectToRunningEmulator:
-            self.emulator = Emulator(homedir=homedir,
-                                     logcat_dir=self.logcat_dir)
+        if connect_to_running_emulator:
+            self.runner = B2GEmulatorRunner(b2g_home=homedir,
+                                            logdir=logdir)
+            self.emulator = self.runner.device
             self.emulator.connect()
             self.port = self.emulator.setup_port_forwarding(self.port)
-            assert(self.emulator.wait_for_port()), "Timed out waiting for port!"
+            assert(self.emulator.wait_for_port(self.port)), "Timed out waiting for port!"
 
         self.client = MarionetteTransport(self.host, self.port)
 
         if emulator:
-            self.emulator.setup(self,
-                                gecko_path=gecko_path,
-                                busybox=busybox)
+            if busybox:
+                self.emulator.install_busybox(busybox=busybox)
+            self.emulator.wait_for_system_message(self)
 
     def cleanup(self):
         if self.session:
             try:
                 self.delete_session()
-            except (MarionetteException, socket.error, IOError):
+            except (errors.MarionetteException, socket.error, IOError):
                 # These exceptions get thrown if the Marionette server
                 # hit an exception/died or the connection died. We can
                 # do no further server-side cleanup in this case.
                 pass
             self.session = None
-        if self.emulator:
-            self.emulator.close()
+        if self.runner:
+            self.runner.cleanup()
         if self.instance:
             self.instance.close()
         for qemu in self.extra_emulators:
             qemu.emulator.close()
 
     def __del__(self):
         self.cleanup()
 
@@ -565,36 +554,16 @@ class Marionette(object):
         try:
             s.bind((host, port))
             return True
         except socket.error:
             return False
         finally:
             s.close()
 
-    @classmethod
-    def getMarionetteOrExit(cls, *args, **kwargs):
-        try:
-            m = cls(*args, **kwargs)
-            return m
-        except InstallGeckoError:
-            # Bug 812395 - the process of installing gecko into the emulator
-            # and then restarting B2G tickles some bug in the emulator/b2g
-            # that intermittently causes B2G to fail to restart.  To work
-            # around this in TBPL runs, we will fail gracefully from this
-            # error so that the mozharness script can try the run again.
-
-            # This string will get caught by mozharness and will cause it
-            # to retry the tests.
-            print "Error installing gecko!"
-
-            # Exit without a normal exception to prevent mozharness from
-            # flagging the error.
-            sys.exit()
-
     def wait_for_port(self, timeout=60):
         starttime = datetime.datetime.now()
         while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout):
             try:
                 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                 sock.connect((self.host, self.port))
                 data = sock.recv(16)
                 sock.close()
@@ -604,32 +573,32 @@ class Marionette(object):
             except socket.error:
                 pass
             time.sleep(1)
         return False
 
     @do_crash_check
     def _send_message(self, command, response_key="ok", **kwargs):
         if not self.session and command != "newSession":
-            raise MarionetteException("Please start a session")
+            raise errors.MarionetteException("Please start a session")
 
         message = {"name": command}
         if self.session:
             message["sessionId"] = self.session
         if kwargs:
             message["parameters"] = kwargs
 
         try:
             response = self.client.send(message)
         except socket.timeout:
             self.session = None
             self.window = None
             self.client.close()
-            raise TimeoutException(
-                "Connection timed out", status=ErrorCodes.TIMEOUT)
+            raise errors.TimeoutException(
+                "Connection timed out", status=errors.ErrorCodes.TIMEOUT)
 
         # Process any emulator commands that are sent from a script
         # while it's executing.
         while True:
             if response.get("emulator_cmd"):
                 response = self._handle_emulator_cmd(response)
                 continue;
 
@@ -641,97 +610,97 @@ class Marionette(object):
 
         if response_key in response:
             return response[response_key]
         self._handle_error(response)
 
     def _handle_emulator_cmd(self, response):
         cmd = response.get("emulator_cmd")
         if not cmd or not self.emulator:
-            raise MarionetteException(
+            raise errors.MarionetteException(
                 "No emulator in this test to run command against")
         cmd = cmd.encode("ascii")
         result = self.emulator._run_telnet(cmd)
         return self.client.send({"name": "emulatorCmdResult",
                                  "id": response.get("id"),
                                  "result": result})
 
     def _handle_emulator_shell(self, response):
         args = response.get("emulator_shell")
         if not isinstance(args, list) or not self.emulator:
-            raise MarionetteException(
+            raise errors.MarionetteException(
                 "No emulator in this test to run shell command against")
-        result = self.emulator._run_shell(args)
+        buf = StringIO.StringIO()
+        self.emulator.dm.shell(args, buf)
+        result = str(buf.getvalue()[0:-1]).rstrip().splitlines()
+        buf.close()
         return self.client.send({"name": "emulatorCmdResult",
                                  "id": response.get("id"),
                                  "result": result})
 
     def _handle_error(self, response):
         if 'error' in response and isinstance(response['error'], dict):
             status = response['error'].get('status', 500)
             message = response['error'].get('message')
             stacktrace = response['error'].get('stacktrace')
             # status numbers come from
             # http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
-            if status == ErrorCodes.NO_SUCH_ELEMENT:
-                raise NoSuchElementException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.NO_SUCH_FRAME:
-                raise NoSuchFrameException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.STALE_ELEMENT_REFERENCE:
-                raise StaleElementException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.ELEMENT_NOT_VISIBLE:
-                raise ElementNotVisibleException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.INVALID_ELEMENT_STATE:
-                raise InvalidElementStateException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.UNKNOWN_ERROR:
-                raise MarionetteException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.ELEMENT_IS_NOT_SELECTABLE:
-                raise ElementNotSelectableException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.JAVASCRIPT_ERROR:
-                raise JavascriptException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.XPATH_LOOKUP_ERROR:
-                raise XPathLookupException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.TIMEOUT:
-                raise TimeoutException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.NO_SUCH_WINDOW:
-                raise NoSuchWindowException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.INVALID_COOKIE_DOMAIN:
-                raise InvalidCookieDomainException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.UNABLE_TO_SET_COOKIE:
-                raise UnableToSetCookieException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.NO_ALERT_OPEN:
-                raise NoAlertPresentException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.SCRIPT_TIMEOUT:
-                raise ScriptTimeoutException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.INVALID_SELECTOR \
-                 or status == ErrorCodes.INVALID_XPATH_SELECTOR \
-                 or status == ErrorCodes.INVALID_XPATH_SELECTOR_RETURN_TYPER:
-                raise InvalidSelectorException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.MOVE_TARGET_OUT_OF_BOUNDS:
-                raise MoveTargetOutOfBoundsException(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.FRAME_SEND_NOT_INITIALIZED_ERROR:
-                raise FrameSendNotInitializedError(message=message, status=status, stacktrace=stacktrace)
-            elif status == ErrorCodes.FRAME_SEND_FAILURE_ERROR:
-                raise FrameSendFailureError(message=message, status=status, stacktrace=stacktrace)
+            if status == errors.ErrorCodes.NO_SUCH_ELEMENT:
+                raise errors.NoSuchElementException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.NO_SUCH_FRAME:
+                raise errors.NoSuchFrameException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.STALE_ELEMENT_REFERENCE:
+                raise errors.StaleElementException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.ELEMENT_NOT_VISIBLE:
+                raise errors.ElementNotVisibleException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.INVALID_ELEMENT_STATE:
+                raise errors.InvalidElementStateException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.UNKNOWN_ERROR:
+                raise errors.MarionetteException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.ELEMENT_IS_NOT_SELECTABLE:
+                raise errors.ElementNotSelectableException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.JAVASCRIPT_ERROR:
+                raise errors.JavascriptException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.XPATH_LOOKUP_ERROR:
+                raise errors.XPathLookupException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.TIMEOUT:
+                raise errors.TimeoutException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.NO_SUCH_WINDOW:
+                raise errors.NoSuchWindowException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.INVALID_COOKIE_DOMAIN:
+                raise errors.InvalidCookieDomainException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.UNABLE_TO_SET_COOKIE:
+                raise errors.UnableToSetCookieException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.NO_ALERT_OPEN:
+                raise errors.NoAlertPresentException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.SCRIPT_TIMEOUT:
+                raise errors.ScriptTimeoutException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.INVALID_SELECTOR \
+                 or status == errors.ErrorCodes.INVALID_XPATH_SELECTOR \
+                 or status == errors.ErrorCodes.INVALID_XPATH_SELECTOR_RETURN_TYPER:
+                raise errors.InvalidSelectorException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.MOVE_TARGET_OUT_OF_BOUNDS:
+                raise errors.MoveTargetOutOfBoundsException(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.FRAME_SEND_NOT_INITIALIZED_ERROR:
+                raise errors.FrameSendNotInitializedError(message=message, status=status, stacktrace=stacktrace)
+            elif status == errors.ErrorCodes.FRAME_SEND_FAILURE_ERROR:
+                raise errors.FrameSendFailureError(message=message, status=status, stacktrace=stacktrace)
             else:
-                raise MarionetteException(message=message, status=status, stacktrace=stacktrace)
-        raise MarionetteException(message=response, status=500)
+                raise errors.MarionetteException(message=message, status=status, stacktrace=stacktrace)
+        raise errors.MarionetteException(message=response, status=500)
 
     def check_for_crash(self):
         returncode = None
         name = None
         crashed = False
-        if self.emulator:
-            if self.emulator.check_for_crash():
+        if self.runner:
+            if self.runner.check_for_crashes():
                 returncode = self.emulator.proc.returncode
                 name = 'emulator'
                 crashed = True
-
-            if self.emulator.check_for_minidumps():
-                crashed = True
         elif self.instance:
             if self.instance.check_for_crashes():
                 crashed = True
         if returncode is not None:
             print ('PROCESS-CRASH | %s | abnormal termination with exit code %d' %
                 (name, returncode))
         return crashed
 
@@ -1454,9 +1423,9 @@ class Marionette(object):
         respectively, and "portrait-secondary" as well as
         "landscape-secondary".
 
         :param orientation: The orientation to lock the screen in.
 
         """
         self._send_message("setScreenOrientation", "ok", orientation=orientation)
         if self.emulator:
-            self.emulator.screen.orientation = self.SCREEN_ORIENTATIONS[orientation.lower()]
+            self.emulator.screen.orientation = orientation.lower()
--- a/testing/marionette/client/marionette/runner/base.py
+++ b/testing/marionette/client/marionette/runner/base.py
@@ -362,39 +362,39 @@ class BaseMarionetteOptions(OptionParser
                         dest='emulator',
                         choices=['x86', 'arm'],
                         help='if no --address is given, then the harness will launch a B2G emulator on which to run '
                              'emulator tests. if --address is given, then the harness assumes you are running an '
                              'emulator already, and will run the emulator tests using that emulator. you need to '
                              'specify which architecture to emulate for both cases')
         self.add_option('--emulator-binary',
                         action='store',
-                        dest='emulatorBinary',
+                        dest='emulator_binary',
                         help='launch a specific emulator binary rather than launching from the B2G built emulator')
         self.add_option('--emulator-img',
                         action='store',
-                        dest='emulatorImg',
+                        dest='emulator_img',
                         help='use a specific image file instead of a fresh one')
         self.add_option('--emulator-res',
                         action='store',
                         dest='emulator_res',
                         type='str',
                         help='set a custom resolution for the emulator'
                              'Example: "480x800"')
         self.add_option('--sdcard',
                         action='store',
                         dest='sdcard',
                         help='size of sdcard to create for the emulator')
         self.add_option('--no-window',
                         action='store_true',
-                        dest='noWindow',
+                        dest='no_window',
                         default=False,
                         help='when Marionette launches an emulator, start it with the -no-window argument')
         self.add_option('--logcat-dir',
-                        dest='logcat_dir',
+                        dest='logdir',
                         action='store',
                         help='directory to store logcat dump files')
         self.add_option('--address',
                         dest='address',
                         action='store',
                         help='host:port of running Gecko instance to connect to')
         self.add_option('--device',
                         dest='device_serial',
@@ -436,20 +436,16 @@ class BaseMarionetteOptions(OptionParser
                         action='store',
                         type=int,
                         default=0,
                         help='number of times to repeat the test(s)')
         self.add_option('-x', '--xml-output',
                         action='store',
                         dest='xml_output',
                         help='xml output')
-        self.add_option('--gecko-path',
-                        dest='gecko_path',
-                        action='store',
-                        help='path to b2g gecko binaries that should be installed on the device or emulator')
         self.add_option('--testvars',
                         dest='testvars',
                         action='store',
                         help='path to a json file with any test data required')
         self.add_option('--tree',
                         dest='tree',
                         action='store',
                         default='b2g',
@@ -520,18 +516,18 @@ class BaseMarionetteOptions(OptionParser
             print 'can\'t specify both --emulator and --binary'
             sys.exit(1)
 
         if not options.es_servers:
             options.es_servers = ['elasticsearch-zlb.dev.vlan81.phx.mozilla.com:9200',
                                   'elasticsearch-zlb.webapp.scl3.mozilla.com:9200']
 
         # default to storing logcat output for emulator runs
-        if options.emulator and not options.logcat_dir:
-            options.logcat_dir = 'logcat'
+        if options.emulator and not options.logdir:
+            options.logdir = 'logcat'
 
         # check for valid resolution string, strip whitespaces
         try:
             if options.emulator_res:
                 dims = options.emulator_res.split('x')
                 assert len(dims) == 2
                 width = str(int(dims[0]))
                 height = str(int(dims[1]))
@@ -557,48 +553,47 @@ class BaseMarionetteOptions(OptionParser
 
         return (options, tests)
 
 
 class BaseMarionetteTestRunner(object):
 
     textrunnerclass = MarionetteTextTestRunner
 
-    def __init__(self, address=None, emulator=None, emulatorBinary=None,
-                 emulatorImg=None, emulator_res='480x800', homedir=None,
+    def __init__(self, address=None, emulator=None, emulator_binary=None,
+                 emulator_img=None, emulator_res='480x800', homedir=None,
                  app=None, app_args=None, bin=None, profile=None, autolog=False,
-                 revision=None, logger=None, testgroup="marionette", noWindow=False,
-                 logcat_dir=None, xml_output=None, repeat=0, gecko_path=None,
+                 revision=None, logger=None, testgroup="marionette", no_window=False,
+                 logdir=None, xml_output=None, repeat=0,
                  testvars=None, tree=None, type=None, device_serial=None,
                  symbols_path=None, timeout=None, es_servers=None, shuffle=False,
                  shuffle_seed=random.randint(0, sys.maxint), sdcard=None,
                  this_chunk=1, total_chunks=1, sources=None, server_root=None,
                  gecko_log=None,
                  **kwargs):
         self.address = address
         self.emulator = emulator
-        self.emulatorBinary = emulatorBinary
-        self.emulatorImg = emulatorImg
+        self.emulator_binary = emulator_binary
+        self.emulator_img = emulator_img
         self.emulator_res = emulator_res
         self.homedir = homedir
         self.app = app
         self.app_args = app_args or []
         self.bin = bin
         self.profile = profile
         self.autolog = autolog
         self.testgroup = testgroup
         self.revision = revision
         self.logger = logger
-        self.noWindow = noWindow
+        self.no_window = no_window
         self.httpd = None
         self.marionette = None
-        self.logcat_dir = logcat_dir
+        self.logdir = logdir
         self.xml_output = xml_output
         self.repeat = repeat
-        self.gecko_path = gecko_path
         self.testvars = {}
         self.test_kwargs = kwargs
         self.tree = tree
         self.type = type
         self.device_serial = device_serial
         self.symbols_path = symbols_path
         self.timeout = timeout
         self._device = None
@@ -635,19 +630,19 @@ class BaseMarionetteTestRunner(object):
 
         self.reset_test_stats()
 
         if self.logger is None:
             self.logger = logging.getLogger('Marionette')
             self.logger.setLevel(logging.INFO)
             self.logger.addHandler(logging.StreamHandler())
 
-        if self.logcat_dir:
-            if not os.access(self.logcat_dir, os.F_OK):
-                os.mkdir(self.logcat_dir)
+        if self.logdir:
+            if not os.access(self.logdir, os.F_OK):
+                os.mkdir(self.logdir)
 
         # for XML output
         self.testvars['xml_output'] = self.xml_output
         self.results = []
 
     @property
     def capabilities(self):
         if self._capabilities:
@@ -712,18 +707,17 @@ class BaseMarionetteTestRunner(object):
                 'bin': self.bin,
                 'profile': self.profile,
                 'gecko_log': self.gecko_log,
             })
 
         if self.emulator:
             kwargs.update({
                 'homedir': self.homedir,
-                'logcat_dir': self.logcat_dir,
-                'gecko_path': self.gecko_path,
+                'logdir': self.logdir,
             })
 
         if self.address:
             host, port = self.address.split(':')
             kwargs.update({
                 'host': host,
                 'port': int(port),
             })
@@ -736,33 +730,33 @@ class BaseMarionetteTestRunner(object):
                     connection = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
                     connection.connect((host,int(port)))
                     connection.close()
                 except Exception, e:
                     raise Exception("Connection attempt to %s:%s failed with error: %s" %(host,port,e))
         elif self.emulator:
             kwargs.update({
                 'emulator': self.emulator,
-                'emulatorBinary': self.emulatorBinary,
-                'emulatorImg': self.emulatorImg,
+                'emulator_binary': self.emulator_binary,
+                'emulator_img': self.emulator_img,
                 'emulator_res': self.emulator_res,
-                'noWindow': self.noWindow,
+                'no_window': self.no_window,
                 'sdcard': self.sdcard,
             })
         return kwargs
 
     def start_marionette(self):
         self.marionette = Marionette(**self._build_kwargs())
 
     def post_to_autolog(self, elapsedtime):
         self.logger.info('posting results to autolog')
 
         logfile = None
         if self.emulator:
-            filename = os.path.join(os.path.abspath(self.logcat_dir),
+            filename = os.path.join(os.path.abspath(self.logdir),
                                     "emulator-%d.log" % self.marionette.emulator.port)
             if os.access(filename, os.F_OK):
                 logfile = filename
 
         for es_server in self.es_servers:
 
             # This is all autolog stuff.
             # See: https://wiki.mozilla.org/Auto-tools/Projects/Autolog
--- a/testing/marionette/client/marionette/runner/mixins/b2g.py
+++ b/testing/marionette/client/marionette/runner/mixins/b2g.py
@@ -4,24 +4,20 @@
 
 import mozdevice
 import os
 import re
 
 
 def get_dm(marionette=None,**kwargs):
     dm_type = os.environ.get('DM_TRANS', 'adb')
-    if marionette and marionette.emulator:
-        adb_path = marionette.emulator.b2g.adb_path
-        return mozdevice.DeviceManagerADB(adbPath=adb_path,
-                                          deviceSerial='emulator-%d' % marionette.emulator.port,
-                                          **kwargs)
+    if marionette and hasattr(marionette.runner, 'device'):
+        return marionette.runner.app_ctx.dm
     elif marionette and marionette.device_serial and dm_type == 'adb':
-        return mozdevice.DeviceManagerADB(deviceSerial=marionette.device_serial,
-                                          **kwargs)
+        return mozdevice.DeviceManagerADB(deviceSerial=marionette.device_serial, **kwargs)
     else:
         if dm_type == 'adb':
             return mozdevice.DeviceManagerADB(**kwargs)
         elif dm_type == 'sut':
             host = os.environ.get('TEST_DEVICE')
             if not host:
                 raise Exception('Must specify host with SUT!')
             return mozdevice.DeviceManagerSUT(host=host)
--- a/testing/marionette/client/marionette/tests/unit/test_clearing.py
+++ b/testing/marionette/client/marionette/tests/unit/test_clearing.py
@@ -1,14 +1,14 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
-from marionette import InvalidElementStateException
+from errors import InvalidElementStateException
 
 class TestClear(MarionetteTestCase):
     def testWriteableTextInputShouldClear(self):
         test_html = self.marionette.absolute_url("test_clearing.html")
         self.marionette.navigate(test_html)
         element = self.marionette.find_element("id", "writableTextInput")
         element.clear()
         self.assertEqual("", element.get_attribute("value"))
--- a/testing/marionette/client/marionette/tests/unit/test_element_touch.py
+++ b/testing/marionette/client/marionette/tests/unit/test_element_touch.py
@@ -1,14 +1,14 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
-from marionette import MarionetteException
+from errors import MarionetteException
 
 class testElementTouch(MarionetteTestCase):
     def test_touch(self):
       testAction = self.marionette.absolute_url("testAction.html")
       self.marionette.navigate(testAction)
       button = self.marionette.find_element("id", "button1")
       button.tap()
       expected = "button1-touchstart-touchend-mousemove-mousedown-mouseup-click"
--- a/testing/marionette/client/marionette/tests/unit/test_emulator.py
+++ b/testing/marionette/client/marionette/tests/unit/test_emulator.py
@@ -1,30 +1,30 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
-from marionette import JavascriptException, MarionetteException
+from errors import JavascriptException, MarionetteException
 
 
 class TestEmulatorContent(MarionetteTestCase):
 
     def test_emulator_cmd(self):
         self.marionette.set_script_timeout(10000)
         expected = ["<build>",
                     "OK"]
         result = self.marionette.execute_async_script("""
         runEmulatorCmd("avd name", marionetteScriptFinished)
         """);
         self.assertEqual(result, expected)
 
     def test_emulator_shell(self):
         self.marionette.set_script_timeout(10000)
-        expected = ["Hello World!", ""]
+        expected = ["Hello World!"]
         result = self.marionette.execute_async_script("""
         runEmulatorShell(["echo", "Hello World!"], marionetteScriptFinished)
         """);
         self.assertEqual(result, expected)
 
     def test_emulator_order(self):
         self.marionette.set_script_timeout(10000)
         self.assertRaises(MarionetteException,
--- a/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py
+++ b/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py
@@ -1,14 +1,14 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
-from marionette import JavascriptException, MarionetteException, ScriptTimeoutException
+from errors import JavascriptException, MarionetteException, ScriptTimeoutException
 import time
 
 
 class TestExecuteAsyncContent(MarionetteTestCase):
     def setUp(self):
         super(TestExecuteAsyncContent, self).setUp()
         self.marionette.set_script_timeout(1000)
 
--- a/testing/marionette/client/marionette/tests/unit/test_execute_isolate.py
+++ b/testing/marionette/client/marionette/tests/unit/test_execute_isolate.py
@@ -1,14 +1,14 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase, skip_if_b2g
-from marionette import JavascriptException, MarionetteException, ScriptTimeoutException
+from errors import JavascriptException, MarionetteException, ScriptTimeoutException
 
 class TestExecuteIsolationContent(MarionetteTestCase):
     def setUp(self):
         super(TestExecuteIsolationContent, self).setUp()
         self.content = True
 
     def test_execute_async_isolate(self):
         # Results from one execute call that has timed out should not
--- a/testing/marionette/client/marionette/tests/unit/test_execute_script.py
+++ b/testing/marionette/client/marionette/tests/unit/test_execute_script.py
@@ -1,16 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import urllib
 
 from by import By
-from marionette import JavascriptException, MarionetteException
+from errors import JavascriptException, MarionetteException
 from marionette_test import MarionetteTestCase
 
 def inline(doc):
     return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
 
 elements = inline("<p>foo</p> <p>bar</p>")
 
 class TestExecuteContent(MarionetteTestCase):
--- a/testing/marionette/client/marionette/tests/unit/test_findelement.py
+++ b/testing/marionette/client/marionette/tests/unit/test_findelement.py
@@ -1,16 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from marionette_test import MarionetteTestCase
 from marionette import HTMLElement
 from by import By
-from marionette import NoSuchElementException
+from errors import NoSuchElementException
 
 
 class TestElements(MarionetteTestCase):
     def test_id(self):
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
         el = self.marionette.execute_script("return window.document.getElementById('mozLink');")
         found_el = self.marionette.find_element(By.ID, "mozLink")
--- a/testing/marionette/client/marionette/tests/unit/test_findelement_chrome.py
+++ b/testing/marionette/client/marionette/tests/unit/test_findelement_chrome.py
@@ -1,16 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from marionette_test import MarionetteTestCase
 from marionette import HTMLElement
 from by import By
-from marionette import NoSuchElementException
+from errors import NoSuchElementException
 
 
 class TestElementsChrome(MarionetteTestCase):
     def setUp(self):
         MarionetteTestCase.setUp(self)
         self.marionette.set_context("chrome")
         self.win = self.marionette.current_window_handle
         self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');")
--- a/testing/marionette/client/marionette/tests/unit/test_implicit_waits.py
+++ b/testing/marionette/client/marionette/tests/unit/test_implicit_waits.py
@@ -1,14 +1,14 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
-from marionette import NoSuchElementException
+from errors import NoSuchElementException
 
 class TestImplicitWaits(MarionetteTestCase):
     def testShouldImplicitlyWaitForASingleElement(self):
         test_html = self.marionette.absolute_url("test_dynamic.html")
         self.marionette.navigate(test_html)
         add = self.marionette.find_element("id", "adder")
         self.marionette.set_search_timeout("3000")
         add.click()
--- a/testing/marionette/client/marionette/tests/unit/test_navigation.py
+++ b/testing/marionette/client/marionette/tests/unit/test_navigation.py
@@ -1,15 +1,14 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
-from marionette import MarionetteException
-from marionette import TimeoutException
+from errors import MarionetteException, TimeoutException
 
 class TestNavigate(MarionetteTestCase):
     def test_navigate(self):
         self.assertTrue(self.marionette.execute_script("window.location.href = 'about:blank'; return true;"))
         self.assertEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
         self.assertNotEqual("about:blank", self.marionette.execute_script("return window.location.href;"))
--- a/testing/marionette/client/marionette/tests/unit/test_screen_orientation.py
+++ b/testing/marionette/client/marionette/tests/unit/test_screen_orientation.py
@@ -1,17 +1,17 @@
 # -*- fill-column: 100; comment-column: 100; -*-
 
 # 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/.
 
-from emulator_screen import EmulatorScreen
-from marionette import MarionetteException
+from errors import MarionetteException
 from marionette_test import MarionetteTestCase
+from mozrunner.devices.emulator_screen import EmulatorScreen
 
 default_orientation = "portrait-primary"
 unknown_orientation = "Unknown screen orientation: %s"
 
 class TestScreenOrientation(MarionetteTestCase):
     def tearDown(self):
         self.marionette.set_orientation(default_orientation)
         self.assertEqual(self.marionette.orientation, default_orientation, "invalid state")
--- a/testing/marionette/client/marionette/tests/unit/test_simpletest_sanity.py
+++ b/testing/marionette/client/marionette/tests/unit/test_simpletest_sanity.py
@@ -1,14 +1,14 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
-from marionette import JavascriptException, MarionetteException, ScriptTimeoutException
+from errors import JavascriptException, MarionetteException, ScriptTimeoutException
 
 class SimpletestSanityTest(MarionetteTestCase):
 
     callFinish = "return finish();"
 
     def test_is(self):
         def runtests():
             sentFail1 = "is(true, false, 'isTest1', TEST_UNEXPECTED_FAIL, TEST_PASS);" + self.callFinish
--- a/testing/marionette/client/marionette/tests/unit/test_single_finger.py
+++ b/testing/marionette/client/marionette/tests/unit/test_single_finger.py
@@ -1,15 +1,15 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
 from marionette import Actions
-from marionette import MarionetteException
+from errors import MarionetteException
 #add this directory to the path
 import os
 import sys
 sys.path.append(os.path.dirname(__file__))
 from single_finger_functions import (
         chain, chain_flick, context_menu, double_tap,
         long_press_action, long_press_on_xy_action,
         move_element, move_element_offset, press_release, single_tap, wait,
--- a/testing/marionette/client/marionette/tests/unit/test_single_finger_desktop.py
+++ b/testing/marionette/client/marionette/tests/unit/test_single_finger_desktop.py
@@ -1,11 +1,11 @@
 from marionette_test import MarionetteTestCase
 from marionette import Actions
-from marionette import MarionetteException
+from errors import MarionetteException
 #add this directory to the path
 import os
 import sys
 sys.path.append(os.path.dirname(__file__))
 from single_finger_functions import (
         chain, chain_flick, context_menu, double_tap,
         long_press_action, long_press_on_xy_action,
         move_element, move_element_offset, press_release, single_tap, wait,
--- a/testing/marionette/client/marionette/tests/unit/test_specialpowers.py
+++ b/testing/marionette/client/marionette/tests/unit/test_specialpowers.py
@@ -1,14 +1,14 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
-from marionette import JavascriptException, MarionetteException
+from errors import JavascriptException, MarionetteException
 
 class TestSpecialPowersContent(MarionetteTestCase):
 
     testpref = "testing.marionette.contentcharpref"
     testvalue = "blabla"
 
     def test_prefs(self):
         result = self.marionette.execute_script("""
--- a/testing/marionette/client/marionette/tests/unit/test_switch_frame.py
+++ b/testing/marionette/client/marionette/tests/unit/test_switch_frame.py
@@ -1,14 +1,14 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
-from marionette import JavascriptException
+from errors import JavascriptException
 
 
 class TestSwitchFrame(MarionetteTestCase):
     def test_switch_simple(self):
         start_url = "test_iframe.html"
         verify_title = "Marionette IFrame Test"
         verify_url = "test.html"
         test_html = self.marionette.absolute_url(start_url)
--- a/testing/marionette/client/marionette/tests/unit/test_switch_frame_chrome.py
+++ b/testing/marionette/client/marionette/tests/unit/test_switch_frame_chrome.py
@@ -1,14 +1,14 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
-from marionette import JavascriptException
+from errors import JavascriptException
 
 class TestSwitchFrameChrome(MarionetteTestCase):
     def setUp(self):
         MarionetteTestCase.setUp(self)
         self.marionette.set_context("chrome")
         self.win = self.marionette.current_window_handle
         self.marionette.execute_script("window.open('chrome://marionette/content/test.xul', 'foo', 'chrome,centerscreen');")
         self.marionette.switch_to_window('foo')
--- a/testing/marionette/client/marionette/tests/unit/test_timeouts.py
+++ b/testing/marionette/client/marionette/tests/unit/test_timeouts.py
@@ -1,16 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import os
 from marionette_test import MarionetteTestCase
 from marionette import HTMLElement
-from marionette import NoSuchElementException, JavascriptException, MarionetteException, ScriptTimeoutException
+from errors import NoSuchElementException, JavascriptException, MarionetteException, ScriptTimeoutException
 
 class TestTimeouts(MarionetteTestCase):
     def test_pagetimeout_notdefinetimeout_pass(self):
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
 
     def test_pagetimeout_fail(self):
         self.marionette.timeouts("page load", 0)
--- a/testing/marionette/client/marionette/tests/unit/test_typing.py
+++ b/testing/marionette/client/marionette/tests/unit/test_typing.py
@@ -1,15 +1,15 @@
 # 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/.
 
 from marionette_test import MarionetteTestCase
 from keys import Keys
-from marionette import ElementNotVisibleException
+from errors import ElementNotVisibleException
 
 
 class TestTyping(MarionetteTestCase):
 
     def testShouldFireKeyPressEvents(self):
         test_html = self.marionette.absolute_url("javascriptPage.html")
         self.marionette.navigate(test_html)
         keyReporter = self.marionette.find_element("id", "keyReporter")
--- a/testing/marionette/client/requirements.txt
+++ b/testing/marionette/client/requirements.txt
@@ -1,12 +1,12 @@
 marionette-transport == 0.2
 manifestparser
 mozhttpd >= 0.5
 mozinfo >= 0.7
 mozprocess >= 0.9
-mozrunner >= 5.15
-mozdevice >= 0.22
+mozrunner >= 6.0
+mozdevice >= 0.37
 moznetwork >= 0.21
 mozcrash >= 0.5
 mozprofile >= 0.7
 moztest >= 0.1
 mozversion >= 0.2
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -173,17 +173,17 @@ class MochitestRunner(MozbuildObject):
         try:
             which.which('adb')
         except which.WhichError:
             # TODO Find adb automatically if it isn't on the path
             print(ADB_NOT_FOUND % ('mochitest-remote', b2g_home))
             return 1
 
         options.b2gPath = b2g_home
-        options.logcat_dir = self.mochitest_dir
+        options.logdir = self.mochitest_dir
         options.httpdPath = self.mochitest_dir
         options.xrePath = xre_path
         return mochitest.run_remote_mochitests(parser, options)
 
     def run_desktop_test(self, context, suite=None, test_paths=None, debugger=None,
         debugger_args=None, slowscript=False, screenshot_on_fail = False, shuffle=False, keep_open=False,
         rerun_failures=False, no_autorun=False, repeat=0, run_until_failure=False,
         slow=False, chunk_by_dir=0, total_chunks=None, this_chunk=None,
@@ -550,19 +550,19 @@ def MochitestCommand(func):
 
 def B2GCommand(func):
     """Decorator that adds shared command arguments to b2g mochitest commands."""
 
     busybox = CommandArgument('--busybox', default=None,
         help='Path to busybox binary to install on device')
     func = busybox(func)
 
-    logcatdir = CommandArgument('--logcat-dir', default=None,
-        help='directory to store logcat dump files')
-    func = logcatdir(func)
+    logdir = CommandArgument('--logdir', default=None,
+        help='directory to store log files')
+    func = logdir(func)
 
     profile = CommandArgument('--profile', default=None,
         help='for desktop testing, the path to the \
               gaia profile to use')
     func = profile(func)
 
     geckopath = CommandArgument('--gecko-path', default=None,
         help='the path to a gecko distribution that should \
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -700,21 +700,21 @@ class B2GOptions(MochitestOptions):
         [["--profile"],
         { "action": "store",
           "type": "string",
           "dest": "profile",
           "help": "for desktop testing, the path to the \
                    gaia profile to use",
           "default": None,
         }],
-        [["--logcat-dir"],
+        [["--logdir"],
         { "action": "store",
           "type": "string",
-          "dest": "logcat_dir",
-          "help": "directory to store logcat dump files",
+          "dest": "logdir",
+          "help": "directory to store log files",
           "default": None,
         }],
         [['--busybox'],
         { "action": 'store',
           "type": 'string',
           "dest": 'busybox',
           "help": "Path to busybox binary to install on device",
           "default": None,
@@ -733,17 +733,16 @@ class B2GOptions(MochitestOptions):
         MochitestOptions.__init__(self)
 
         for option in self.b2g_options:
             self.add_option(*option[0], **option[1])
 
         defaults = {}
         defaults["httpPort"] = DEFAULT_PORTS['http']
         defaults["sslPort"] = DEFAULT_PORTS['https']
-        defaults["remoteTestRoot"] = "/data/local/tests"
         defaults["logFile"] = "mochitest.log"
         defaults["autorun"] = True
         defaults["closeWhenDone"] = True
         defaults["testPath"] = ""
         defaults["extensionsToExclude"] = ["specialpowers"]
         self.set_defaults(**defaults)
 
     def verifyRemoteOptions(self, options):
@@ -752,18 +751,18 @@ class B2GOptions(MochitestOptions):
                 options.remoteWebServer = moznetwork.get_ip()
             else:
                 self.error("You must specify a --remote-webserver=<ip address>")
         options.webServer = options.remoteWebServer
 
         if options.geckoPath and not options.emulator:
             self.error("You must specify --emulator if you specify --gecko-path")
 
-        if options.logcat_dir and not options.emulator:
-            self.error("You must specify --emulator if you specify --logcat-dir")
+        if options.logdir and not options.emulator:
+            self.error("You must specify --emulator if you specify --logdir")
 
         if not os.path.isdir(options.xrePath):
             self.error("--xre-path '%s' is not a directory" % options.xrePath)
         xpcshell = os.path.join(options.xrePath, 'xpcshell')
         if not os.access(xpcshell, os.F_OK):
             self.error('xpcshell not found at %s' % xpcshell)
         if self.elf_arm(xpcshell):
             self.error('--xre-path points to an ARM version of xpcshell; it '
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -1253,33 +1253,28 @@ class Mochitest(MochitestUtilsMixin):
                    'cwd': SCRIPT_DIR,
                    'onTimeout': [timeoutHandler]}
       kp_kwargs['processOutputLine'] = [outputHandler]
 
       # create mozrunner instance and start the system under test process
       self.lastTestSeen = self.test_name
       startTime = datetime.now()
 
-      # b2g desktop requires FirefoxRunner even though appname is b2g
+      # b2g desktop requires Runner even though appname is b2g
       if mozinfo.info.get('appname') == 'b2g' and mozinfo.info.get('toolkit') != 'gonk':
-          runner_cls = mozrunner.FirefoxRunner
+          runner_cls = mozrunner.Runner
       else:
           runner_cls = mozrunner.runners.get(mozinfo.info.get('appname', 'firefox'),
                                              mozrunner.Runner)
       runner = runner_cls(profile=self.profile,
                           binary=cmd,
                           cmdargs=args,
                           env=env,
                           process_class=mozprocess.ProcessHandlerMixin,
-                          kp_kwargs=kp_kwargs,
-                          )
-
-      # XXX work around bug 898379 until mozrunner is updated for m-c; see
-      # https://bugzilla.mozilla.org/show_bug.cgi?id=746243#c49
-      runner.kp_kwargs = kp_kwargs
+                          process_args=kp_kwargs)
 
       # start the runner
       runner.start(debug_args=debug_args,
                    interactive=interactive,
                    outputTimeout=timeout)
       proc = runner.process_handler
       log.info("INFO | runtests.py | Application pid: %d", proc.pid)
 
--- a/testing/mochitest/runtestsb2g.py
+++ b/testing/mochitest/runtestsb2g.py
@@ -4,46 +4,44 @@
 
 import json
 import os
 import posixpath
 import shutil
 import sys
 import tempfile
 import threading
-import time
 import traceback
 
 here = os.path.abspath(os.path.dirname(__file__))
 sys.path.insert(0, here)
 
 from runtests import Mochitest
 from runtests import MochitestUtilsMixin
-from runtests import MochitestOptions
 from runtests import MochitestServer
 from mochitest_options import B2GOptions, MochitestOptions
 
 from marionette import Marionette
 
 from mozdevice import DeviceManagerADB
 from mozprofile import Profile, Preferences
-from mozrunner import B2GRunner
 import mozlog
 import mozinfo
-import moznetwork
 
 log = mozlog.getLogger('Mochitest')
 
 class B2GMochitest(MochitestUtilsMixin):
-    def __init__(self, marionette,
+    marionette = None
+
+    def __init__(self, marionette_args,
                        out_of_process=True,
                        profile_data_dir=None,
                        locations=os.path.join(here, 'server-locations.txt')):
         super(B2GMochitest, self).__init__()
-        self.marionette = marionette
+        self.marionette_args = marionette_args
         self.out_of_process = out_of_process
         self.locations_file = locations
         self.preferences = []
         self.webapps = None
         self.test_script = os.path.join(here, 'b2g_start_script.js')
         self.test_script_args = [self.out_of_process]
         self.product = 'b2g'
 
@@ -116,97 +114,117 @@ class B2GMochitest(MochitestUtilsMixin):
         return manifest
 
     def run_tests(self, options):
         """ Prepare, configure, run tests and cleanup """
 
         self.leak_report_file = os.path.join(options.profilePath, "runtests_leaks.log")
         manifest = self.build_profile(options)
 
-        self.startServers(options, None)
-        self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'})
-        self.test_script_args.append(not options.emulator)
-        self.test_script_args.append(options.wifi)
-
         if options.debugger or not options.autorun:
             timeout = None
         else:
             if not options.timeout:
                 if mozinfo.info['debug']:
                     options.timeout = 420
                 else:
                     options.timeout = 300
             timeout = options.timeout + 30.0
 
         log.info("runtestsb2g.py | Running tests: start.")
         status = 0
         try:
-            runner_args = { 'profile': self.profile,
-                            'devicemanager': self._dm,
-                            'marionette': self.marionette,
-                            'remote_test_root': self.remote_test_root,
-                            'symbols_path': options.symbolsPath,
-                            'test_script': self.test_script,
-                            'test_script_args': self.test_script_args }
-            self.runner = B2GRunner(**runner_args)
+            self.marionette_args['profile'] = self.profile
+            self.marionette = Marionette(**self.marionette_args)
+            self.runner = self.marionette.runner
+            self.app_ctx = self.runner.app_ctx
+
+            self.remote_log = posixpath.join(self.app_ctx.remote_test_root,
+                                             'log', 'mochitest.log')
+            if not self.app_ctx.dm.dirExists(posixpath.dirname(self.remote_log)):
+                self.app_ctx.dm.mkDirs(self.remote_log)
+
+            self.startServers(options, None)
+            self.buildURLOptions(options, {'MOZ_HIDE_RESULTS_TABLE': '1'})
+            self.test_script_args.append(not options.emulator)
+            self.test_script_args.append(options.wifi)
+
+
             self.runner.start(outputTimeout=timeout)
+
+            self.marionette.wait_for_port()
+            self.marionette.start_session()
+            self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+
+            # Disable offline status management (bug 777145), otherwise the network
+            # will be 'offline' when the mochitests start.  Presumably, the network
+            # won't be offline on a real device, so we only do this for emulators.
+            self.marionette.execute_script("""
+                Components.utils.import("resource://gre/modules/Services.jsm");
+                Services.io.manageOfflineStatus = false;
+                Services.io.offline = false;
+                """)
+
+            if os.path.isfile(self.test_script):
+                with open(self.test_script, 'r') as script:
+                    self.marionette.execute_script(script.read(),
+                                                   script_args=self.test_script_args)
+            else:
+                self.marionette.execute_script(self.test_script,
+                                               script_args=self.test_script_args)
             status = self.runner.wait()
             if status is None:
                 # the runner has timed out
                 status = 124
         except KeyboardInterrupt:
             log.info("runtests.py | Received keyboard interrupt.\n");
             status = -1
         except:
             traceback.print_exc()
             log.error("Automation Error: Received unexpected exception while running application\n")
-            self.runner.check_for_crashes()
+            if hasattr(self, 'runner'):
+                self.runner.check_for_crashes()
             status = 1
 
         self.stopServers()
 
         log.info("runtestsb2g.py | Running tests: end.")
 
         if manifest is not None:
             self.cleanup(manifest, options)
         return status
 
 
 class B2GDeviceMochitest(B2GMochitest, Mochitest):
+    remote_log = None
 
-    _dm = None
-
-    def __init__(self, marionette, devicemanager, profile_data_dir,
+    def __init__(self, marionette_args, profile_data_dir,
                  local_binary_dir, remote_test_root=None, remote_log_file=None):
-        B2GMochitest.__init__(self, marionette, out_of_process=True, profile_data_dir=profile_data_dir)
-        Mochitest.__init__(self)
-        self._dm = devicemanager
-        self.remote_test_root = remote_test_root or self._dm.getDeviceRoot()
-        self.remote_profile = posixpath.join(self.remote_test_root, 'profile')
-        self.remote_log = remote_log_file or posixpath.join(self.remote_test_root, 'log', 'mochitest.log')
+        B2GMochitest.__init__(self, marionette_args, out_of_process=True, profile_data_dir=profile_data_dir)
         self.local_log = None
         self.local_binary_dir = local_binary_dir
 
-        if not self._dm.dirExists(posixpath.dirname(self.remote_log)):
-            self._dm.mkDirs(self.remote_log)
-
     def cleanup(self, manifest, options):
         if self.local_log:
-            self._dm.getFile(self.remote_log, self.local_log)
-            self._dm.removeFile(self.remote_log)
+            self.app_ctx.dm.getFile(self.remote_log, self.local_log)
+            self.app_ctx.dm.removeFile(self.remote_log)
 
         if options.pidFile != "":
             try:
                 os.remove(options.pidFile)
                 os.remove(options.pidFile + ".xpcshell.pid")
             except:
                 print "Warning: cleaning up pidfile '%s' was unsuccessful from the test harness" % options.pidFile
 
         # stop and clean up the runner
         if getattr(self, 'runner', False):
+            if self.local_log:
+                self.app_ctx.dm.getFile(self.remote_log, self.local_log)
+                self.app_ctx.dm.removeFile(self.remote_log)
+
             self.runner.cleanup()
             self.runner = None
 
     def startServers(self, options, debuggerInfo):
         """ Create the servers on the host and start them up """
         savedXre = options.xrePath
         savedUtility = options.utilityPath
         savedProfie = options.profilePath
@@ -223,24 +241,24 @@ class B2GDeviceMochitest(B2GMochitest, M
     def buildURLOptions(self, options, env):
         self.local_log = options.logFile
         options.logFile = self.remote_log
         options.profilePath = self.profile.profile
         super(B2GDeviceMochitest, self).buildURLOptions(options, env)
 
         self.setup_common_options(options)
 
-        options.profilePath = self.remote_profile
+        options.profilePath = self.app_ctx.remote_profile
         options.logFile = self.local_log
 
 
 class B2GDesktopMochitest(B2GMochitest, Mochitest):
 
-    def __init__(self, marionette, profile_data_dir):
-        B2GMochitest.__init__(self, marionette, out_of_process=False, profile_data_dir=profile_data_dir)
+    def __init__(self, marionette_args, profile_data_dir):
+        B2GMochitest.__init__(self, marionette_args, out_of_process=False, profile_data_dir=profile_data_dir)
         Mochitest.__init__(self)
         self.certdbNew = True
 
     def runMarionetteScript(self, marionette, test_script, test_script_args):
         assert(marionette.wait_for_port())
         marionette.start_session()
         marionette.set_context(marionette.CONTEXT_CHROME)
 
@@ -250,16 +268,17 @@ class B2GDesktopMochitest(B2GMochitest, 
             f.close()
         self.marionette.execute_script(test_script,
                                        script_args=test_script_args)
 
     def startTests(self):
         # This is run in a separate thread because otherwise, the app's
         # stdout buffer gets filled (which gets drained only after this
         # function returns, by waitForFinish), which causes the app to hang.
+        self.marionette = Marionette(**self.marionette_args)
         thread = threading.Thread(target=self.runMarionetteScript,
                                   args=(self.marionette,
                                         self.test_script,
                                         self.test_script_args))
         thread.start()
 
     def buildURLOptions(self, options, env):
         super(B2GDesktopMochitest, self).buildURLOptions(options, env)
@@ -277,59 +296,37 @@ class B2GDesktopMochitest(B2GMochitest, 
                             os.path.join(bundlesDir, filename))
 
     def buildProfile(self, options):
         return self.build_profile(options)
 
 
 def run_remote_mochitests(parser, options):
     # create our Marionette instance
-    kwargs = {}
-    if options.emulator:
-        kwargs['emulator'] = options.emulator
-        if options.noWindow:
-            kwargs['noWindow'] = True
-        if options.geckoPath:
-            kwargs['gecko_path'] = options.geckoPath
-        if options.logcat_dir:
-            kwargs['logcat_dir'] = options.logcat_dir
-        if options.busybox:
-            kwargs['busybox'] = options.busybox
-        if options.symbolsPath:
-            kwargs['symbols_path'] = options.symbolsPath
-    # needless to say sdcard is only valid if using an emulator
-    if options.sdcard:
-        kwargs['sdcard'] = options.sdcard
-    if options.b2gPath:
-        kwargs['homedir'] = options.b2gPath
+    marionette_args = {
+        'adb_path': options.adbPath,
+        'emulator': options.emulator,
+        'no_window': options.noWindow,
+        'logdir': options.logdir,
+        'busybox': options.busybox,
+        'symbols_path': options.symbolsPath,
+        'sdcard': options.sdcard,
+        'homedir': options.b2gPath,
+    }
     if options.marionette:
         host, port = options.marionette.split(':')
-        kwargs['host'] = host
-        kwargs['port'] = int(port)
-
-    marionette = Marionette.getMarionetteOrExit(**kwargs)
-
-    if options.emulator:
-        dm = marionette.emulator.dm
-    else:
-        # create the DeviceManager
-        kwargs = {'adbPath': options.adbPath,
-                  'deviceRoot': options.remoteTestRoot}
-        if options.deviceIP:
-            kwargs.update({'host': options.deviceIP,
-                           'port': options.devicePort})
-        dm = DeviceManagerADB(**kwargs)
+        marionette_args['host'] = host
+        marionette_args['port'] = int(port)
 
     options = parser.verifyRemoteOptions(options)
     if (options == None):
         print "ERROR: Invalid options specified, use --help for a list of valid options"
         sys.exit(1)
 
-    mochitest = B2GDeviceMochitest(marionette, dm, options.profile_data_dir, options.xrePath,
-                                   remote_test_root=options.remoteTestRoot,
+    mochitest = B2GDeviceMochitest(marionette_args, options.profile_data_dir, options.xrePath,
                                    remote_log_file=options.remoteLogFile)
 
     options = parser.verifyOptions(options, mochitest)