Merge inbound to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 17 Apr 2014 22:31:17 -0400
changeset 197672 7fe3ee0cf8be3f598d23d610618b1fee976a8fa7
parent 197590 ec728bfdbb79cb5ef6a847d042bda0cbad1207d0 (diff)
parent 197671 fdfb11f2cf9cc52df05c86358bb3537d7e5bc4cd (current diff)
child 197673 8f2f17f5ab237347fdd46b9ad91220bd1decda7d
child 197690 c75f7df6645b9f55613786e789f344f29307ea86
child 197720 35524db8493a53c3922924b1cda451c58dd996cd
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone31.0a1
first release with
nightly linux32
7fe3ee0cf8be / 31.0a1 / 20140418030202 / files
nightly linux64
7fe3ee0cf8be / 31.0a1 / 20140418030202 / files
nightly mac
7fe3ee0cf8be / 31.0a1 / 20140418030202 / files
nightly win32
7fe3ee0cf8be / 31.0a1 / 20140418030202 / files
nightly win64
7fe3ee0cf8be / 31.0a1 / 20140418030202 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c.
b2g/config/tooltool-manifests/ics.manifest
b2g/config/tooltool-manifests/releng.manifest
content/canvas/public/nsICanvasElementExternal.h
gfx/layers/composite/APZCTreeManager.cpp
gfx/layers/composite/APZCTreeManager.h
gfx/layers/ipc/AsyncPanZoomController.cpp
gfx/layers/ipc/AsyncPanZoomController.h
gfx/layers/ipc/Axis.cpp
gfx/layers/ipc/Axis.h
gfx/layers/ipc/GeckoContentController.h
gfx/layers/ipc/GestureEventListener.cpp
gfx/layers/ipc/GestureEventListener.h
gfx/layers/ipc/TaskThrottler.cpp
gfx/layers/ipc/TaskThrottler.h
layout/generic/crashtests/455407.html
layout/reftests/backgrounds/background-size-continuous.html
layout/reftests/backgrounds/background-size-cover-bounding-box.html
layout/reftests/backgrounds/background-size-cover-continuous.html
layout/reftests/backgrounds/background-size-cover-each-box.html
layout/reftests/backgrounds/background-size-each-box.html
layout/reftests/bugs/368020-4-ref.html
layout/reftests/bugs/368020-4.html
testing/profiles/prefs_general.js
widget/xpwidgets/APZCCallbackHelper.cpp
widget/xpwidgets/APZCCallbackHelper.h
widget/xpwidgets/ActiveElementManager.cpp
widget/xpwidgets/ActiveElementManager.h
--- a/addon-sdk/source/lib/sdk/addon/installer.js
+++ b/addon-sdk/source/lib/sdk/addon/installer.js
@@ -60,18 +60,22 @@ exports.install = function install(xpiPa
     },
     onDownloadFailed: function(aInstall) {
       this.onInstallFailed(aInstall);
     }
   };
 
   // Order AddonManager to install the addon
   AddonManager.getInstallForFile(file, function(install) {
-    install.addListener(listener);
-    install.install();
+    if (install.error != null) {
+      install.addListener(listener);
+      install.install();
+    } else {
+      reject(install.error);
+    }
   });
 
   return promise;
 };
 
 exports.uninstall = function uninstall(addonId) {
   let { promise, resolve, reject } = defer();
 
--- a/addon-sdk/source/lib/sdk/content/content-worker.js
+++ b/addon-sdk/source/lib/sdk/content/content-worker.js
@@ -281,27 +281,16 @@ const ContentWorker = Object.freeze({
       on: pipe.on.bind(null),
       once: pipe.once.bind(null),
       removeListener: pipe.removeListener.bind(null),
     };
     Object.defineProperty(exports, "self", {
       value: self
     });
 
-    // Deprecated use of on/postMessage from globals
-    exports.postMessage = function deprecatedPostMessage() {
-      console.error("DEPRECATED: The global `postMessage()` function in " +
-                    "content scripts is deprecated in favor of the " +
-                    "`self.postMessage()` function, which works the same. " +
-                    "Replace calls to `postMessage()` with calls to " +
-                    "`self.postMessage()`." +
-                    "For more info on `self.on`, see " +
-                    "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
-      return self.postMessage.apply(null, arguments);
-    };
     exports.on = function deprecatedOn() {
       console.error("DEPRECATED: The global `on()` function in content " +
                     "scripts is deprecated in favor of the `self.on()` " +
                     "function, which works the same. Replace calls to `on()` " +
                     "with calls to `self.on()`" +
                     "For more info on `self.on`, see " +
                     "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.");
       return self.on.apply(null, arguments);
--- a/addon-sdk/source/lib/sdk/l10n/html.js
+++ b/addon-sdk/source/lib/sdk/l10n/html.js
@@ -41,19 +41,21 @@ function onDocumentReady2Translate(event
   let document = event.target;
   document.removeEventListener("DOMContentLoaded", onDocumentReady2Translate,
                                false);
 
   translateElement(document);
 
   try {
     // Finally display document when we finished replacing all text content
-    let winUtils = document.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
-                                       .getInterface(Ci.nsIDOMWindowUtils);
-    winUtils.removeSheet(hideSheetUri, winUtils.USER_SHEET);
+    if (document.defaultView) {
+      let winUtils = document.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+                                         .getInterface(Ci.nsIDOMWindowUtils);
+      winUtils.removeSheet(hideSheetUri, winUtils.USER_SHEET);
+    }
   }
   catch(e) {
     console.exception(e);
   }
 }
 
 function onContentWindow(event) {
   let document = event.subject;
--- a/addon-sdk/source/lib/sdk/panel/utils.js
+++ b/addon-sdk/source/lib/sdk/panel/utils.js
@@ -218,17 +218,16 @@ function show(panel, options, anchor) {
 
   open(panel, options, anchor);
 }
 exports.show = show
 
 function setupPanelFrame(frame) {
   frame.setAttribute("flex", 1);
   frame.setAttribute("transparent", "transparent");
-  frame.setAttribute("showcaret", true);
   frame.setAttribute("autocompleteenabled", true);
   if (platform === "darwin") {
     frame.style.borderRadius = "6px";
     frame.style.padding = "1px";
   }
 }
 
 function make(document) {
--- a/addon-sdk/source/test/test-content-script.js
+++ b/addon-sdk/source/test/test-content-script.js
@@ -186,19 +186,16 @@ exports["test postMessage"] = createProx
     assert.equal(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}",
                      "message data is correct");
 
     helper.done();
   }, false);
 
   helper.createWorker(
     'new ' + function ContentScriptScope() {
-      assert(postMessage === postMessage,
-          "verify that we doesn't generate multiple functions for the same method");
-
       var json = JSON.stringify({foo : "bar\n \"escaped\"."});
 
       document.getElementById("iframe").contentWindow.postMessage(json, "*");
     }
   );
 });
 
 let html = '<input id="input2" type="checkbox" />';
--- a/addon-sdk/source/test/test-content-worker.js
+++ b/addon-sdk/source/test/test-content-worker.js
@@ -21,17 +21,23 @@ const { set: setPref } = require("sdk/pr
 const { isArray } = require("sdk/lang/type");
 const { URL } = require('sdk/url');
 const fixtures = require("./fixtures");
 
 const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings";
 
 const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo";
 
-function makeWindow(contentURL) {
+const WINDOW_SCRIPT_URL = "data:text/html;charset=utf-8," +
+                          "<script>window.addEventListener('message', function (e) {" +
+                          "  if (e.data === 'from -> content-script')" +
+                          "    window.postMessage('from -> window', '*');" +
+                          "});</script>";
+
+function makeWindow() {
   let content =
     "<?xml version=\"1.0\"?>" +
     "<window " +
     "xmlns=\"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul\">" +
     "<script>var documentValue=true;</script>" +
     "</window>";
   var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," +
             encodeURIComponent(content);
@@ -777,60 +783,16 @@ exports["test:check worker API with page
         }, 500);
 
       }, false);
     });
 
   }
 );
 
-exports["test:global postMessage"] = WorkerTest(
-  DEFAULT_CONTENT_URL,
-  function(assert, browser, done) {
-    let { loader } = LoaderWithHookedConsole(module, onMessage);
-    setPref(DEPRECATE_PREF, true);
-
-    // Intercept all console method calls
-    let seenMessages = 0;
-    function onMessage(type, message) {
-      seenMessages++;
-      assert.equal(type, "error", "Should be an error");
-      assert.equal(message, "DEPRECATED: The global `postMessage()` function in " +
-                            "content scripts is deprecated in favor of the " +
-                            "`self.postMessage()` function, which works the same. " +
-                            "Replace calls to `postMessage()` with calls to " +
-                            "`self.postMessage()`." +
-                            "For more info on `self.on`, see " +
-                            "<https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/addon-development/web-content.html>.",
-                            "Should have seen the deprecation message")
-    }
-
-    assert.notEqual(browser.contentWindow.location.href, "about:blank",
-                        "window is now on the right document");
-
-    let window = browser.contentWindow
-    let worker = loader.require("sdk/content/worker").Worker({
-      window: window,
-      contentScript: "new " + function WorkerScope() {
-        postMessage("success");
-      },
-      contentScriptWhen: "ready",
-      onMessage: function(msg) {
-        assert.equal("success", msg, "Should have seen the right postMessage call");
-        assert.equal(1, seenMessages, "Should have seen the deprecation message");
-        done();
-      }
-    });
-
-    assert.equal(worker.url, window.location.href,
-                     "worker.url works");
-    worker.postMessage("hi!");
-  }
-);
-
 exports['test:conentScriptFile as URL instance'] = WorkerTest(
   DEFAULT_CONTENT_URL,
   function(assert, browser, done) {
 
     let url = new URL(fixtures.url("test-contentScriptFile.js"));
     let worker =  Worker({
       window: browser.contentWindow,
       contentScriptFile: url,
@@ -884,18 +846,18 @@ exports["test:onDetach in contentScript 
     let worker = Worker({
       window: browser.contentWindow,
       contentScript: 'new ' + function WorkerScope() {
         self.port.on('detach', function(reason) {
           window.location.hash += '!' + reason;
         })
       },
     });
-    browser.contentWindow.addEventListener('hashchange', _ => { 
-      assert.equal(browser.contentWindow.location.hash, '#detach!', 
+    browser.contentWindow.addEventListener('hashchange', _ => {
+      assert.equal(browser.contentWindow.location.hash, '#detach!',
                    "location.href is as expected");
       done();
     })
     worker.destroy();
   }
 );
 
 exports["test:onDetach in contentScript on unload"] = WorkerTest(
@@ -905,18 +867,18 @@ exports["test:onDetach in contentScript 
     let worker = loader.require("sdk/content/worker").Worker({
       window: browser.contentWindow,
       contentScript: 'new ' + function WorkerScope() {
         self.port.on('detach', function(reason) {
           window.location.hash += '!' + reason;
         })
       },
     });
-    browser.contentWindow.addEventListener('hashchange', _ => { 
-      assert.equal(browser.contentWindow.location.hash, '#detach!shutdown', 
+    browser.contentWindow.addEventListener('hashchange', _ => {
+      assert.equal(browser.contentWindow.location.hash, '#detach!shutdown',
                    "location.href is as expected");
       done();
     })
     loader.unload('shutdown');
   }
 );
 
 exports["test:console method log functions properly"] = WorkerTest(
@@ -949,9 +911,30 @@ exports["test:console method log functio
         ]);
 
         done();
       }
     });
   }
 );
 
+exports["test:global postMessage"] = WorkerTest(
+  WINDOW_SCRIPT_URL,
+  function(assert, browser, done) {
+    let contentScript = "window.addEventListener('message', function (e) {" +
+                        "  if (e.data === 'from -> window')" +
+                        "    self.port.emit('response', e.data, e.origin);" +
+                        "});" +
+                        "postMessage('from -> content-script', '*');";
+    let { loader } = LoaderWithHookedConsole(module);
+    let worker =  loader.require("sdk/content/worker").Worker({
+      window: browser.contentWindow,
+      contentScriptWhen: "ready",
+      contentScript: contentScript
+    });
+
+    worker.port.on("response", (data, origin) => {
+      assert.equal(data, "from -> window", "Communication from content-script to window completed");
+      done();
+    });
+});
+
 require("test").run(exports);
--- a/addon-sdk/source/test/test-page-worker.js
+++ b/addon-sdk/source/test/test-page-worker.js
@@ -1,17 +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/. */
-
 "use strict";
 
 const { Loader } = require('sdk/test/loader');
-const Pages = require("sdk/page-worker");
-const Page = Pages.Page;
+const { Page } = require("sdk/page-worker");
 const { URL } = require("sdk/url");
 const fixtures = require("./fixtures");
 const testURI = fixtures.url("test.html");
 
 const ERR_DESTROYED =
   "Couldn't find the worker to receive this message. " +
   "The script may not be initialized yet, or may already have been unloaded.";
 
@@ -88,27 +86,27 @@ exports.testPageProperties = function(as
   }
 
   assert.ok(function () page.postMessage("foo") || true,
               "postMessage doesn't throw exception on page.");
 }
 
 exports.testConstructorAndDestructor = function(assert, done) {
   let loader = Loader(module);
-  let Pages = loader.require("sdk/page-worker");
+  let { Page } = loader.require("sdk/page-worker");
   let global = loader.sandbox("sdk/page-worker");
 
   let pagesReady = 0;
 
-  let page1 = Pages.Page({
+  let page1 = Page({
     contentScript:      "self.postMessage('')",
     contentScriptWhen:  "end",
     onMessage:          pageReady
   });
-  let page2 = Pages.Page({
+  let page2 = Page({
     contentScript:      "self.postMessage('')",
     contentScriptWhen:  "end",
     onMessage:          pageReady
   });
 
   assert.notEqual(page1, page2,
                       "Page 1 and page 2 should be different objects.");
 
@@ -123,19 +121,19 @@ exports.testConstructorAndDestructor = f
       loader.unload();
       done();
     }
   }
 }
 
 exports.testAutoDestructor = function(assert, done) {
   let loader = Loader(module);
-  let Pages = loader.require("sdk/page-worker");
+  let { Page } = loader.require("sdk/page-worker");
 
-  let page = Pages.Page({
+  let page = Page({
     contentScript: "self.postMessage('')",
     contentScriptWhen: "end",
     onMessage: function() {
       loader.unload();
       assert.ok(isDestroyed(page), "Page correctly unloaded.");
       done();
     }
   });
@@ -311,22 +309,22 @@ exports.testPingPong = function(assert, 
       }
     }
   });
 };
 
 exports.testRedirect = function (assert, done) {
   let page = Page({
     contentURL: 'data:text/html;charset=utf-8,first-page',
-    contentScript: '(function () {' +
+    contentScriptWhen: "end",
+    contentScript: '' +
       'if (/first-page/.test(document.location.href)) ' +
       '  document.location.href = "data:text/html;charset=utf-8,redirect";' +
       'else ' +
-      '  self.port.emit("redirect", document.location.href);' +
-      '})();'
+      '  self.port.emit("redirect", document.location.href);'
   });
 
   page.port.on('redirect', function (url) {
     assert.equal(url, 'data:text/html;charset=utf-8,redirect', 'Reinjects contentScript on reload');
     done();
   });
 };
 
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -1162,19 +1162,19 @@ let RemoteDebugger = {
           } : DebuggerServer.globalActorFactories
         };
         let root = new DebuggerServer.RootActor(connection, parameters);
         root.applicationType = "operating-system";
         return root;
       };
 
 #ifdef MOZ_WIDGET_GONK
-      DebuggerServer.onConnectionChange = function(what) {
+      DebuggerServer.on("connectionchange", function() {
         AdbController.updateState();
-      }
+      });
 #endif
     }
 
     let path = Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") ||
                "/data/local/debugger-socket";
     try {
       DebuggerServer.openListener(path);
       // Temporary event, until bug 942756 lands and offers a way to know
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="55bcc2d7e44dc805c24b57d1e783fc26e8a2ee86"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="99a67a75855d8ca077018c819aedd90bf0447d9b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
@@ -123,14 +123,14 @@
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Emulator specific things -->
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="09485b73629856b21b2ed6073e327ab0e69a1189"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="f8e49f3837230cfbb2d6fbcf239d1f25b57b39a7"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="8afce6d5d48b71b6e7cb917179fb6147fb747521"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="72e3a520e3c700839f07ba0113fd527b923c3330"/>
   <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="6a7ada569fd37c09ed4bbee6eb78cea5b467b229"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
 </manifest>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="52c909ccead537f8f9dbf634f3e6639078a8b0bd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- Stock Android things -->
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="55bcc2d7e44dc805c24b57d1e783fc26e8a2ee86"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="99a67a75855d8ca077018c819aedd90bf0447d9b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -13,17 +13,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
@@ -113,33 +113,33 @@
   <project name="platform/system/extras" path="system/extras" revision="4d4bc6e7777887d93340f577d9b46de4a7c75f26"/>
   <project name="platform/system/media" path="system/media" revision="df2cdd433738a891102873710bdd3c3db7adb0cc"/>
   <project name="platform/system/netd" path="system/netd" revision="ea8103eae5642621ca8202e00620f4ca954ed413"/>
   <project name="platform/system/security" path="system/security" revision="360f51f7af191316cd739f229db1c5f7233be063"/>
   <project name="platform/system/vold" path="system/vold" revision="153df4d067a4149c7d78f1c92fed2ce2bd6a272e"/>
   <default remote="caf" revision="jb_3.2" sync-j="4"/>
   <!-- Flame specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="e8a318f7690092e639ba88891606f4183e846d3f"/>
-  <project name="device/qcom/common" path="device/qcom/common" revision="c2c115bec6f5e9bc1daf7bb74bb4d14861c00b9c"/>
+  <project name="device/qcom/common" path="device/qcom/common" revision="234ed34543345f58c0d4dcb1aa012de68802b9dc"/>
   <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="92f9b79e3a5ecf24cb0f66e20d5292b300f8cac9"/>
-  <project name="kernel/msm" path="kernel" revision="39ca56deb6c4419c0220a762f6e340091032390d"/>
+  <project name="kernel/msm" path="kernel" revision="b3092c54430df89636fb0670d32058bc63474017"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="f2914eacee9120680a41463708bb6ee8291749fc"/>
-  <project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="c53977638eb8a7091f752d870e8e0f00f7a23d95"/>
+  <project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="fa892235a9bd8983f8b591129fc1a9398f64e514"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="f0689ac1914cdbc59e53bdc9edd9013dc157c299"/>
   <project name="platform/external/bluetooth/glib" path="external/bluetooth/glib" revision="dd925f76e4f149c3d5571b80e12f7e24bbe89c59"/>
   <project name="platform/external/dbus" path="external/dbus" revision="ea87119c843116340f5df1d94eaf8275e1055ae8"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="320b05a5761eb2a4816f7529c91ea49422979b55"/>
   <project name="platform/frameworks/av" path="frameworks/av" revision="1df6dac11d7370a2fffca8e31d65b80f537faec5"/>
   <project name="platform/frameworks/base" path="frameworks/base" revision="807d87d5ff66cb5e0664f6924f612fcdb5e2c453"/>
   <project name="platform/frameworks/native" path="frameworks/native" revision="33a2b51f78416536e1bfba0c0b7776db307f3a4f"/>
   <project name="platform/hardware/libhardware" path="hardware/libhardware" revision="484802559ed106bac4811bd01c024ca64f741e60"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="d30227d7ae5cbe8bac8775358b472f44504a20d2"/>
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="81afa7f775b7559da52f468150d1fe090c3fbdc5"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="e38444b6ce12c7c25883272a439800376d5308eb"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="13312a5577db9261cb0fcee9ccbc58cdb5e6bc55"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="8a0d0b0d9889ef99c4c6317c810db4c09295f15a"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="2208fa3537ace873b8f9ec2355055761c79dfd5f"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="c4e2ac95907a5519a0e09f01a0d8e27fec101af0"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="e1eb226fa3ad3874ea7b63c56a9dc7012d7ff3c2"/>
-  <project name="platform/system/core" path="system/core" revision="8b7736c56fa9a3fd8b4341eb243e12eec847efed"/>
+  <project name="platform/system/core" path="system/core" revision="e284280277c1312017a9450956a9676584441cdf"/>
   <project name="platform/system/qcom" path="system/qcom" revision="1cdab258b15258b7f9657da70e6f06ebd5a2fc25"/>
   <project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="b3001d5f1686f89995b7bf70963cf69c8faebd83"/>
 </manifest>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "35bd8bfb0ee9dce0eb9dcfdbb43587517b655121", 
+    "revision": "d253e50da6b54f14a1ffa6d8a8a287337f0e3f34", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="dadf0e60a6421f5b57ee9fc536c6617212805c19"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="375d87bfef8a5d7135f139da8c17f237b990d3f5"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -2,16 +2,17 @@
 # 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/.
 
 XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () {
   return Cu.import("resource://gre/modules/FxAccountsCommon.js", {});
 });
 
 const PREF_SYNC_START_DOORHANGER = "services.sync.ui.showSyncStartDoorhanger";
+const DOORHANGER_ACTIVATE_DELAY_MS = 5000;
 
 let gFxAccounts = {
 
   _initialized: false,
   _inCustomizationMode: false,
 
   get weave() {
     delete this.weave;
@@ -28,26 +29,16 @@ let gFxAccounts = {
       "weave:service:sync:start",
       "weave:service:login:error",
       "weave:service:setup-complete",
       FxAccountsCommon.ONVERIFIED_NOTIFICATION,
       FxAccountsCommon.ONLOGOUT_NOTIFICATION
     ];
   },
 
-  // The set of topics that only the active window should handle.
-  get activeWindowTopics() {
-    // Do all this dance to lazy-load FxAccountsCommon.
-    delete this.activeWindowTopics;
-    return this.activeWindowTopics = new Set([
-      "weave:service:sync:start",
-      FxAccountsCommon.ONVERIFIED_NOTIFICATION
-    ]);
-  },
-
   get button() {
     delete this.button;
     return this.button = document.getElementById("PanelUI-fxa-status");
   },
 
   get loginFailed() {
     // Referencing Weave.Service will implicitly initialize sync, and we don't
     // want to force that - so first check if it is ready.
@@ -59,31 +50,31 @@ let gFxAccounts = {
     }
     // LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in".
     // All other login failures are assumed to be transient and should go
     // away by themselves, so aren't reflected here.
     return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
   },
 
   get isActiveWindow() {
-    let mostRecentNonPopupWindow =
-      RecentWindow.getMostRecentBrowserWindow({allowPopups: false});
-    return window == mostRecentNonPopupWindow;
+    let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+    return fm.activeWindow == window;
   },
 
   init: function () {
     // Bail out if we're already initialized and for pop-up windows.
     if (this._initialized || !window.toolbar.visible) {
       return;
     }
 
     for (let topic of this.topics) {
       Services.obs.addObserver(this, topic, false);
     }
 
+    addEventListener("activate", this);
     gNavToolbox.addEventListener("customizationstarting", this);
     gNavToolbox.addEventListener("customizationending", this);
 
     this._initialized = true;
 
     this.updateUI();
   },
 
@@ -95,50 +86,58 @@ let gFxAccounts = {
     for (let topic of this.topics) {
       Services.obs.removeObserver(this, topic);
     }
 
     this._initialized = false;
   },
 
   observe: function (subject, topic) {
-    // Ignore certain topics if we're not the active window.
-    if (this.activeWindowTopics.has(topic) && !this.isActiveWindow) {
-      return;
-    }
-
     switch (topic) {
       case FxAccountsCommon.ONVERIFIED_NOTIFICATION:
         Services.prefs.setBoolPref(PREF_SYNC_START_DOORHANGER, true);
         break;
       case "weave:service:sync:start":
         this.onSyncStart();
         break;
       default:
         this.updateUI();
         break;
     }
   },
 
   onSyncStart: function () {
+    if (!this.isActiveWindow) {
+      return;
+    }
+
     let showDoorhanger = false;
 
     try {
       showDoorhanger = Services.prefs.getBoolPref(PREF_SYNC_START_DOORHANGER);
     } catch (e) { /* The pref might not exist. */ }
 
     if (showDoorhanger) {
       Services.prefs.clearUserPref(PREF_SYNC_START_DOORHANGER);
       this.showSyncStartedDoorhanger();
     }
   },
 
   handleEvent: function (event) {
-    this._inCustomizationMode = event.type == "customizationstarting";
-    this.updateUI();
+    if (event.type == "activate") {
+      // Our window might have been in the background while we received the
+      // sync:start notification. If still needed, show the doorhanger after
+      // a short delay. Without this delay the doorhanger would not show up
+      // or with a too small delay show up while we're still animating the
+      // window.
+      setTimeout(() => this.onSyncStart(), DOORHANGER_ACTIVATE_DELAY_MS);
+    } else {
+      this._inCustomizationMode = event.type == "customizationstarting";
+      this.updateUI();
+    }
   },
 
   showDoorhanger: function (id) {
     let panel = document.getElementById(id);
     let anchor = document.getElementById("PanelUI-menu-button");
 
     let iconAnchor =
       document.getAnonymousElementByAttribute(anchor, "class",
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6994,20 +6994,16 @@ function restoreLastSession() {
 
 var TabContextMenu = {
   contextTab: null,
   updateContextMenu: function updateContextMenu(aPopupMenu) {
     this.contextTab = aPopupMenu.triggerNode.localName == "tab" ?
                       aPopupMenu.triggerNode : gBrowser.selectedTab;
     let disabled = gBrowser.tabs.length == 1;
 
-    // Enable the "Close Tab" menuitem when the window doesn't close with the last tab.
-    document.getElementById("context_closeTab").disabled =
-      disabled && gBrowser.tabContainer._closeWindowWithLastTab;
-
     var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
     for (let menuItem of menuItems)
       menuItem.disabled = disabled;
 
     disabled = gBrowser.visibleTabs.length == 1;
     menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible");
     for (let menuItem of menuItems)
       menuItem.disabled = disabled;
--- a/browser/base/content/test/general/browser_devices_get_user_media.js
+++ b/browser/base/content/test/general/browser_devices_get_user_media.js
@@ -1,8 +1,12 @@
+/* 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 kObservedTopics = [
   "getUserMedia:response:allow",
   "getUserMedia:revoke",
   "getUserMedia:response:deny",
   "getUserMedia:request",
   "recording-device-events",
   "recording-window-ended"
 ];
@@ -731,16 +735,60 @@ let gTests = [
 
     info("request audio+video, stop sharing resets both");
     yield stopAndCheckPerm(true, true);
     info("request audio, stop sharing resets audio only");
     yield stopAndCheckPerm(true, false);
     info("request video, stop sharing resets video only");
     yield stopAndCheckPerm(false, true);
   }
+},
+
+{
+  desc: "'Always Allow' ignored and not shown on http pages",
+  run: function checkNoAlwaysOnHttp() {
+    // Load an http page instead of the https version.
+    let deferred = Promise.defer();
+    let browser = gBrowser.selectedTab.linkedBrowser;
+    browser.addEventListener("load", function onload() {
+      browser.removeEventListener("load", onload, true);
+      deferred.resolve();
+    }, true);
+    content.location = content.location.href.replace("https://", "http://");
+    yield deferred.promise;
+
+    // Initially set both permissions to 'allow'.
+    let Perms = Services.perms;
+    let uri = content.document.documentURIObject;
+    Perms.add(uri, "microphone", Perms.ALLOW_ACTION);
+    Perms.add(uri, "camera", Perms.ALLOW_ACTION);
+
+    // Request devices and expect a prompt despite the saved 'Allow' permission,
+    // because the connection isn't secure.
+    yield promisePopupNotificationShown("webRTC-shareDevices", () => {
+      content.wrappedJSObject.requestDevice(true, true);
+    });
+    expectObserverCalled("getUserMedia:request");
+
+    // Ensure that the 'Always Allow' action isn't shown.
+    let alwaysLabel = gNavigatorBundle.getString("getUserMedia.always.label");
+    ok(!!alwaysLabel, "found the 'Always Allow' localized label");
+    let labels = [];
+    let notification = PopupNotifications.panel.firstChild;
+    for (let node of notification.childNodes) {
+      if (node.localName == "menuitem")
+        labels.push(node.getAttribute("label"));
+    }
+    is(labels.indexOf(alwaysLabel), -1, "The 'Always Allow' item isn't shown");
+
+    // Cleanup.
+    yield closeStream(true);
+    Perms.remove(uri.host, "camera");
+    Perms.remove(uri.host, "microphone");
+  }
 }
 
 ];
 
 function test() {
   waitForExplicitFinish();
 
   let tab = gBrowser.addTab();
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -158,18 +158,18 @@ let gSyncPane = {
         let checkbox = document.getElementById("fxa-pweng-chk");
         let help = document.getElementById("fxa-pweng-help");
         let allowPasswordsEngine = service.allowPasswordsEngine;
 
         if (!allowPasswordsEngine) {
           checkbox.checked = false;
         }
 
-        checkbox.disabled = !allowPasswordsEngine;
-        help.hidden = allowPasswordsEngine;
+        checkbox.disabled = !allowPasswordsEngine || enginesListDisabled;
+        help.hidden = allowPasswordsEngine || enginesListDisabled;
       });
     // If fxAccountEnabled is false and we are in a "not configured" state,
     // then fxAccounts is probably fully disabled rather than just unconfigured,
     // so handle this case.  This block can be removed once we remove support
     // for fxAccounts being disabled.
     } else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
                Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
       this.page = PAGE_NO_ACCOUNT;
--- a/browser/components/preferences/sync.js
+++ b/browser/components/preferences/sync.js
@@ -158,18 +158,18 @@ let gSyncPane = {
         let checkbox = document.getElementById("fxa-pweng-chk");
         let help = document.getElementById("fxa-pweng-help");
         let allowPasswordsEngine = service.allowPasswordsEngine;
 
         if (!allowPasswordsEngine) {
           checkbox.checked = false;
         }
 
-        checkbox.disabled = !allowPasswordsEngine;
-        help.hidden = allowPasswordsEngine;
+        checkbox.disabled = !allowPasswordsEngine || enginesListDisabled;
+        help.hidden = allowPasswordsEngine || enginesListDisabled;
       });
     // If fxAccountEnabled is false and we are in a "not configured" state,
     // then fxAccounts is probably fully disabled rather than just unconfigured,
     // so handle this case.  This block can be removed once we remove support
     // for fxAccounts being disabled.
     } else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
                Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
       this.page = PAGE_NO_ACCOUNT;
--- a/browser/components/tabview/test/browser_tabview_bug625195.js
+++ b/browser/components/tabview/test/browser_tabview_bug625195.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   is(gBrowser.tabs.length, 1, "Only one tab exist");
 
   let originalTab = gBrowser.tabs[0];
 
   popup(originalTab);
-  ok(document.getElementById("context_closeTab").disabled, "The 'Close tab' menu item is disabled");
+  ok(!document.getElementById("context_closeTab").disabled, "The 'Close tab' menu item is enabled");
   ok(document.getElementById("context_openTabInWindow").disabled, "The 'Move to New Window' menu item is disabled");
 
   let newTabOne = gBrowser.addTab("about:blank", {skipAnimation: true});
 
   waitForExplicitFinish();
 
   showTabView(function() {
     registerCleanupFunction(function () {
--- a/browser/devtools/framework/ToolboxProcess.jsm
+++ b/browser/devtools/framework/ToolboxProcess.jsm
@@ -11,59 +11,104 @@ const DBG_XUL = "chrome://browser/conten
 const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
 Cu.import("resource://gre/modules/devtools/Loader.jsm");
 let require = devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
+let EventEmitter = require("devtools/toolkit/event-emitter");
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 
 this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"];
 
+let processes = Set();
+
 /**
  * Constructor for creating a process that will hold a chrome toolbox.
  *
  * @param function aOnClose [optional]
  *        A function called when the process stops running.
  * @param function aOnRun [optional]
  *        A function called when the process starts running.
  * @param object aOptions [optional]
  *        An object with properties for configuring BrowserToolboxProcess.
  */
 this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun, aOptions) {
+  let emitter = new EventEmitter();
+  this.on = emitter.on.bind(emitter);
+  this.off = emitter.off.bind(emitter);
+  this.once = emitter.once.bind(emitter);
+  // Forward any events to the shared emitter.
+  this.emit = function(...args) {
+    emitter.emit(...args);
+    BrowserToolboxProcess.emit(...args);
+  }
+
   // If first argument is an object, use those properties instead of
   // all three arguments
   if (typeof aOnClose === "object") {
-    this._closeCallback = aOnClose.onClose;
-    this._runCallback = aOnClose.onRun;
+    if (aOnClose.onClose) {
+      this.on("close", aOnClose.onClose);
+    }
+    if (aOnClose.onRun) {
+      this.on("run", aOnClose.onRun);
+    }
     this._options = aOnClose;
   } else {
-    this._closeCallback = aOnClose;
-    this._runCallback = aOnRun;
+    if (aOnClose) {
+      this.on("close", aOnClose);
+    }
+    if (aOnRun) {
+      this.on("run", aOnRun);
+    }
     this._options = aOptions || {};
   }
 
   this._telemetry = new Telemetry();
 
   this.close = this.close.bind(this);
   Services.obs.addObserver(this.close, "quit-application", false);
   this._initServer();
   this._initProfile();
   this._create();
+
+  processes.add(this);
 };
 
+EventEmitter.decorate(BrowserToolboxProcess);
+
 /**
  * Initializes and starts a chrome toolbox process.
  * @return object
  */
 BrowserToolboxProcess.init = function(aOnClose, aOnRun, aOptions) {
   return new BrowserToolboxProcess(aOnClose, aOnRun, aOptions);
 };
 
+/**
+ * Passes a set of options to the BrowserAddonActors for the given ID.
+ *
+ * @param aId string
+ *        The ID of the add-on to pass the options to
+ * @param aOptions object
+ *        The options.
+ * @return a promise that will be resolved when complete.
+ */
+BrowserToolboxProcess.setAddonOptions = function DSC_setAddonOptions(aId, aOptions) {
+  let promises = [];
+
+  for (let process of processes.values()) {
+    promises.push(process.debuggerServer.setAddonOptions(aId, aOptions));
+  }
+
+  return promise.all(promises);
+};
+
 BrowserToolboxProcess.prototype = {
   /**
    * Initializes the debugger server.
    */
   _initServer: function() {
     dumpn("Initializing the chrome toolbox server.");
 
     if (!this.loader) {
@@ -72,16 +117,19 @@ BrowserToolboxProcess.prototype = {
       // This allows us to safely use the tools against even the actors and
       // DebuggingServer itself, especially since we can mark this loader as
       // invisible to the debugger (unlike the usual loader settings).
       this.loader = new DevToolsLoader();
       this.loader.invisibleToDebugger = true;
       this.loader.main("devtools/server/main");
       this.debuggerServer = this.loader.DebuggerServer;
       dumpn("Created a separate loader instance for the DebuggerServer.");
+
+      // Forward interesting events.
+      this.debuggerServer.on("connectionchange", this.emit.bind(this));
     }
 
     if (!this.debuggerServer.initialized) {
       this.debuggerServer.init();
       this.debuggerServer.addBrowserActors();
       dumpn("initialized and added the browser actors for the DebuggerServer.");
     }
 
@@ -164,19 +212,17 @@ BrowserToolboxProcess.prototype = {
     dumpn("Running chrome debugging process.");
     let args = ["-no-remote", "-foreground", "-P", this._dbgProfile.name, "-chrome", xulURI];
 
     process.runwAsync(args, args.length, { observe: () => this.close() });
 
     this._telemetry.toolOpened("jsbrowserdebugger");
 
     dumpn("Chrome toolbox is now running...");
-    if (typeof this._runCallback == "function") {
-      this._runCallback.call({}, this);
-    }
+    this.emit("run", this);
   },
 
   /**
    * Closes the remote debugging server and kills the toolbox process.
    */
   close: function() {
     if (this.closed) {
       return;
@@ -191,19 +237,18 @@ BrowserToolboxProcess.prototype = {
 
     this._telemetry.toolClosed("jsbrowserdebugger");
     if (this.debuggerServer) {
       this.debuggerServer.destroy();
     }
 
     dumpn("Chrome toolbox is now closed...");
     this.closed = true;
-    if (typeof this._closeCallback == "function") {
-      this._closeCallback.call({}, this);
-    }
+    this.emit("close", this);
+    processes.delete(this);
   }
 };
 
 /**
  * Shortcuts for accessing various debugger preferences.
  */
 let Prefs = new ViewHelpers.Prefs("devtools.debugger", {
   chromeDebuggingHost: ["Char", "chrome-debugging-host"],
--- a/browser/devtools/framework/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -4,19 +4,21 @@
  * 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 Cu = Components.utils;
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 
 let gClient;
 let gConnectionTimeout;
 
 XPCOMUtils.defineLazyGetter(window, 'l10n', function () {
   return Services.strings.createBundle('chrome://browser/locale/devtools/connection-screen.properties');
 });
 
@@ -70,69 +72,107 @@ function submit() {
   let delay = Services.prefs.getIntPref("devtools.debugger.remote-timeout");
   gConnectionTimeout = setTimeout(handleConnectionTimeout, delay);
   gClient.connect(onConnectionReady);
 }
 
 /**
  * Connection is ready. List actors and build buttons.
  */
-function onConnectionReady(aType, aTraits) {
+let onConnectionReady = Task.async(function*(aType, aTraits) {
   clearTimeout(gConnectionTimeout);
-  gClient.listTabs(function(aResponse) {
-    document.body.classList.remove("connecting");
-    document.body.classList.add("actors-mode");
 
-    let parent = document.getElementById("tabActors");
+  let deferred = promise.defer();
+  gClient.listAddons(deferred.resolve);
+  let response = yield deferred.promise;
 
-    // Add Global Process debugging...
-    let globals = JSON.parse(JSON.stringify(aResponse));
-    delete globals.tabs;
-    delete globals.selected;
-    // ...only if there are appropriate actors (a 'from' property will always
-    // be there).
+  let parent = document.getElementById("addonActors")
+  if (!response.error && response.addons.length > 0) {
+    // Add one entry for each add-on.
+    for (let addon of response.addons) {
+      if (!addon.debuggable) {
+        continue;
+      }
+      buildAddonLink(addon, parent);
+    }
+  }
+  else {
+    // Hide the section when there are no add-ons
+    parent.previousElementSibling.remove();
+    parent.remove();
+  }
 
-    // Add one entry for each open tab.
-    for (let i = 0; i < aResponse.tabs.length; i++) {
-      buildLink(aResponse.tabs[i], parent, i == aResponse.selected);
-    }
+  deferred = promise.defer();
+  gClient.listTabs(deferred.resolve);
+  response = yield deferred.promise;
+
+  parent = document.getElementById("tabActors");
 
-    let gParent = document.getElementById("globalActors");
+  // Add Global Process debugging...
+  let globals = JSON.parse(JSON.stringify(response));
+  delete globals.tabs;
+  delete globals.selected;
+  // ...only if there are appropriate actors (a 'from' property will always
+  // be there).
+
+  // Add one entry for each open tab.
+  for (let i = 0; i < response.tabs.length; i++) {
+    buildTabLink(response.tabs[i], parent, i == response.selected);
+  }
+
+  let gParent = document.getElementById("globalActors");
 
-    // Build the Remote Process button
-    if (Object.keys(globals).length > 1) {
-      let a = document.createElement("a");
-      a.onclick = function() {
-        openToolbox(globals, true);
+  // Build the Remote Process button
+  if (Object.keys(globals).length > 1) {
+    let a = document.createElement("a");
+    a.onclick = function() {
+      openToolbox(globals, true);
 
-      }
-      a.title = a.textContent = window.l10n.GetStringFromName("mainProcess");
-      a.className = "remote-process";
-      a.href = "#";
-      gParent.appendChild(a);
     }
-    // Move the selected tab on top
-    let selectedLink = parent.querySelector("a.selected");
-    if (selectedLink) {
-      parent.insertBefore(selectedLink, parent.firstChild);
-    }
+    a.title = a.textContent = window.l10n.GetStringFromName("mainProcess");
+    a.className = "remote-process";
+    a.href = "#";
+    gParent.appendChild(a);
+  }
+  // Move the selected tab on top
+  let selectedLink = parent.querySelector("a.selected");
+  if (selectedLink) {
+    parent.insertBefore(selectedLink, parent.firstChild);
+  }
+
+  document.body.classList.remove("connecting");
+  document.body.classList.add("actors-mode");
 
-    // Ensure the first link is focused
-    let firstLink = parent.querySelector("a:first-of-type");
-    if (firstLink) {
-      firstLink.focus();
-    }
+  // Ensure the first link is focused
+  let firstLink = parent.querySelector("a:first-of-type");
+  if (firstLink) {
+    firstLink.focus();
+  }
+});
 
-  });
+/**
+ * Build one button for an add-on actor.
+ */
+function buildAddonLink(addon, parent) {
+  let a = document.createElement("a");
+  a.onclick = function() {
+    openToolbox({ addonActor: addon.actor, title: addon.name }, true, "jsdebugger");
+  }
+
+  a.textContent = addon.name;
+  a.title = addon.id;
+  a.href = "#";
+
+  parent.appendChild(a);
 }
 
 /**
- * Build one button for an actor.
+ * Build one button for a tab actor.
  */
-function buildLink(tab, parent, selected) {
+function buildTabLink(tab, parent, selected) {
   let a = document.createElement("a");
   a.onclick = function() {
     openToolbox(tab);
   }
 
   a.textContent = tab.title;
   a.title = tab.url;
   if (!a.textContent) {
@@ -168,24 +208,24 @@ function showError(type) {
 function handleConnectionTimeout() {
   showError("timeout");
 }
 
 /**
  * The user clicked on one of the buttons.
  * Opens the toolbox.
  */
-function openToolbox(form, chrome=false) {
+function openToolbox(form, chrome=false, tool="webconsole") {
   let options = {
     form: form,
     client: gClient,
     chrome: chrome
   };
   devtools.TargetFactory.forRemoteTab(options).then((target) => {
     let hostType = devtools.Toolbox.HostType.WINDOW;
-    gDevTools.showToolbox(target, "webconsole", hostType).then((toolbox) => {
+    gDevTools.showToolbox(target, tool, hostType).then((toolbox) => {
       toolbox.once("destroyed", function() {
         gClient.close();
       });
     });
     window.close();
   });
 }
--- a/browser/devtools/framework/connect/connect.xhtml
+++ b/browser/devtools/framework/connect/connect.xhtml
@@ -34,16 +34,18 @@
       </form>
       <p class="error-message error-timeout">&errorTimeout;</p>
       <p class="error-message error-refused">&errorRefused;</p>
       <p class="error-message error-unexpected">&errorUnexpected;</p>
     </section>
     <section id="actors-list">
       <p>&availableTabs;</p>
       <ul class="actors" id="tabActors"></ul>
+      <p>&availableAddons;</p>
+      <ul class="actors" id="addonActors"></ul>
       <p>&availableProcesses;</p>
       <ul class="actors" id="globalActors"></ul>
     </section>
     <section id="connecting">
       <p><img src="chrome://browser/skin/tabbrowser/loading.png"></img> &connecting;</p>
     </section>
     <footer>&remoteHelp;<a target='_' href='https://developer.mozilla.org/docs/Tools/Remote_Debugging'>&remoteDocumentation;</a>&remoteHelpSuffix;</footer>
   </body>
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -817,21 +817,27 @@ MarkupView.prototype = {
           this.updateNodeOuterHTML(aNode, aValue, oldValue);
         }
       });
     });
   },
 
   /**
    * Mark the given node expanded.
-   * @param aNode The NodeFront to mark as expanded.
+   * @param {NodeFront} aNode The NodeFront to mark as expanded.
+   * @param {Boolean} aExpanded Whether the expand or collapse.
+   * @param {Boolean} aExpandDescendants Whether to expand all descendants too
    */
-  setNodeExpanded: function(aNode, aExpanded) {
+  setNodeExpanded: function(aNode, aExpanded, aExpandDescendants) {
     if (aExpanded) {
-      this.expandNode(aNode);
+      if (aExpandDescendants) {
+        this.expandAll(aNode);
+      } else {
+        this.expandNode(aNode);
+      }
     } else {
       this.collapseNode(aNode);
     }
   },
 
   /**
    * Mark the given node selected, and update the inspector.selection
    * object's NodeFront to keep consistent state between UI and selection.
@@ -1408,17 +1414,17 @@ MarkupContainer.prototype = {
       this.elt.classList.add("collapsed");
       this.expander.removeAttribute("open");
     }
   },
 
   _onToggle: function(event) {
     this.markup.navigate(this);
     if(this.hasChildren) {
-      this.markup.setNodeExpanded(this.node, !this.expanded);
+      this.markup.setNodeExpanded(this.node, !this.expanded, event.altKey);
     }
     event.stopPropagation();
   },
 
   _onMouseDown: function(event) {
     let target = event.target;
 
     // Target may be a resource link (generated by the output-parser)
--- a/browser/devtools/markupview/test/browser.ini
+++ b/browser/devtools/markupview/test/browser.ini
@@ -4,16 +4,17 @@ subsuite = devtools
 support-files =
   doc_markup_edit.html
   doc_markup_flashing.html
   doc_markup_mutation.html
   doc_markup_navigation.html
   doc_markup_pagesize_01.html
   doc_markup_pagesize_02.html
   doc_markup_search.html
+  doc_markup_toggle.html
   doc_markup_tooltip.png
   head.js
   helper_attributes_test_runner.js
   helper_outerhtml_test_runner.js
 
 [browser_markupview_copy_image_data.js]
 [browser_markupview_css_completion_style_attribute.js]
 [browser_markupview_highlight_hover_01.js]
@@ -32,8 +33,11 @@ support-files =
 [browser_markupview_tag_edit_02.js]
 [browser_markupview_tag_edit_03.js]
 [browser_markupview_tag_edit_04.js]
 [browser_markupview_tag_edit_05.js]
 [browser_markupview_tag_edit_06.js]
 [browser_markupview_tag_edit_07.js]
 [browser_markupview_tag_edit_08.js]
 [browser_markupview_textcontent_edit_01.js]
+[browser_markupview_toggle_01.js]
+[browser_markupview_toggle_02.js]
+[browser_markupview_toggle_03.js]
--- a/browser/devtools/markupview/test/browser_markupview_navigation.js
+++ b/browser/devtools/markupview/test/browser_markupview_navigation.js
@@ -108,24 +108,16 @@ function pressKey(key) {
       EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
       break;
     case "home":
       EventUtils.synthesizeKey("VK_HOME", {});
       break;
   }
 }
 
-function waitForChildrenUpdated(inspector) {
-  let def = promise.defer();
-  inspector.markup._waitForChildren().then(() => {
-    executeSoon(def.resolve);
-  });
-  return def.promise;
-}
-
 function checkSelectedNode(key, className, inspector) {
   let node = inspector.selection.node;
 
   if (className == "*comment*") {
     is(node.nodeType, Node.COMMENT_NODE, "Found a comment after pressing " + key);
   } else if (className == "*text*") {
     is(node.nodeType, Node.TEXT_NODE, "Found text after pressing " + key);
   } else if (className == "*doctype*") {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_markupview_toggle_01.js
@@ -0,0 +1,45 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test toggling (expand/collapse) elements by clicking on twisties
+
+const TEST_URL = TEST_URL_ROOT + "doc_markup_toggle.html";
+
+let test = asyncTest(function*() {
+  let {inspector} = yield addTab(TEST_URL).then(openInspector);
+
+  info("Getting the container for the UL parent element");
+  let container = getContainerForRawNode("ul", inspector);
+
+  info("Clicking on the UL parent expander, and waiting for children");
+  let onChildren = waitForChildrenUpdated(inspector);
+  let onUpdated = inspector.once("inspector-updated");
+  EventUtils.synthesizeMouseAtCenter(container.expander, {},
+    inspector.markup.doc.defaultView);
+  yield onChildren;
+  yield onUpdated;
+
+  info("Checking that child LI elements have been created");
+  for (let li of content.document.querySelectorAll("li")) {
+    ok(getContainerForRawNode(li, inspector),
+      "A container for the child LI element was created");
+  }
+  ok(container.expanded, "Parent UL container is expanded");
+
+  info("Clicking again on the UL expander");
+  // No need to wait, this is a local, synchronous operation where nodes are
+  // only hidden from the view, not destroyed
+  EventUtils.synthesizeMouseAtCenter(container.expander, {},
+    inspector.markup.doc.defaultView);
+
+  info("Checking that child LI elements have been hidden");
+  for (let li of content.document.querySelectorAll("li")) {
+    let liContainer = getContainerForRawNode(li, inspector);
+    is(liContainer.elt.getClientRects().length, 0,
+      "The container for the child LI element was hidden");
+  }
+  ok(!container.expanded, "Parent UL container is collapsed");
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_markupview_toggle_02.js
@@ -0,0 +1,45 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test toggling (expand/collapse) elements by dbl-clicking on tag lines
+
+const TEST_URL = TEST_URL_ROOT + "doc_markup_toggle.html";
+
+let test = asyncTest(function*() {
+  let {inspector} = yield addTab(TEST_URL).then(openInspector);
+
+  info("Getting the container for the UL parent element");
+  let container = getContainerForRawNode("ul", inspector);
+
+  info("Dbl-clicking on the UL parent expander, and waiting for children");
+  let onChildren = waitForChildrenUpdated(inspector);
+  let onUpdated = inspector.once("inspector-updated");
+  EventUtils.synthesizeMouseAtCenter(container.tagLine, {clickCount: 2},
+    inspector.markup.doc.defaultView);
+  yield onChildren;
+  yield onUpdated;
+
+  info("Checking that child LI elements have been created");
+  for (let li of content.document.querySelectorAll("li")) {
+    ok(getContainerForRawNode(li, inspector),
+      "A container for the child LI element was created");
+  }
+  ok(container.expanded, "Parent UL container is expanded");
+
+  info("Dbl-clicking again on the UL expander");
+  // No need to wait, this is a local, synchronous operation where nodes are
+  // only hidden from the view, not destroyed
+  EventUtils.synthesizeMouseAtCenter(container.tagLine, {clickCount: 2},
+    inspector.markup.doc.defaultView);
+
+  info("Checking that child LI elements have been hidden");
+  for (let li of content.document.querySelectorAll("li")) {
+    let liContainer = getContainerForRawNode(li, inspector);
+    is(liContainer.elt.getClientRects().length, 0,
+      "The container for the child LI element was hidden");
+  }
+  ok(!container.expanded, "Parent UL container is collapsed");
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_markupview_toggle_03.js
@@ -0,0 +1,44 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test toggling (expand/collapse) elements by alt-clicking on twisties, which
+// should expand all the descendants
+
+const TEST_URL = TEST_URL_ROOT + "doc_markup_toggle.html";
+
+let test = asyncTest(function*() {
+  let {inspector} = yield addTab(TEST_URL).then(openInspector);
+
+  info("Getting the container for the UL parent element");
+  let container = getContainerForRawNode("ul", inspector);
+
+  info("Alt-clicking on the UL parent expander, and waiting for children");
+  let onUpdated = inspector.once("inspector-updated");
+  EventUtils.synthesizeMouseAtCenter(container.expander, {altKey: true},
+    inspector.markup.doc.defaultView);
+  yield onUpdated;
+  yield waitForMultipleChildrenUpdates(inspector);
+
+  info("Checking that all nodes exist and are expanded");
+  for (let node of content.document.querySelectorAll("ul, li, span, em")) {
+    let nodeContainer = getContainerForRawNode(node, inspector);
+    ok(nodeContainer, "Container for node " + node.tagName + " exists");
+    ok(nodeContainer.expanded,
+      "Container for node " + node.tagName + " is expanded");
+  }
+});
+
+// The expand all operation of the markup-view calls itself recursively and
+// there's not one event we can wait for to know when it's done
+function* waitForMultipleChildrenUpdates(inspector) {
+  // As long as child updates are queued up while we wait for an update already
+  // wait again
+  if (inspector.markup._queuedChildUpdates &&
+      inspector.markup._queuedChildUpdates.size) {
+    yield waitForChildrenUpdated(inspector);
+    return yield waitForMultipleChildrenUpdates(inspector);
+  }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/doc_markup_toggle.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <title>Expanding and collapsing markup-view containers</title>
+</head>
+<body>
+  <ul>
+    <li>
+      <span>list <em>item</em></span>
+    </li>
+    <li>
+      <span>list <em>item</em></span>
+    </li>
+    <li>
+      <span>list <em>item</em></span>
+    </li>
+    <li>
+      <span>list <em>item</em></span>
+    </li>
+    <li>
+      <span>list <em>item</em></span>
+    </li>
+    <li>
+      <span>list <em>item</em></span>
+    </li>
+  </ul>
+</body>
+</html>
\ No newline at end of file
--- a/browser/devtools/markupview/test/head.js
+++ b/browser/devtools/markupview/test/head.js
@@ -162,16 +162,33 @@ function selectNode(nodeOrSelector, insp
 function getContainerForRawNode(nodeOrSelector, {markup}) {
   let front = markup.walker.frontForRawNode(getNode(nodeOrSelector));
   let container = markup.getContainer(front);
   info("Markup-container object for " + nodeOrSelector + " " + container);
   return container;
 }
 
 /**
+ * Using the markupview's _waitForChildren function, wait for all queued
+ * children updates to be handled.
+ * @param {InspectorPanel} inspector The instance of InspectorPanel currently
+ * loaded in the toolbox
+ * @return a promise that resolves when all queued children updates have been
+ * handled
+ */
+function waitForChildrenUpdated({markup}) {
+  info("Waiting for queued children updates to be handled");
+  let def = promise.defer();
+  markup._waitForChildren().then(() => {
+    executeSoon(def.resolve);
+  });
+  return def.promise;
+}
+
+/**
  * Simulate a mouse-over on the markup-container (a line in the markup-view)
  * that corresponds to the node or selector passed.
  * @param {String|DOMNode} nodeOrSelector
  * @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
  * @return a promise that resolves when the container is hovered and the higlighter
  * is shown on the corresponding node
  */
 function hoverContainer(nodeOrSelector, inspector) {
@@ -347,8 +364,20 @@ function getSelectorSearchBox(inspector)
  */
 function searchUsingSelectorSearch(selector, inspector) {
   info("Entering \"" + selector + "\" into the selector-search input field");
   let field = getSelectorSearchBox(inspector);
   field.focus();
   field.value = selector;
   EventUtils.sendKey("return", inspector.panelWin);
 }
+
+/**
+ * This shouldn't be used in the tests, but is useful when writing new tests or
+ * debugging existing tests in order to introduce delays in the test steps
+ * @param {Number} ms The time to wait
+ * @return A promise that resolves when the time is passed
+ */
+function wait(ms) {
+  let def = promise.defer();
+  content.setTimeout(def.resolve, ms);
+  return def.promise;
+}
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -37,21 +37,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 // would.
 XPCOMUtils.defineLazyGetter(this, "CertUtils",
   function() {
     var mod = {};
     Cu.import("resource://gre/modules/CertUtils.jsm", mod);
     return mod;
   });
 
-#ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
                                    "@mozilla.org/xre/app-info;1",
                                    "nsICrashReporter");
-#endif
 
 const FILE_CACHE                = "experiments.json";
 const OBSERVER_TOPIC            = "experiments-changed";
 const MANIFEST_VERSION          = 1;
 const CACHE_VERSION             = 1;
 
 const KEEP_HISTORY_N_DAYS       = 180;
 const MIN_EXPERIMENT_ACTIVE_SECONDS = 60;
@@ -71,46 +69,59 @@ const PREF_HEALTHREPORT_ENABLED = "datar
 
 const PREF_BRANCH_TELEMETRY     = "toolkit.telemetry.";
 const PREF_TELEMETRY_ENABLED    = "enabled";
 
 const TELEMETRY_LOG = {
   // log(key, [kind, experimentId, details])
   ACTIVATION_KEY: "EXPERIMENT_ACTIVATION",
   ACTIVATION: {
-    ACTIVATED: "ACTIVATED",             // successfully activated
-    INSTALL_FAILURE: "INSTALL_FAILURE", // failed to install the extension
-    REJECTED: "REJECTED",               // experiment was rejected because of it's conditions,
-                                        // provides details on which
+    // Successfully activated.
+    ACTIVATED: "ACTIVATED",
+    // Failed to install the add-on.
+    INSTALL_FAILURE: "INSTALL_FAILURE",
+    // Experiment does not meet activation requirements. Details will
+    // be provided.
+    REJECTED: "REJECTED",
   },
 
   // log(key, [kind, experimentId, optionalDetails...])
   TERMINATION_KEY: "EXPERIMENT_TERMINATION",
   TERMINATION: {
-    USERDISABLED: "USERDISABLED", // the user disabled this experiment
-    FROM_API: "FROM_API",         // the experiment disabled itself
-    EXPIRED: "EXPIRED",           // experiment expired e.g. by exceeding the end-date
-    RECHECK: "RECHECK",           // disabled after re-evaluating conditions,
-                                  // provides details on which
+    // The Experiments service was disabled.
+    SERVICE_DISABLED: "SERVICE_DISABLED",
+    // Add-on uninstalled.
+    ADDON_UNINSTALLED: "ADDON_UNINSTALLED",
+    // The experiment disabled itself.
+    FROM_API: "FROM_API",
+    // The experiment expired (e.g. by exceeding the end date).
+    EXPIRED: "EXPIRED",
+    // Disabled after re-evaluating conditions. If this is specified,
+    // details will be provided.
+    RECHECK: "RECHECK",
   },
 };
 
 const gPrefs = new Preferences(PREF_BRANCH);
 const gPrefsTelemetry = new Preferences(PREF_BRANCH_TELEMETRY);
 let gExperimentsEnabled = false;
 let gExperiments = null;
 let gLogAppenderDump = null;
 let gPolicyCounter = 0;
 let gExperimentsCounter = 0;
 let gExperimentEntryCounter = 0;
 
 // Tracks active AddonInstall we know about so we can deny external
 // installs.
 let gActiveInstallURLs = new Set();
 
+// Tracks add-on IDs that are being uninstalled by us. This allows us
+// to differentiate between expected uninstalled and user-driven uninstalls.
+let gActiveUninstallAddonIDs = new Set();
+
 let gLogger;
 let gLogDumping = false;
 
 function configureLogging() {
   if (!gLogger) {
     gLogger = Log.repository.getLogger("Browser.Experiments");
     gLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
   }
@@ -311,16 +322,24 @@ Experiments.Policy.prototype = {
     });
   },
 
   telemetryPayload: function () {
     return TelemetryPing.getPayload();
   },
 };
 
+function AlreadyShutdownError(message="already shut down") {
+  this.name = "AlreadyShutdownError";
+  this.message = message;
+}
+
+AlreadyShutdownError.prototype = new Error();
+AlreadyShutdownError.prototype.constructor = AlreadyShutdownError;
+
 /**
  * Manages the experiments and provides an interface to control them.
  */
 
 Experiments.Experiments = function (policy=new Experiments.Policy()) {
   this._log = Log.repository.getLoggerWithMessagePrefix(
     "Browser.Experiments.Experiments",
     "Experiments #" + gExperimentsCounter++ + "::");
@@ -335,19 +354,16 @@ Experiments.Experiments = function (poli
   this._experiments = null;
   this._refresh = false;
   this._terminateReason = null; // or TELEMETRY_LOG.TERMINATION....
   this._dirty = false;
 
   // Loading the cache happens once asynchronously on startup
   this._loadTask = null;
 
-  // Ignore addon-manager notifications for addons that we are uninstalling ourself
-  this._pendingUninstall = null;
-
   // The _main task handles all other actions:
   // * refreshing the manifest off the network (if _refresh)
   // * disabling/enabling experiments
   // * saving the cache (if _dirty)
   this._mainTask = null;
 
   // Timer for re-evaluating experiment status.
   this._timer = null;
@@ -421,17 +437,21 @@ Experiments.Experiments.prototype = {
 
       if (this._timer) {
         this._timer.clear();
       }
     }
 
     this._shutdown = true;
     if (this._mainTask) {
-      yield this._mainTask;
+      try {
+        yield this._mainTask;
+      } catch (e if e instanceof AlreadyShutdownError) {
+        // We error out of tasks after shutdown via that exception.
+      }
     }
 
     this._log.info("Completed uninitialization.");
   }),
 
   _registerWithAddonManager: function () {
     this._log.trace("Registering instance with Addon Manager.");
 
@@ -444,17 +464,17 @@ Experiments.Experiments.prototype = {
     AddonManager.removeAddonListener(this);
   },
 
   /**
    * Throws an exception if we've already shut down.
    */
   _checkForShutdown: function() {
     if (this._shutdown) {
-      throw Error("uninit() already called");
+      throw new AlreadyShutdownError("uninit() already called");
     }
   },
 
   /**
    * Whether the experiments feature is enabled.
    */
   get enabled() {
     return gExperimentsEnabled;
@@ -463,34 +483,34 @@ Experiments.Experiments.prototype = {
   /**
    * Toggle whether the experiments feature is enabled or not.
    */
   set enabled(enabled) {
     this._log.trace("set enabled(" + enabled + ")");
     gPrefs.set(PREF_ENABLED, enabled);
   },
 
-  _toggleExperimentsEnabled: function (enabled) {
+  _toggleExperimentsEnabled: Task.async(function* (enabled) {
     this._log.trace("_toggleExperimentsEnabled(" + enabled + ")");
     let wasEnabled = gExperimentsEnabled;
     gExperimentsEnabled = enabled && telemetryEnabled();
 
     if (wasEnabled == gExperimentsEnabled) {
       return;
     }
 
     if (gExperimentsEnabled) {
-      this.updateManifest();
+      yield this.updateManifest();
     } else {
-      this.disableExperiment();
+      yield this.disableExperiment(TELEMETRY_LOG.TERMINATION.SERVICE_DISABLED);
       if (this._timer) {
         this._timer.clear();
       }
     }
-  },
+  }),
 
   _telemetryStatusChanged: function () {
     this._toggleExperimentsEnabled(gExperimentsEnabled);
   },
 
   /**
    * Returns a promise that is resolved with an array of `ExperimentInfo` objects,
    * which provide info on the currently and recently active experiments.
@@ -662,39 +682,28 @@ Experiments.Experiments.prototype = {
   notify: function (timer) {
     this._log.trace("notify()");
     this._checkForShutdown();
     return this._run();
   },
 
   // START OF ADD-ON LISTENERS
 
-  onDisabled: function (addon) {
-    this._log.trace("onDisabled() - addon id: " + addon.id);
-    if (addon.id == this._pendingUninstall) {
-      return;
-    }
-    let activeExperiment = this._getActiveExperiment();
-    if (!activeExperiment || activeExperiment._addonId != addon.id) {
-      return;
-    }
-    this.disableExperiment();
-  },
-
   onUninstalled: function (addon) {
     this._log.trace("onUninstalled() - addon id: " + addon.id);
-    if (addon.id == this._pendingUninstall) {
+    if (gActiveUninstallAddonIDs.has(addon.id)) {
       this._log.trace("matches pending uninstall");
       return;
     }
     let activeExperiment = this._getActiveExperiment();
     if (!activeExperiment || activeExperiment._addonId != addon.id) {
       return;
     }
-    this.disableExperiment();
+
+    this.disableExperiment(TELEMETRY_LOG.TERMINATION.ADDON_UNINSTALLED);
   },
 
   onInstallStarted: function (install) {
     if (install.addon.type != "experiment") {
       return;
     }
 
     // We want to be in control of all experiment add-ons: reject installs
@@ -922,25 +931,27 @@ Experiments.Experiments.prototype = {
       this._log.error("getActiveExperimentId() - should not have more than 1 active experiment");
       throw new Error("have more than 1 active experiment");
     }
 
     return null;
   },
 
   /**
-   * Disable an experiment by id.
-   * @param experimentId The id of the experiment.
-   * @param userDisabled (optional) Whether this is disabled as a result of a user action.
+   * Disables all active experiments.
+   *
    * @return Promise<> Promise that will get resolved once the task is done or failed.
    */
-  disableExperiment: function (userDisabled=true) {
+  disableExperiment: function (reason) {
+    if (!reason) {
+      throw new Error("Must specify a termination reason.");
+    }
+
     this._log.trace("disableExperiment()");
-
-    this._terminateReason = userDisabled ? TELEMETRY_LOG.TERMINATION.USERDISABLED : TELEMETRY_LOG.TERMINATION.FROM_API;
+    this._terminateReason = reason;
     return this._run();
   },
 
   /**
    * The Set of add-on IDs that we know about from manifests.
    */
   get _trackedAddonIds() {
     if (!this._experiments) {
@@ -987,54 +998,53 @@ Experiments.Experiments.prototype = {
     let activeChanged = false;
     let now = this._policy.now();
 
     if (!activeExperiment) {
       // Avoid this pref staying out of sync if there were e.g. crashes.
       gPrefs.set(PREF_ACTIVE_EXPERIMENT, false);
     }
 
+    // Ensure the active experiment is in the proper state. This may install,
+    // uninstall, upgrade, or enable the experiment add-on. What exactly is
+    // abstracted away from us by design.
     if (activeExperiment) {
-      this._pendingUninstall = activeExperiment._addonId;
-      try {
-        let wasStopped;
-        if (this._terminateReason) {
-          yield activeExperiment.stop(this._terminateReason);
-          wasStopped = true;
+      let changes;
+      let shouldStopResult = yield activeExperiment.shouldStop();
+      if (shouldStopResult.shouldStop) {
+        let expireReasons = ["endTime", "maxActiveSeconds"];
+        let kind, reason;
+
+        if (expireReasons.indexOf(shouldStopResult.reason[0]) != -1) {
+          kind = TELEMETRY_LOG.TERMINATION.EXPIRED;
+          reason = null;
         } else {
-          wasStopped = yield activeExperiment.maybeStop();
+          kind = TELEMETRY_LOG.TERMINATION.RECHECK;
+          reason = shouldStopResult.reason;
         }
-        if (wasStopped) {
-          this._dirty = true;
-          this._log.debug("evaluateExperiments() - stopped experiment "
-                        + activeExperiment.id);
-          activeExperiment = null;
-          activeChanged = true;
-        } else if (!gExperimentsEnabled) {
-          // No further actions if the feature is disabled.
-        } else if (activeExperiment.needsUpdate) {
-          this._log.debug("evaluateExperiments() - updating experiment "
-                        + activeExperiment.id);
-          try {
-            yield activeExperiment.stop();
-            yield activeExperiment.start();
-          } catch (e) {
-            this._log.error(e);
-            // On failure try the next experiment.
-            activeExperiment = null;
-          }
-          this._dirty = true;
-          activeChanged = true;
-        } else {
-          yield activeExperiment.ensureActive();
-        }
-      } finally {
-        this._pendingUninstall = null;
+        changes = yield activeExperiment.stop(kind, reason);
+      }
+      else if (this._terminateReason) {
+        changes = yield activeExperiment.stop(this._terminateReason);
+      }
+      else {
+        changes = yield activeExperiment.reconcileAddonState();
+      }
+
+      if (changes) {
+        this._dirty = true;
+        activeChanged = true;
+      }
+
+      if (!activeExperiment._enabled) {
+        activeExperiment = null;
+        activeChanged = true;
       }
     }
+
     this._terminateReason = null;
 
     if (!activeExperiment && gExperimentsEnabled) {
       for (let [id, experiment] of this._experiments) {
         let applicable;
         let reason = null;
         try {
           applicable = yield experiment.isApplicable();
@@ -1047,40 +1057,45 @@ Experiments.Experiments.prototype = {
         if (!applicable && reason && reason[0] != "was-active") {
           // Report this from here to avoid over-reporting.
           let desc = TELEMETRY_LOG.ACTIVATION;
           let data = [TELEMETRY_LOG.ACTIVATION.REJECTED, id];
           data = data.concat(reason);
           TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY, data);
         }
 
-        if (applicable) {
-          this._log.debug("evaluateExperiments() - activating experiment " + id);
-          try {
-            yield experiment.start();
-            activeChanged = true;
-            activeExperiment = experiment;
-            this._dirty = true;
-            break;
-          } catch (e) {
-            // On failure try the next experiment.
-          }
+        if (!applicable) {
+          continue;
+        }
+
+        this._log.debug("evaluateExperiments() - activating experiment " + id);
+        try {
+          yield experiment.start();
+          activeChanged = true;
+          activeExperiment = experiment;
+          this._dirty = true;
+          break;
+        } catch (e) {
+          // On failure, clean up the best we can and try the next experiment.
+          this._log.error("evaluateExperiments() - Unable to start experiment: " + e.message);
+          experiment._enabled = false;
+          yield experiment.reconcileAddonState();
         }
       }
     }
 
+    gPrefs.set(PREF_ACTIVE_EXPERIMENT, activeExperiment != null);
+
     if (activeChanged) {
       Services.obs.notifyObservers(null, OBSERVER_TOPIC, null);
     }
 
-#ifdef MOZ_CRASHREPORTER
-    if (activeExperiment) {
+    if ("@mozilla.org/toolkit/crash-reporter;1" in Cc && activeExperiment) {
       gCrashReporter.annotateCrashReport("ActiveExperiment", activeExperiment.id);
     }
-#endif
   },
 
   /*
    * Schedule the soonest re-check of experiment applicability that is needed.
    */
   _scheduleNextRun: function () {
     this._checkForShutdown();
 
@@ -1122,17 +1137,17 @@ Experiments.Experiments.prototype = {
  */
 
 Experiments.ExperimentEntry = function (policy) {
   this._policy = policy || new Experiments.Policy();
   this._log = Log.repository.getLoggerWithMessagePrefix(
     "Browser.Experiments.Experiments",
     "ExperimentEntry #" + gExperimentEntryCounter++ + "::");
 
-  // Is this experiment running?
+  // Is the experiment supposed to be running.
   this._enabled = false;
   // When this experiment was started, if ever.
   this._startDate = null;
   // When this experiment was ended, if ever.
   this._endDate = null;
   // The condition data from the manifest.
   this._manifestData = null;
   // For an active experiment, signifies whether we need to update the xpi.
@@ -1193,16 +1208,21 @@ Experiments.ExperimentEntry.prototype = 
     "_endDate",
   ]),
 
   DATE_KEYS: new Set([
     "_startDate",
     "_endDate",
   ]),
 
+  ADDON_CHANGE_NONE: 0,
+  ADDON_CHANGE_INSTALL: 1,
+  ADDON_CHANGE_UNINSTALL: 2,
+  ADDON_CHANGE_ENABLE: 4,
+
   /*
    * Initialize entry from the manifest.
    * @param data The experiment data from the manifest.
    * @return boolean Whether initialization succeeded.
    */
   initFromManifestData: function (data) {
     if (!this._isManifestDataValid(data)) {
       return false;
@@ -1482,36 +1502,28 @@ Experiments.ExperimentEntry.prototype = 
       }
 
       throw new Task.Result(true);
     }.bind(this));
   },
 
   /*
    * Start running the experiment.
+   *
    * @return Promise<> Resolved when the operation is complete.
    */
-  start: function () {
+  start: Task.async(function* () {
     this._log.trace("start() for " + this.id);
 
-    return Task.spawn(function* ExperimentEntry_start_task() {
-      let addons = yield installedExperimentAddons();
-      if (addons.length > 0) {
-        this._log.error("start() - there are already "
-                        + addons.length + " experiment addons installed");
-        yield uninstallAddons(addons);
-      }
-
-      yield this._installAddon();
-      gPrefs.set(PREF_ACTIVE_EXPERIMENT, true);
-    }.bind(this));
-  },
+    this._enabled = true;
+    return yield this.reconcileAddonState();
+  }),
 
   // Async install of the addon for this experiment, part of the start task above.
-  _installAddon: function* () {
+  _installAddon: Task.async(function* () {
     let deferred = Promise.defer();
 
     let hash = this._policy.ignoreHashes ? null : this._manifestData.xpiHash;
 
     let install = yield addonInstallForURL(this._manifestData.xpiURL, hash);
     gActiveInstallURLs.add(install.sourceURI.spec);
 
     let failureHandler = (install, handler) => {
@@ -1600,108 +1612,146 @@ Experiments.ExperimentEntry.prototype = 
     ["onDownloadCancelled", "onDownloadFailed", "onInstallCancelled", "onInstallFailed"]
       .forEach(what => {
         listener[what] = install => failureHandler(install, what)
       });
 
     install.addListener(listener);
     install.install();
 
-    return deferred.promise;
-  },
+    return yield deferred.promise;
+  }),
 
-  /*
+  /**
    * Stop running the experiment if it is active.
-   * @param terminationKind (optional) The termination kind, e.g. USERDISABLED or EXPIRED.
-   * @param terminationReason (optional) The termination reason details for
-   *                          termination kind RECHECK.
+   *
+   * @param terminationKind (optional)
+   *        The termination kind, e.g. ADDON_UNINSTALLED or EXPIRED.
+   * @param terminationReason (optional)
+   *        The termination reason details for termination kind RECHECK.
    * @return Promise<> Resolved when the operation is complete.
    */
-  stop: function (terminationKind, terminationReason) {
+  stop: Task.async(function* (terminationKind, terminationReason) {
     this._log.trace("stop() - id=" + this.id + ", terminationKind=" + terminationKind);
     if (!this._enabled) {
-      this._log.warning("stop() - experiment not enabled: " + id);
-      return Promise.reject();
+      throw new Error("Must not call stop() on an inactive experiment.");
     }
 
     this._enabled = false;
-    gPrefs.set(PREF_ACTIVE_EXPERIMENT, false);
+
+    let changes = yield this.reconcileAddonState();
+    let now = this._policy.now();
+    this._lastChangedDate = now;
+    this._endDate = now;
+    this._logTermination(terminationKind, terminationReason);
+
+    return changes;
+  }),
 
-    let deferred = Promise.defer();
-    let updateDates = () => {
-      let now = this._policy.now();
-      this._lastChangedDate = now;
-      this._endDate = now;
-    };
+  /**
+   * Reconcile the state of the add-on against what it's supposed to be.
+   *
+   * If we are active, ensure the add-on is enabled and up to date.
+   *
+   * If we are inactive, ensure the add-on is not installed.
+   */
+  reconcileAddonState: Task.async(function* () {
+    this._log.trace("reconcileAddonState()");
 
-    this._getAddon().then((addon) => {
-      if (!addon) {
-        let message = "could not get Addon for " + this.id;
-        this._log.warn("stop() - " + message);
-        updateDates();
-        deferred.resolve();
-        return;
+    if (!this._enabled) {
+      if (!this._addonId) {
+        this._log.trace("reconcileAddonState() - Experiment is not enabled and " +
+                        "has no add-on. Doing nothing.");
+        return this.ADDON_CHANGE_NONE;
       }
 
-      updateDates();
-      this._logTermination(terminationKind, terminationReason);
-      deferred.resolve(uninstallAddons([addon]));
-    });
+      let addon = yield this._getAddon();
+      if (!addon) {
+        this._log.trace("reconcileAddonState() - Inactive experiment has no " +
+                        "add-on. Doing nothing.");
+        return this.ADDON_CHANGE_NONE;
+      }
 
-    return deferred.promise;
-  },
+      this._log.info("reconcileAddonState() - Uninstalling add-on for inactive " +
+                     "experiment: " + addon.id);
+      gActiveUninstallAddonIDs.add(addon.id);
+      yield uninstallAddons([addon]);
+      gActiveUninstallAddonIDs.delete(addon.id);
+      return this.ADDON_CHANGE_UNINSTALL;
+    }
+
+    // If we get here, we're supposed to be active.
+
+    let changes = 0;
 
-  /**
-   * Try to ensure this experiment is active.
-   *
-   * The returned promise will be resolved if the experiment is active
-   * in the Addon Manager or rejected if it isn't.
-   *
-   * @return Promise<>
-   */
-  ensureActive: Task.async(function* () {
-    this._log.trace("ensureActive() for " + this.id);
+    // That requires an add-on.
+    let currentAddon = yield this._getAddon();
+
+    // If we have an add-on but it isn't up to date, uninstall it
+    // (to prepare for reinstall).
+    if (currentAddon && this._needsUpdate) {
+      this._log.info("reconcileAddonState() - Uninstalling add-on because update " +
+                     "needed: " + currentAddon.id);
+      gActiveUninstallAddonIDs.add(currentAddon.id);
+      yield uninstallAddons([currentAddon]);
+      gActiveUninstallAddonIDs.delete(currentAddon.id);
+      changes |= this.ADDON_CHANGE_UNINSTALL;
+    }
+
+    if (!currentAddon || this._needsUpdate) {
+      this._log.info("reconcileAddonState() - Installing add-on.");
+      yield this._installAddon();
+      changes |= this.ADDON_CHANGE_INSTALL;
+    }
 
     let addon = yield this._getAddon();
     if (!addon) {
-      this._log.warn("Experiment is not installed: " + this._addonId);
-      throw new Error("Experiment is not installed: " + this._addonId);
+      throw new Error("Could not obtain add-on for experiment that should be " +
+                      "enabled.");
     }
 
-    // User disabled likely means the experiment is disabled at startup,
-    // since the permissions don't allow it to be disabled by the user.
+    // If we have the add-on and it is enabled, we are done.
     if (!addon.userDisabled) {
-      return;
+      return changes;
     }
 
     let deferred = Promise.defer();
 
+    // Else we need to enable it.
     let listener = {
       onEnabled: enabledAddon => {
         if (enabledAddon.id != addon.id) {
           return;
         }
 
         AddonManager.removeAddonListener(listener);
         deferred.resolve();
       },
     };
 
     this._log.info("Activating add-on: " + addon.id);
     AddonManager.addAddonListener(listener);
     addon.userDisabled = false;
     yield deferred.promise;
-  }),
+    changes |= this.ADDON_CHANGE_ENABLE;
+
+    this._log.info("Add-on has been enabled: " + addon.id);
+    return changes;
+   }),
 
   /**
    * Obtain the underlying Addon from the Addon Manager.
    *
    * @return Promise<Addon|null>
    */
   _getAddon: function () {
+    if (!this._addonId) {
+      return Promise.resolve(null);
+    }
+
     let deferred = Promise.defer();
 
     AddonManager.getAddonByID(this._addonId, deferred.resolve);
 
     return deferred.promise;
   },
 
   _logTermination: function (terminationKind, terminationReason) {
@@ -1717,53 +1767,28 @@ Experiments.ExperimentEntry.prototype = 
     let data = [terminationKind, this.id];
     if (terminationReason) {
       data = data.concat(terminationReason);
     }
 
     TelemetryLog.log(TELEMETRY_LOG.TERMINATION_KEY, data);
   },
 
-  /*
-   * Stop if experiment stop criteria are met.
-   * @return Promise<boolean> Resolved when done stopping or checking,
-   *                          the value indicates whether it was stopped.
+  /**
+   * Determine whether an active experiment should be stopped.
    */
-  maybeStop: function () {
-    this._log.trace("maybeStop()");
-    return Task.spawn(function* ExperimentEntry_maybeStop_task() {
-      if (!gExperimentsEnabled) {
-        this._log.warn("maybeStop() - should not get here");
-        yield this.stop(TELEMETRY_LOG.TERMINATION.FROM_API);
-        return true;
-      }
+  shouldStop: function () {
+    if (!this._enabled) {
+      throw new Error("shouldStop must not be called on disabled experiments.");
+    }
 
-      let result = yield this._shouldStop();
-      if (result.shouldStop) {
-        let expireReasons = ["endTime", "maxActiveSeconds"];
-        if (expireReasons.indexOf(result.reason[0]) != -1) {
-          yield this.stop(TELEMETRY_LOG.TERMINATION.EXPIRED);
-        } else {
-          yield this.stop(TELEMETRY_LOG.TERMINATION.RECHECK, result.reason);
-        }
-      }
-
-      return result.shouldStop;
-    }.bind(this));
-  },
-
-  _shouldStop: function () {
     let data = this._manifestData;
     let now = this._policy.now() / 1000; // The manifest times are in seconds.
     let maxActiveSec = data.maxActiveSeconds || 0;
 
-    if (!this._enabled) {
-      return Promise.resolve({shouldStop: false});
-    }
-
     let deferred = Promise.defer();
     this.isApplicable().then(
       () => deferred.resolve({shouldStop: false}),
       reason => deferred.resolve({shouldStop: true, reason: reason})
     );
 
     return deferred.promise;
   },
--- a/browser/experiments/moz.build
+++ b/browser/experiments/moz.build
@@ -4,15 +4,15 @@
 
 EXTRA_COMPONENTS += [
     'Experiments.manifest',
     'ExperimentsService.js',
 ]
 
 JS_MODULES_PATH = 'modules/experiments'
 
-EXTRA_PP_JS_MODULES += [
+EXTRA_JS_MODULES += [
   'Experiments.jsm',
 ]
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
 
 SPHINX_TREES['experiments'] = 'docs'
--- a/browser/experiments/test/xpcshell/test_activate.js
+++ b/browser/experiments/test/xpcshell/test_activate.js
@@ -97,51 +97,52 @@ add_task(function* test_startStop() {
   let addons = yield getExperimentAddons();
   Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
 
   defineNow(gPolicy, futureDate(startDate, 5 * MS_IN_ONE_DAY));
   result = yield isApplicable(experiment);
   Assert.equal(result.applicable, true, "Experiment should now be applicable.");
   Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
 
-  yield experiment.start();
+  let changes = yield experiment.start();
+  Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
   addons = yield getExperimentAddons();
   Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
   Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
   Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
   Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
   Assert.ok(addons[0].isActive, "The add-on is active.");
 
-  yield experiment.stop();
+  changes = yield experiment.stop();
+  Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on was uninstalled.");
   addons = yield getExperimentAddons();
   Assert.equal(experiment.enabled, false, "Experiment should not be enabled.");
   Assert.equal(addons.length, 0, "Experiment should be uninstalled from the Addon Manager.");
 
-  yield experiment.start();
+  changes = yield experiment.start();
+  Assert.equal(changes, experiment.ADDON_CHANGE_INSTALL, "Add-on was installed.");
   addons = yield getExperimentAddons();
   Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
   Assert.equal(addons.length, 1, "1 experiment add-on is installed.");
   Assert.equal(addons[0].id, experiment._addonId, "The add-on is the one we expect.");
   Assert.equal(addons[0].userDisabled, false, "The add-on is not userDisabled.");
   Assert.ok(addons[0].isActive, "The add-on is active.");
 
-  let result = yield experiment._shouldStop();
+  let result = yield experiment.shouldStop();
   Assert.equal(result.shouldStop, false, "shouldStop should be false.");
-  let maybeStop = yield experiment.maybeStop();
-  Assert.equal(maybeStop, false, "Experiment should not have been stopped.");
   Assert.equal(experiment.enabled, true, "Experiment should be enabled.");
   addons = yield getExperimentAddons();
   Assert.equal(addons.length, 1, "Experiment still in add-ons manager.");
   Assert.ok(addons[0].isActive, "The add-on is still active.");
 
   defineNow(gPolicy, futureDate(endDate, MS_IN_ONE_DAY));
-  result = yield experiment._shouldStop();
+  result = yield experiment.shouldStop();
   Assert.equal(result.shouldStop, true, "shouldStop should now be true.");
-  maybeStop = yield experiment.maybeStop();
-  Assert.equal(maybeStop, true, "Experiment should have been stopped.");
+  changes = yield experiment.stop();
+  Assert.equal(changes, experiment.ADDON_CHANGE_UNINSTALL, "Add-on should be uninstalled.");
   Assert.equal(experiment.enabled, false, "Experiment should be disabled.");
   addons = yield getExperimentAddons();
   Assert.equal(addons.length, 0, "Experiment add-on is uninstalled.");
 
   // Ensure hash validation works.
   // We set an incorrect hash and expect the install to fail.
   experiment._manifestData.xpiHash = "sha1:41014dcc66b4dcedcd973491a1530a32f0517d8a";
   let errored = false;
--- a/browser/experiments/test/xpcshell/test_api.js
+++ b/browser/experiments/test/xpcshell/test_api.js
@@ -419,17 +419,17 @@ add_task(function* test_disableExperimen
     Assert.equal(experimentInfo[k], list[0][k],
                  "Property " + k + " should match reference data.");
   }
 
   // Test disabling the experiment.
 
   now = futureDate(now, 1 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
-  yield experiments.disableExperiment();
+  yield experiments.disableExperiment("foo");
 
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
 
   experimentInfo.active = false;
   experimentInfo.endDate = now.getTime();
   for (let k of Object.keys(experimentInfo)) {
     Assert.equal(experimentInfo[k], list[0][k],
@@ -744,17 +744,17 @@ add_task(function* test_userDisabledAndU
   let todayActive = yield experiments.lastActiveToday();
   Assert.ok(todayActive, "Last active for today reports a value.");
   Assert.equal(todayActive.id, list[0].id, "The entry is what we expect.");
 
   // Explicitly disable an experiment.
 
   now = futureDate(now, 20 * MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
-  yield experiments.disableExperiment();
+  yield experiments.disableExperiment("foo");
   Assert.equal(observerFireCount, ++expectedObserverFireCount,
                "Experiments observer should have been called.");
 
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
   Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
   Assert.equal(list[0].active, false, "Experiment should not be active anymore.");
   todayActive = yield experiments.lastActiveToday();
@@ -1400,17 +1400,17 @@ add_task(function* testForeignExperiment
     experiments: [],
   };
 
   yield experiments.init();
 
   let addons = yield getExperimentAddons();
   Assert.equal(addons.length, 0, "Precondition: No experiment add-ons present.");
 
-  let failed;
+  let failed = false;
   try {
     yield AddonTestUtils.installXPIFromURL(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
   } catch (ex) {
     failed = true;
   }
   Assert.ok(failed, "Add-on install should not have completed successfully");
   addons = yield getExperimentAddons();
   Assert.equal(addons.length, 0, "Add-on install should have been cancelled.");
--- a/browser/experiments/test/xpcshell/test_cache.js
+++ b/browser/experiments/test/xpcshell/test_cache.js
@@ -238,12 +238,12 @@ add_task(function* test_cache() {
 
   experimentListData[0].active = false;
   experimentListData[0].endDate = now.getTime();
   checkExperimentListsEqual(experimentListData, list);
   checkExperimentSerializations(experiments._experiments.values());
 
   // Cleanup.
 
-  yield experiments.disableExperiment();
+  yield experiments._toggleExperimentsEnabled(false);
   yield experiments.uninit();
   yield removeCacheFile();
 });
--- a/browser/experiments/test/xpcshell/test_telemetry.js
+++ b/browser/experiments/test/xpcshell/test_telemetry.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://gre/modules/TelemetryLog.jsm");
 Cu.import("resource://gre/modules/TelemetryPing.jsm");
-Cu.import("resource:///modules/experiments/Experiments.jsm");
+let bsp = Cu.import("resource:///modules/experiments/Experiments.jsm");
 
 
 const FILE_MANIFEST            = "experiments.manifest";
 const MANIFEST_HANDLER         = "manifests/handler";
 
 const SEC_IN_ONE_DAY  = 24 * 60 * 60;
 const MS_IN_ONE_DAY   = SEC_IN_ONE_DAY * 1000;
 
@@ -20,35 +20,17 @@ let gProfileDir          = null;
 let gHttpServer          = null;
 let gHttpRoot            = null;
 let gDataRoot            = null;
 let gReporter            = null;
 let gPolicy              = null;
 let gManifestObject      = null;
 let gManifestHandlerURI  = null;
 
-const TLOG = {
-  // log(key, [kind, experimentId, details])
-  ACTIVATION_KEY: "EXPERIMENT_ACTIVATION",
-  ACTIVATION: {
-    ACTIVATED: "ACTIVATED",
-    INSTALL_FAILURE: "INSTALL_FAILURE",
-    REJECTED: "REJECTED",
-  },
-
-  // log(key, [kind, experimentId, optionalDetails...])
-  TERMINATION_KEY: "EXPERIMENT_TERMINATION",
-  TERMINATION: {
-    USERDISABLED: "USERDISABLED",
-    FROM_API: "FROM_API",
-    EXPIRED: "EXPIRED",
-    RECHECK: "RECHECK",
-  },
-};
-
+const TLOG = bsp.TELEMETRY_LOG;
 
 let gGlobalScope = this;
 function loadAddonManager() {
   let ns = {};
   Cu.import("resource://gre/modules/Services.jsm", ns);
   let head = "../../../../toolkit/mozapps/extensions/test/xpcshell/head_addons.js";
   let file = do_get_file(head);
   let uri = ns.Services.io.newFileURI(file);
@@ -258,30 +240,30 @@ add_task(function* test_telemetryBasics(
   Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
 
   expectedLogLength += 1;
   log = TelemetryPing.getPayload().log;
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
              [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT2_ID]);
 
-  // Fake user-disable of an experiment.
+  // Fake user uninstall of experiment via add-on manager.
 
   now = futureDate(now, MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
 
-  yield experiments.disableExperiment();
+  yield experiments.disableExperiment(TLOG.TERMINATION.ADDON_UNINSTALLED);
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
 
   expectedLogLength += 1;
   log = TelemetryPing.getPayload().log;
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
-             [TLOG.TERMINATION.USERDISABLED, EXPERIMENT2_ID]);
+             [TLOG.TERMINATION.ADDON_UNINSTALLED, EXPERIMENT2_ID]);
 
   // Trigger update with experiment 1a ready to start.
 
   now = futureDate(now, MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
   gManifestObject.experiments[0].id      = EXPERIMENT3_ID;
   gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY));
 
@@ -290,22 +272,22 @@ add_task(function* test_telemetryBasics(
   Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
 
   expectedLogLength += 1;
   log = TelemetryPing.getPayload().log;
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
              [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT3_ID]);
 
-  // Trigger non-user-disable of an experiment via the API
+  // Trigger disable of an experiment via the API.
 
   now = futureDate(now, MS_IN_ONE_DAY);
   defineNow(gPolicy, now);
 
-  yield experiments.disableExperiment(false);
+  yield experiments.disableExperiment(TLOG.TERMINATION.FROM_API);
   list = yield experiments.getExperiments();
   Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
 
   expectedLogLength += 1;
   log = TelemetryPing.getPayload().log;
   Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
   checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
              [TLOG.TERMINATION.FROM_API, EXPERIMENT3_ID]);
--- a/browser/installer/windows/nsis/stub.nsi
+++ b/browser/installer/windows/nsis/stub.nsi
@@ -225,16 +225,19 @@ Var ControlRightPX
 !insertmacro GetOptions
 !insertmacro StrFilter
 
 !include "locales.nsi"
 !include "branding.nsi"
 
 !include "defines.nsi"
 
+; Must be included after defines.nsi
+!include "locale-fonts.nsh"
+
 ; The OFFICIAL define is a workaround to support different urls for Release and
 ; Beta since they share the same branding when building with other branches that
 ; set the update channel to beta.
 !ifdef OFFICIAL
 !ifdef BETA_UPDATE_CHANNEL
 !undef URLStubDownload
 !define URLStubDownload "http://download.mozilla.org/?os=win&lang=${AB_CD}&product=firefox-beta-latest"
 !undef URLManualDownload
@@ -411,19 +414,37 @@ Function .onInit
   StrCpy $CheckboxSendPing "1"
 !ifdef MOZ_MAINTENANCE_SERVICE
   StrCpy $CheckboxInstallMaintSvc "1"
 !else
   StrCpy $CheckboxInstallMaintSvc "0"
 !endif
   StrCpy $WasOptionsButtonClicked "0"
 
-  CreateFont $FontBlurb "$(^Font)" "12" "500"
-  CreateFont $FontNormal "$(^Font)" "11" "500"
-  CreateFont $FontItalic "$(^Font)" "11" "500" /ITALIC
+  StrCpy $0 ""
+!ifdef FONT_FILE1
+  ${If} ${FileExists} "$FONTS\${FONT_FILE1}"
+    StrCpy $0 "${FONT_NAME1}"
+  ${EndIf}
+!endif
+
+!ifdef FONT_FILE2
+  ${If} $0 == ""
+  ${AndIf} ${FileExists} "$FONTS\${FONT_FILE2}"
+    StrCpy $0 "${FONT_NAME2}"
+  ${EndIf}
+!endif
+
+  ${If} $0 == ""
+    StrCpy $0 "$(^Font)"
+  ${EndIf}
+
+  CreateFont $FontBlurb "$0" "12" "500"
+  CreateFont $FontNormal "$0" "11" "500"
+  CreateFont $FontItalic "$0" "11" "500" /ITALIC
 
   InitPluginsDir
   File /oname=$PLUGINSDIR\bgintro.bmp "bgintro.bmp"
   File /oname=$PLUGINSDIR\appname.bmp "appname.bmp"
   File /oname=$PLUGINSDIR\clock.bmp "clock.bmp"
   File /oname=$PLUGINSDIR\particles.bmp "particles.bmp"
 !ifdef ${AB_CD}_rtl
   ; The horizontally flipped pencil looks better in RTL
--- a/browser/locales/en-US/chrome/browser/devtools/connection-screen.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/connection-screen.dtd
@@ -8,16 +8,17 @@
   - -->
 
 <!ENTITY title      "Connect">
 <!ENTITY header     "Connect to remote device">
 <!ENTITY host       "Host:">
 <!ENTITY port       "Port:">
 <!ENTITY connect    "Connect">
 <!ENTITY connecting "Connecting…">
+<!ENTITY availableAddons "Available remote add-ons:">
 <!ENTITY availableTabs "Available remote tabs:">
 <!ENTITY availableProcesses "Available remote processes:">
 <!ENTITY connectionError "Error:">
 <!ENTITY errorTimeout "Error: connection timeout.">
 <!ENTITY errorRefused "Error: connection refused.">
 <!ENTITY errorUnexpected "Unexpected error.">
 
 <!-- LOCALIZATION NOTE (remoteHelp, remoteDocumentation, remoteHelpSuffix):
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -136,46 +136,49 @@ function prompt(aContentWindow, aCallID,
     // The real callback will be set during the "showing" event. The
     // empty function here is so that PopupNotifications.show doesn't
     // reject the action.
     callback: function() {}
   };
 
   let secondaryActions = [
     {
-      label: stringBundle.getString("getUserMedia.always.label"),
-      accessKey: stringBundle.getString("getUserMedia.always.accesskey"),
-      callback: function () {
-        // don't save unless secure load!
-        mainAction.callback(aSecure);
-      }
-    },
-    {
       label: stringBundle.getString("getUserMedia.denyRequest.label"),
       accessKey: stringBundle.getString("getUserMedia.denyRequest.accesskey"),
       callback: function () {
         denyRequest(aCallID);
       }
     },
     {
       label: stringBundle.getString("getUserMedia.never.label"),
       accessKey: stringBundle.getString("getUserMedia.never.accesskey"),
       callback: function () {
         denyRequest(aCallID);
-	// Let someone save "Never" for http sites so that they can be stopped from
-	// bothering you with doorhangers
+        // Let someone save "Never" for http sites so that they can be stopped from
+        // bothering you with doorhangers.
         let perms = Services.perms;
         if (audioDevices.length)
           perms.add(uri, "microphone", perms.DENY_ACTION);
         if (videoDevices.length)
           perms.add(uri, "camera", perms.DENY_ACTION);
       }
     }
   ];
 
+  if (aSecure) {
+    // Don't show the 'Always' action if the connection isn't secure.
+    secondaryActions.unshift({
+      label: stringBundle.getString("getUserMedia.always.label"),
+      accessKey: stringBundle.getString("getUserMedia.always.accesskey"),
+      callback: function () {
+        mainAction.callback(true);
+      }
+    });
+  }
+
   let options = {
     eventCallback: function(aTopic, aNewBrowser) {
       if (aTopic == "swapping")
         return true;
 
       let chromeDoc = this.browser.ownerDocument;
 
       if (aTopic == "shown") {
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -30,16 +30,21 @@
   display: block;
   width: 64px;
   height: 64px;
   position: absolute;
   animation: moveX 3.05s linear 0s infinite alternate,
              moveY 3.4s linear 0s infinite alternate;
 }
 
+#PanelUI-popup #PanelUI-contents:-moz-locale-dir(rtl):empty::before {
+  animation: moveXRTL 3.05s linear 0s infinite alternate,
+             moveY 3.4s linear 0s infinite alternate;
+}
+
 #PanelUI-popup #PanelUI-contents:empty:hover::before {
   background-image: url(chrome://browser/skin/customizableui/whimsy.png);
 }
 
 @media (min-resolution: 2dppx) {
   #PanelUI-popup #PanelUI-contents:empty::before {
     background-image: url(chrome://browser/skin/customizableui/whimsy-bw@2x.png);
     background-size: 64px 64px;
@@ -48,16 +53,22 @@
     background-image: url(chrome://browser/skin/customizableui/whimsy@2x.png);
   }
 }
 
 @keyframes moveX {
   /* These values are adjusted for the padding on the panel. */
   from { margin-left: -15px; } to { margin-left: calc(100% - 49px); }
 }
+
+@keyframes moveXRTL {
+  /* These values are adjusted for the padding on the panel. */
+  from { margin-right: -15px; } to { margin-right: calc(100% - 49px); }
+}
+
 @keyframes moveY {
   /* These values are adjusted for the padding and height of the panel. */
   from { margin-top: -.5em; } to { margin-top: calc(64px - .5em); }
 }
 
 #PanelUI-button {
   background-image: linear-gradient(to bottom, hsla(0,0%,100%,0), hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, hsla(0,0%,100%,0)),
                     linear-gradient(to bottom, hsla(210,54%,20%,0), hsla(210,54%,20%,.3) 30%, hsla(210,54%,20%,.3) 70%, hsla(210,54%,20%,0)),
--- a/build/autoconf/android.m4
+++ b/build/autoconf/android.m4
@@ -288,29 +288,45 @@ case "$target" in
         fi
     fi
 
     android_tools="$android_sdk_root"/tools
     android_platform_tools="$android_sdk_root"/platform-tools
     if test ! -d "$android_platform_tools" ; then
         android_platform_tools="$android_sdk"/tools # SDK Tools < r8
     fi
-    # The build tools got moved around to different directories in
-    # SDK Tools r22.  Try to locate them.
+
+    dnl The build tools got moved around to different directories in SDK
+    dnl Tools r22. Try to locate them. This is awful, but, from
+    dnl http://stackoverflow.com/a/4495368, the following sorts versions
+    dnl of the form x.y.z.a.b from newest to oldest:
+    dnl sort -t. -k 1,1nr -k 2,2nr -k 3,3nr -k 4,4nr -k 5,5nr
+    dnl We want to favour the newer versions that start with 'android-';
+    dnl that's what the sed is about.
+    dnl We might iterate over directories that aren't build-tools at all;
+    dnl we use the presence of aapt as a marker.
+    AC_MSG_CHECKING([for android build-tools directory])
     android_build_tools=""
-    for suffix in android-4.4 android-4.3 android-4.2.2 19.0.3 19.0.2 19.0.0 18.1.0 18.0.1 18.0.0 17.0.0; do
-        tools_directory="$android_sdk_root/build-tools/$suffix"
-        if test -d "$tools_directory" ; then
+    for suffix in `ls "$android_sdk_root/build-tools" | sed -e "s,android-,999.," | sort -t. -k 1,1nr -k 2,2nr -k 3,3nr -k 4,4nr -k 5,5nr`; do
+        tools_directory=`echo "$android_sdk_root/build-tools/$suffix" | sed -e "s,999.,android-,"`
+        if test -d "$tools_directory" -a -f "$tools_directory/aapt"; then
             android_build_tools="$tools_directory"
             break
         fi
     done
     if test -z "$android_build_tools" ; then
         android_build_tools="$android_platform_tools" # SDK Tools < r22
     fi
+
+    if test -d "$android_build_tools" -a -f "$android_build_tools/aapt"; then
+        AC_MSG_RESULT([$android_build_tools])
+    else
+        AC_MSG_ERROR([not found. Please check your SDK for the subdirectory of build-tools. With the current configuration, it should be in $android_sdk_root/build_tools])
+    fi
+
     ANDROID_SDK="${android_sdk}"
     ANDROID_SDK_ROOT="${android_sdk_root}"
     if test -e "${ANDROID_SDK_ROOT}/extras/android/compatibility/v4/android-support-v4.jar" ; then
         ANDROID_COMPAT_LIB="${ANDROID_SDK_ROOT}/extras/android/compatibility/v4/android-support-v4.jar"
     else
         ANDROID_COMPAT_LIB="${ANDROID_SDK_ROOT}/extras/android/support/v4/android-support-v4.jar";
     fi
     ANDROID_TOOLS="${android_tools}"
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -283,16 +283,20 @@ DOMInterfaces = {
 'CSSValueList': {
     'nativeType': 'nsDOMCSSValueList'
 },
 
 'DataChannel': {
     'nativeType': 'nsDOMDataChannel',
 },
 
+'DataStoreCursor': {
+    'wrapperCache': False,
+},
+
 'DedicatedWorkerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
     'workers': True,
 },
 
 'DelayNode': {
     'resultNotAddRefed': [ 'delayTime' ],
 },
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
@@ -598,50 +598,49 @@ BluetoothHfpManager::ProcessDtmfCmd(char
 
 void
 BluetoothHfpManager::ProcessAtChld(bthf_chld_type_t aChld)
 {
   nsAutoCString message("CHLD=");
   message.AppendInt((int)aChld);
   BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER,
                       NS_ConvertUTF8toUTF16(message));
+  SendResponse(BTHF_AT_RESPONSE_OK);
 }
 
 void BluetoothHfpManager::ProcessDialCall(char *aNumber)
 {
   nsAutoCString message(aNumber);
+
+  // There are three cases based on aNumber,
+  // 1) Empty value:    Redial, BLDN
+  // 2) >xxx:           Memory dial, ATD>xxx
+  // 3) xxx:            Normal dial, ATDxxx
+  // We need to respond OK/Error for dial requests for every case listed above,
+  // 1) and 2):         Respond in either RespondToBLDNTask or
+  //                    HandleCallStateChanged()
+  // 3):                Respond here
   if (message.IsEmpty()) {
-    // Redial: BLDN
     mDialingRequestProcessed = false;
     BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER,
                         NS_LITERAL_STRING("BLDN"));
+    BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::POST_TASK_RESPOND_TO_BLDN);
+  } else if (message[0] == '>') {
+    mDialingRequestProcessed = false;
+    nsAutoCString newMsg("ATD");
+    newMsg += StringHead(message, message.Length() - 1);
+    BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER,
+                        NS_ConvertUTF8toUTF16(newMsg));
+    BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::POST_TASK_RESPOND_TO_BLDN);
   } else {
     nsAutoCString newMsg("ATD");
-
-    if (message[0] == '>') {
-      // Memory dial: ATD>xxx
-      mDialingRequestProcessed = false;
-      newMsg += message;
-    } else {
-      // Dial number: ATDxxx
-      int end = message.FindChar(';');
-      if (end < 0) {
-        BT_WARNING("Couldn't get the number to dial");
-        SendResponse(BTHF_AT_RESPONSE_OK);
-        return;
-      }
-      newMsg += nsDependentCSubstring(message, 0, end);
-    }
-
+    newMsg += StringHead(message, message.Length() - 1);
     BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::NOTIFY_DIALER,
                         NS_ConvertUTF8toUTF16(newMsg));
-  }
-
-  if (!mDialingRequestProcessed) {
-    BT_HF_DISPATCH_MAIN(MainThreadTaskCmd::POST_TASK_RESPOND_TO_BLDN);
+    SendResponse(BTHF_AT_RESPONSE_OK);
   }
 }
 
 void
 BluetoothHfpManager::ProcessAtCnum()
 {
   if (!mMsisdn.IsEmpty()) {
     nsAutoCString message("+CNUM: ,\"");
@@ -694,16 +693,18 @@ BluetoothHfpManager::ProcessAtClcc()
   }
 
   if (!mCdmaSecondCall.mNumber.IsEmpty()) {
     MOZ_ASSERT(mPhoneType == PhoneType::CDMA);
     MOZ_ASSERT(i == 2);
 
     SendCLCC(mCdmaSecondCall, 2);
   }
+
+  SendResponse(BTHF_AT_RESPONSE_OK);
 }
 
 void
 BluetoothHfpManager::ProcessUnknownAt(char *aAtString)
 {
   BT_LOGR("[%s]", aAtString);
 
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
@@ -1093,16 +1094,23 @@ BluetoothHfpManager::HandleCallStateChan
                                             const bool aIsConference,
                                             bool aSend)
 {
   if (!IsConnected()) {
     // Normal case. No need to print out warnings.
     return;
   }
 
+  // aCallIndex can be UINT32_MAX for the pending outgoing call state update.
+  // aCallIndex will be updated again after real call state changes. See Bug
+  // 990467.
+  if (aCallIndex == UINT32_MAX) {
+    return;
+  }
+
   while (aCallIndex >= mCurrentCallArray.Length()) {
     Call call;
     mCurrentCallArray.AppendElement(call);
   }
 
   mCurrentCallArray[aCallIndex].mState = aCallState;
   mCurrentCallArray[aCallIndex].mNumber = aNumber;
   mCurrentCallArray[aCallIndex].mDirection = (aIsOutgoing) ?
--- a/dom/bluetooth/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp
@@ -1149,18 +1149,18 @@ AppendDeviceName(BluetoothSignal& aSigna
   nsString devicePath = arr[0].value().get_nsString();
 
   nsRefPtr<AppendDeviceNameReplyHandler> handler =
     new AppendDeviceNameReplyHandler(nsCString(DBUS_DEVICE_IFACE),
                                      devicePath, aSignal);
 
   bool success = sDBusConnection->SendWithReply(
     AppendDeviceNameReplyHandler::Callback, handler.get(), 1000,
-    NS_ConvertUTF16toUTF8(devicePath).get(), DBUS_DEVICE_IFACE,
-    "GetProperties", DBUS_TYPE_INVALID);
+    BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(devicePath).get(),
+    DBUS_DEVICE_IFACE, "GetProperties", DBUS_TYPE_INVALID);
 
   NS_ENSURE_TRUE_VOID(success);
 
   unused << handler.forget(); // picked up by callback handler
 }
 
 static DBusHandlerResult
 AgentEventFilter(DBusConnection *conn, DBusMessage *msg, void *data)
@@ -1483,16 +1483,17 @@ private:
     }
 
     nsRefPtr<RegisterAgentReplyHandler> handler =
       new RegisterAgentReplyHandler(aAgentVTable);
     MOZ_ASSERT(!sAdapterPath.IsEmpty());
 
     bool success = sDBusConnection->SendWithReply(
       RegisterAgentReplyHandler::Callback, handler.get(), -1,
+      BLUEZ_DBUS_BASE_IFC,
       NS_ConvertUTF16toUTF8(sAdapterPath).get(),
       DBUS_ADAPTER_IFACE, "RegisterAgent",
       DBUS_TYPE_OBJECT_PATH, &agentPath,
       DBUS_TYPE_STRING, &capabilities,
       DBUS_TYPE_INVALID);
 
     NS_ENSURE_TRUE(success, false);
 
@@ -1522,16 +1523,17 @@ public:
 
     nsRefPtr<DBusReplyHandler> handler =
       new AddReservedServiceRecordsReplyHandler();
 
     const dbus_uint32_t* services = sServices;
 
     bool success = sDBusConnection->SendWithReply(
       DBusReplyHandler::Callback, handler.get(), -1,
+      BLUEZ_DBUS_BASE_IFC,
       NS_ConvertUTF16toUTF8(sAdapterPath).get(),
       DBUS_ADAPTER_IFACE, "AddReservedServiceRecords",
       DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32,
       &services, ArrayLength(sServices), DBUS_TYPE_INVALID);
 
     NS_ENSURE_TRUE_VOID(success);
 
     unused << handler.forget(); /* picked up by callback handler */
@@ -1934,17 +1936,17 @@ public:
     /* Normally we'll receive the signal 'AdapterAdded' with the adapter object
      * path from the DBus daemon during start up. So, there's no need to query
      * the object path of default adapter here. However, if we restart from a
      * crash, the default adapter might already be available, so we ask the daemon
      * explicitly here.
      */
     if (sAdapterPath.IsEmpty()) {
       bool success = sDBusConnection->SendWithReply(OnDefaultAdapterReply, nullptr,
-                                                    1000, "/",
+                                                    1000, BLUEZ_DBUS_BASE_IFC, "/",
                                                     DBUS_MANAGER_IFACE,
                                                     "DefaultAdapter",
                                                     DBUS_TYPE_INVALID);
       if (!success) {
         BT_WARNING("Failed to query default adapter!");
       }
     }
   }
@@ -2206,16 +2208,17 @@ protected:
 
     mAdapterPath = value.get_nsString();
 
     // Acquire another reference to this reply handler
     nsRefPtr<DefaultAdapterPathReplyHandler> handler = this;
 
     bool success = sDBusConnection->SendWithReply(
       DefaultAdapterPathReplyHandler::Callback, handler.get(), 1000,
+      BLUEZ_DBUS_BASE_IFC,
       NS_ConvertUTF16toUTF8(mAdapterPath).get(),
       DBUS_ADAPTER_IFACE, "GetProperties", DBUS_TYPE_INVALID);
 
     if (!success) {
       aReplyError = NS_LITERAL_STRING("SendWithReply failed");
       return false;
     }
 
@@ -2269,17 +2272,17 @@ public:
     MOZ_ASSERT(!NS_IsMainThread()); // I/O thread
     MOZ_ASSERT(sDBusConnection);
 
     nsRefPtr<DefaultAdapterPathReplyHandler> handler =
       new DefaultAdapterPathReplyHandler(mRunnable);
 
     bool success = sDBusConnection->SendWithReply(
       DefaultAdapterPathReplyHandler::Callback,
-      handler.get(), 1000,
+      handler.get(), 1000, BLUEZ_DBUS_BASE_IFC,
       "/", DBUS_MANAGER_IFACE, "DefaultAdapter",
       DBUS_TYPE_INVALID);
     NS_ENSURE_TRUE_VOID(success);
 
     unused << handler.forget(); // picked up by callback handler
   }
 
 private:
@@ -2337,16 +2340,17 @@ public:
   {
     MOZ_ASSERT(!NS_IsMainThread()); // I/O thread
     MOZ_ASSERT(sDBusConnection);
     MOZ_ASSERT(!sAdapterPath.IsEmpty());
 
     bool success = sDBusConnection->SendWithReply(
       OnSendDiscoveryMessageReply,
       static_cast<void*>(mRunnable.get()), -1,
+      BLUEZ_DBUS_BASE_IFC,
       NS_ConvertUTF16toUTF8(sAdapterPath).get(),
       DBUS_ADAPTER_IFACE, mMessageName.get(),
       DBUS_TYPE_INVALID);
     NS_ENSURE_TRUE_VOID(success);
 
     unused << mRunnable.forget(); // picked up by callback handler
   }
 
@@ -2415,18 +2419,18 @@ public:
 
   void Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(!NS_IsMainThread()); // I/O thread
     MOZ_ASSERT(sDBusConnection);
 
     bool success = sDBusConnection->SendWithReply(
       mCallback, static_cast<void*>(mServiceClass), -1,
-      mObjectPath.get(), mInterface.get(), mMessage.get(),
-      DBUS_TYPE_INVALID);
+      BLUEZ_DBUS_BASE_IFC, mObjectPath.get(), mInterface.get(),
+      mMessage.get(), DBUS_TYPE_INVALID);
     NS_ENSURE_TRUE_VOID(success);
 
     mServiceClass.forget();
   }
 
 private:
   DBusReplyCallback mCallback;
   nsAutoPtr<BluetoothServiceClass> mServiceClass;
@@ -2602,17 +2606,17 @@ protected:
     // cache object path for reply
     mObjectPath = GetObjectPathFromAddress(sAdapterPath,
       mDeviceAddresses[mProcessedDeviceAddresses]);
 
     nsRefPtr<BluetoothArrayOfDevicePropertiesReplyHandler> handler = this;
 
     bool success = sDBusConnection->SendWithReply(
       BluetoothArrayOfDevicePropertiesReplyHandler::Callback,
-      handler.get(), 1000,
+      handler.get(), 1000, BLUEZ_DBUS_BASE_IFC,
       NS_ConvertUTF16toUTF8(mObjectPath).get(),
       DBUS_DEVICE_IFACE, "GetProperties",
       DBUS_TYPE_INVALID);
 
     NS_ENSURE_TRUE(success, false);
 
     unused << handler.forget(); // picked up by callback handler
 
@@ -2730,17 +2734,17 @@ public:
 
   void Send(unsigned int aType, const void* aValue)
   {
     MOZ_ASSERT(!NS_IsMainThread()); // I/O thread
     MOZ_ASSERT(sDBusConnection);
     MOZ_ASSERT(!sAdapterPath.IsEmpty());
 
     DBusMessage* msg =
-      dbus_message_new_method_call("org.bluez",
+      dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
                                    NS_ConvertUTF16toUTF8(sAdapterPath).get(),
                                    sBluetoothDBusIfaces[mType],
                                    "SetProperty");
     if (!msg) {
       BT_WARNING("Could not allocate D-Bus message object!");
       return;
     }
 
@@ -2900,16 +2904,17 @@ public:
     const char *deviceAddress = mDeviceAddress.get();
     const char *deviceAgentPath = KEY_REMOTE_AGENT;
     const char *capabilities = B2G_AGENT_CAPABILITIES;
 
     // Then send CreatePairedDevice, it will register a temp device agent then
     // unregister it after pairing process is over
     bool success = sDBusConnection->SendWithReply(
       GetObjectPathCallback, static_cast<void*>(mRunnable), mTimeout,
+      BLUEZ_DBUS_BASE_IFC,
       NS_ConvertUTF16toUTF8(sAdapterPath).get(),
       DBUS_ADAPTER_IFACE,
       "CreatePairedDevice",
       DBUS_TYPE_STRING, &deviceAddress,
       DBUS_TYPE_OBJECT_PATH, &deviceAgentPath,
       DBUS_TYPE_STRING, &capabilities,
       DBUS_TYPE_INVALID);
     NS_ENSURE_TRUE_VOID(success);
@@ -2971,16 +2976,17 @@ public:
 
     nsCString deviceObjectPath =
       NS_ConvertUTF16toUTF8(GetObjectPathFromAddress(sAdapterPath,
                                                      mDeviceAddress));
     const char* cstrDeviceObjectPath = deviceObjectPath.get();
 
     bool success = sDBusConnection->SendWithReply(
       OnRemoveDeviceReply, static_cast<void*>(mRunnable.get()), -1,
+      BLUEZ_DBUS_BASE_IFC,
       NS_ConvertUTF16toUTF8(sAdapterPath).get(),
       DBUS_ADAPTER_IFACE, "RemoveDevice",
       DBUS_TYPE_OBJECT_PATH, &cstrDeviceObjectPath,
       DBUS_TYPE_INVALID);
     NS_ENSURE_TRUE_VOID(success);
 
     unused << mRunnable.forget(); // picked up by callback handler
   }
@@ -3493,16 +3499,17 @@ public:
       new OnGetServiceChannelReplyHandler(mDeviceAddress, mServiceUUID,
                                           mBluetoothProfileManager);
 
     nsCString serviceUUID = NS_ConvertUTF16toUTF8(mServiceUUID);
     const char* cstrServiceUUID = serviceUUID.get();
 
     bool success = sDBusConnection->SendWithReply(
       OnGetServiceChannelReplyHandler::Callback, handler, -1,
+      BLUEZ_DBUS_BASE_IFC,
       NS_ConvertUTF16toUTF8(objectPath).get(),
       DBUS_DEVICE_IFACE, "GetServiceAttributeValue",
       DBUS_TYPE_STRING, &cstrServiceUUID,
       DBUS_TYPE_UINT16, &sProtocolDescriptorList,
       DBUS_TYPE_INVALID);
     NS_ENSURE_TRUE_VOID(success);
 
     unused << handler.forget(); // picked up by callback handler
@@ -3573,16 +3580,17 @@ public:
 
     // I choose to use raw pointer here because this is going to be passed as an
     // argument into SendWithReply() at once.
     OnUpdateSdpRecordsRunnable* callbackRunnable =
       new OnUpdateSdpRecordsRunnable(objectPath, mBluetoothProfileManager);
 
     sDBusConnection->SendWithReply(DiscoverServicesCallback,
                                    (void*)callbackRunnable, -1,
+                                   BLUEZ_DBUS_BASE_IFC,
                                    NS_ConvertUTF16toUTF8(objectPath).get(),
                                    DBUS_DEVICE_IFACE,
                                    "DiscoverServices",
                                    DBUS_TYPE_STRING, &EmptyCString(),
                                    DBUS_TYPE_INVALID);
   }
 
 protected:
@@ -3791,16 +3799,17 @@ public:
     const char* artist = mArtist.get();
     const char* mediaNumber = tempMediaNumber.get();
     const char* totalMediaCount = tempTotalMediaCount.get();
     const char* duration = tempDuration.get();
     const char* genre = tempGenre.get();
 
     bool success = sDBusConnection->SendWithReply(
       GetVoidCallback, static_cast<void*>(mRunnable.get()), -1,
+      BLUEZ_DBUS_BASE_IFC,
       objectPath.get(),
       DBUS_CTL_IFACE, "UpdateMetaData",
       DBUS_TYPE_STRING, &title,
       DBUS_TYPE_STRING, &artist,
       DBUS_TYPE_STRING, &album,
       DBUS_TYPE_STRING, &mediaNumber,
       DBUS_TYPE_STRING, &totalMediaCount,
       DBUS_TYPE_STRING, &duration,
@@ -3927,16 +3936,17 @@ public:
 
     const nsCString objectPath = NS_ConvertUTF16toUTF8(
       GetObjectPathFromAddress(sAdapterPath, mDeviceAddress));
 
     uint32_t tempPlayStatus = mPlayStatus;
 
     bool success = sDBusConnection->SendWithReply(
       GetVoidCallback, static_cast<void*>(mRunnable.get()), -1,
+      BLUEZ_DBUS_BASE_IFC,
       objectPath.get(),
       DBUS_CTL_IFACE, "UpdatePlayStatus",
       DBUS_TYPE_UINT32, &mDuration,
       DBUS_TYPE_UINT32, &mPosition,
       DBUS_TYPE_UINT32, &tempPlayStatus,
       DBUS_TYPE_INVALID);
     NS_ENSURE_TRUE_VOID(success);
 
@@ -4050,16 +4060,17 @@ public:
 
     const nsCString objectPath = NS_ConvertUTF16toUTF8(
       GetObjectPathFromAddress(sAdapterPath, mDeviceAddress));
 
     uint32_t tempPlayStatus = mPlayStatus;
 
     bool success = sDBusConnection->SendWithReply(
       ControlCallback, nullptr, -1,
+      BLUEZ_DBUS_BASE_IFC,
       objectPath.get(),
       DBUS_CTL_IFACE, "UpdatePlayStatus",
       DBUS_TYPE_UINT32, &mDuration,
       DBUS_TYPE_UINT32, &mPosition,
       DBUS_TYPE_UINT32, &tempPlayStatus,
       DBUS_TYPE_INVALID);
     NS_ENSURE_TRUE_VOID(success);
   }
@@ -4116,16 +4127,17 @@ public:
 
     const nsCString objectPath = NS_ConvertUTF16toUTF8(
       GetObjectPathFromAddress(sAdapterPath, mDeviceAddress));
 
     uint16_t eventId = mEventId;
 
     bool success = sDBusConnection->SendWithReply(
       ControlCallback, nullptr, -1,
+      BLUEZ_DBUS_BASE_IFC,
       objectPath.get(),
       DBUS_CTL_IFACE, "UpdateNotification",
       DBUS_TYPE_UINT16, &eventId,
       DBUS_TYPE_UINT64, &mData,
       DBUS_TYPE_INVALID);
     NS_ENSURE_TRUE_VOID(success);
   }
 
new file mode 100644
--- /dev/null
+++ b/dom/datastore/DataStore.cpp
@@ -0,0 +1,188 @@
+/* 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/. */
+
+#include "mozilla/dom/DataStore.h"
+#include "mozilla/dom/DataStoreCursor.h"
+#include "mozilla/dom/DataStoreBinding.h"
+#include "mozilla/dom/DataStoreImplBinding.h"
+#include "mozilla/dom/Navigator.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Preferences.h"
+#include "AccessCheck.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ADDREF_INHERITED(DataStore, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(DataStore, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DataStore)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_1(DataStore, mStore)
+
+DataStore::DataStore(nsPIDOMWindow* aWindow)
+  : DOMEventTargetHelper(aWindow)
+{
+}
+
+already_AddRefed<DataStore>
+DataStore::Constructor(GlobalObject& aGlobal, ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!window) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<DataStore> store = new DataStore(window);
+  return store.forget();
+}
+
+JSObject*
+DataStore::WrapObject(JSContext* aCx)
+{
+  return DataStoreBinding::Wrap(aCx, this);
+}
+
+/*static*/ bool
+DataStore::EnabledForScope(JSContext* aCx, JS::Handle<JSObject*> aObj)
+{
+  // Only expose the interface when it is:
+  // 1. enabled by the preference and
+  // 2. accessed by the chrome codes in Gecko.
+  return (Navigator::HasDataStoreSupport(aCx, aObj) &&
+          nsContentUtils::ThreadsafeIsCallerChrome());
+}
+
+void
+DataStore::GetName(nsAString& aName, ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mStore);
+
+  nsAutoString name;
+  mStore->GetName(name, aRv);
+  aName.Assign(name);
+}
+
+void
+DataStore::GetOwner(nsAString& aOwner, ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mStore);
+
+  nsAutoString owner;
+  mStore->GetOwner(owner, aRv);
+  aOwner.Assign(owner);
+}
+
+bool
+DataStore::GetReadOnly(ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mStore);
+
+  return mStore->GetReadOnly(aRv);
+}
+
+already_AddRefed<Promise>
+DataStore::Get(const Sequence<OwningStringOrUnsignedLong>& aId,
+               ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mStore);
+
+  return mStore->Get(aId, aRv);
+}
+
+already_AddRefed<Promise>
+DataStore::Put(JSContext* aCx,
+               JS::Handle<JS::Value> aObj,
+               const StringOrUnsignedLong& aId,
+               const nsAString& aRevisionId,
+               ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mStore);
+
+  return mStore->Put(aObj, aId, aRevisionId, aRv);
+}
+
+already_AddRefed<Promise>
+DataStore::Add(JSContext* aCx,
+               JS::Handle<JS::Value> aObj,
+               const Optional<StringOrUnsignedLong>& aId,
+               const nsAString& aRevisionId,
+               ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mStore);
+
+  return mStore->Add(aObj, aId, aRevisionId, aRv);
+}
+
+already_AddRefed<Promise>
+DataStore::Remove(const StringOrUnsignedLong& aId,
+                  const nsAString& aRevisionId,
+                  ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mStore);
+
+  return mStore->Remove(aId, aRevisionId, aRv);
+}
+
+already_AddRefed<Promise>
+DataStore::Clear(const nsAString& aRevisionId, ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mStore);
+
+  return mStore->Clear(aRevisionId, aRv);
+}
+
+void
+DataStore::GetRevisionId(nsAString& aRevisionId, ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mStore);
+
+  nsAutoString revisionId;
+  mStore->GetRevisionId(revisionId, aRv);
+  aRevisionId.Assign(revisionId);
+}
+
+already_AddRefed<Promise>
+DataStore::GetLength(ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mStore);
+
+  return mStore->GetLength(aRv);
+}
+
+already_AddRefed<DataStoreCursor>
+DataStore::Sync(const nsAString& aRevisionId, ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mStore);
+
+  return mStore->Sync(aRevisionId, aRv);
+}
+
+void
+DataStore::SetDataStoreImpl(DataStoreImpl& aStore, ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mStore);
+
+  mStore = &aStore;
+  mStore->SetEventTarget(*this, aRv);
+}
+
+} //namespace dom
+} //namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/datastore/DataStore.h
@@ -0,0 +1,95 @@
+/* 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/. */
+
+#ifndef mozilla_dom_DataStore_h
+#define mozilla_dom_DataStore_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class Promise;
+class DataStoreCursor;
+class DataStoreImpl;
+class StringOrUnsignedLong;
+class OwningStringOrUnsignedLong;
+
+class DataStore MOZ_FINAL : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DataStore,
+                                           DOMEventTargetHelper)
+
+  explicit DataStore(nsPIDOMWindow* aWindow);
+
+  // WebIDL (internal functions)
+
+  static already_AddRefed<DataStore> Constructor(GlobalObject& aGlobal,
+                                                 ErrorResult& aRv);
+
+  virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
+
+  static bool EnabledForScope(JSContext* aCx, JS::Handle<JSObject*> aObj);
+
+  // WebIDL (public APIs)
+
+  void GetName(nsAString& aName, ErrorResult& aRv);
+
+  void GetOwner(nsAString& aOwner, ErrorResult& aRv);
+
+  bool GetReadOnly(ErrorResult& aRv);
+
+  already_AddRefed<Promise> Get(const Sequence<OwningStringOrUnsignedLong>& aId,
+                                ErrorResult& aRv);
+
+  already_AddRefed<Promise> Put(JSContext* aCx,
+                                JS::Handle<JS::Value> aObj,
+                                const StringOrUnsignedLong& aId,
+                                const nsAString& aRevisionId,
+                                ErrorResult& aRv);
+
+  already_AddRefed<Promise> Add(JSContext* aCx,
+                                JS::Handle<JS::Value> aObj,
+                                const Optional<StringOrUnsignedLong>& aId,
+                                const nsAString& aRevisionId,
+                                ErrorResult& aRv);
+
+  already_AddRefed<Promise> Remove(const StringOrUnsignedLong& aId,
+                                   const nsAString& aRevisionId,
+                                   ErrorResult& aRv);
+
+  already_AddRefed<Promise> Clear(const nsAString& aRevisionId,
+                                  ErrorResult& aRv);
+
+  void GetRevisionId(nsAString& aRevisionId, ErrorResult& aRv);
+
+  already_AddRefed<Promise> GetLength(ErrorResult& aRv);
+
+  already_AddRefed<DataStoreCursor> Sync(const nsAString& aRevisionId,
+                                         ErrorResult& aRv);
+
+  IMPL_EVENT_HANDLER(change)
+
+  // This internal function (ChromeOnly) is aimed to make the DataStore keep a
+  // reference to the DataStoreImpl which really implements the API's logic in
+  // JS. We also need to let the DataStoreImpl implementation keep the event
+  // target of DataStore, so that it can know where to fire the events.
+  void SetDataStoreImpl(DataStoreImpl& aStore, ErrorResult& aRv);
+
+protected:
+  virtual ~DataStore() {}
+
+private:
+  nsRefPtr<DataStoreImpl> mStore;
+};
+
+} //namespace dom
+} //namespace mozilla
+
+#endif
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/datastore/DataStoreCursor.cpp
@@ -0,0 +1,71 @@
+/* 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/. */
+
+#include "mozilla/dom/DataStore.h"
+#include "mozilla/dom/DataStoreCursor.h"
+#include "mozilla/dom/DataStoreBinding.h"
+#include "mozilla/dom/DataStoreImplBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/ErrorResult.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DataStoreCursor, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DataStoreCursor, Release)
+
+NS_IMPL_CYCLE_COLLECTION_1(DataStoreCursor, mCursor)
+
+already_AddRefed<DataStoreCursor>
+DataStoreCursor::Constructor(GlobalObject& aGlobal, ErrorResult& aRv)
+{
+  nsRefPtr<DataStoreCursor> cursor = new DataStoreCursor();
+  return cursor.forget();
+}
+
+JSObject*
+DataStoreCursor::WrapObject(JSContext* aCx)
+{
+  return DataStoreCursorBinding::Wrap(aCx, this);
+}
+
+already_AddRefed<DataStore>
+DataStoreCursor::GetStore(ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mCursor);
+
+  return mCursor->GetStore(aRv);
+}
+
+already_AddRefed<Promise>
+DataStoreCursor::Next(ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mCursor);
+
+  return mCursor->Next(aRv);
+}
+
+void
+DataStoreCursor::Close(ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mCursor);
+
+  mCursor->Close(aRv);
+}
+
+void
+DataStoreCursor::SetDataStoreCursorImpl(DataStoreCursorImpl& aCursor)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mCursor);
+
+  mCursor = &aCursor;
+}
+
+} //namespace dom
+} //namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/datastore/DataStoreCursor.h
@@ -0,0 +1,61 @@
+/* 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/. */
+
+#ifndef mozilla_dom_DataStoreCursor_h
+#define mozilla_dom_DataStoreCursor_h
+
+#include "nsAutoPtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+
+class nsPIDOMWindow;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class Promise;
+class DataStore;
+class GlobalObject;
+class DataStoreCursorImpl;
+
+class DataStoreCursor MOZ_FINAL
+{
+public:
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DataStoreCursor)
+  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(DataStoreCursor)
+
+  // WebIDL (internal functions)
+
+  static already_AddRefed<DataStoreCursor> Constructor(GlobalObject& aGlobal,
+                                                       ErrorResult& aRv);
+
+  JSObject* WrapObject(JSContext *aCx);
+
+  // WebIDL (public APIs)
+
+  already_AddRefed<DataStore> GetStore(ErrorResult& aRv);
+
+  already_AddRefed<Promise> Next(ErrorResult& aRv);
+
+  void Close(ErrorResult& aRv);
+
+  // This internal function (ChromeOnly) is aimed to make the DataStoreCursor
+  // keep a reference to the DataStoreCursorImpl which really implements the
+  // API's logic in JS.
+  void SetDataStoreCursorImpl(DataStoreCursorImpl& aCursor);
+
+protected:
+  virtual ~DataStoreCursor() {}
+
+private:
+  nsRefPtr<DataStoreCursorImpl> mCursor;
+};
+
+} //namespace dom
+} //namespace mozilla
+
+#endif
\ No newline at end of file
rename from dom/datastore/DataStoreCursor.jsm
rename to dom/datastore/DataStoreCursorImpl.jsm
--- a/dom/datastore/DataStoreCursor.jsm
+++ b/dom/datastore/DataStoreCursorImpl.jsm
@@ -72,23 +72,24 @@ Cu.import('resource://gre/modules/XPCOMU
 
 /* Helper functions */
 function createDOMError(aWindow, aEvent) {
   return new aWindow.DOMError(aEvent.target.error.name);
 }
 
 /* DataStoreCursor object */
 this.DataStoreCursor = function(aWindow, aDataStore, aRevisionId) {
+  debug("DataStoreCursor created");
   this.init(aWindow, aDataStore, aRevisionId);
 }
 
 this.DataStoreCursor.prototype = {
   classDescription: 'DataStoreCursor XPCOM Component',
   classID: Components.ID('{b6d14349-1eab-46b8-8513-584a7328a26b}'),
-  contractID: '@mozilla.org/dom/datastore-cursor;1',
+  contractID: '@mozilla.org/dom/datastore-cursor-impl;1',
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports]),
 
   _window: null,
   _dataStore: null,
   _revisionId: null,
   _revision: null,
   _revisionsList: null,
   _objectId: 0,
rename from dom/datastore/DataStore.jsm
rename to dom/datastore/DataStoreImpl.jsm
--- a/dom/datastore/DataStore.jsm
+++ b/dom/datastore/DataStoreImpl.jsm
@@ -18,17 +18,17 @@ const REVISION_ADDED = "added";
 const REVISION_UPDATED = "updated";
 const REVISION_REMOVED = "removed";
 const REVISION_VOID = "void";
 
 // This value has to be tuned a bit. Currently it's just a guess
 // and yet we don't know if it's too low or too high.
 const MAX_REQUESTS = 25;
 
-Cu.import("resource://gre/modules/DataStoreCursor.jsm");
+Cu.import("resource://gre/modules/DataStoreCursorImpl.jsm");
 Cu.import("resource://gre/modules/DataStoreDB.jsm");
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.importGlobalProperties(["indexedDB"]);
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
@@ -62,30 +62,31 @@ function validateId(aId) {
 this.DataStore = function(aWindow, aName, aOwner, aReadOnly) {
   debug("DataStore created");
   this.init(aWindow, aName, aOwner, aReadOnly);
 }
 
 this.DataStore.prototype = {
   classDescription: "DataStore XPCOM Component",
   classID: Components.ID("{db5c9602-030f-4bff-a3de-881a8de370f2}"),
-  contractID: "@mozilla.org/dom/datastore;1",
+  contractID: "@mozilla.org/dom/datastore-impl;1",
   QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
                                          Components.interfaces.nsIObserver]),
 
   callbacks: [],
 
   _window: null,
   _name: null,
   _owner: null,
   _readOnly: null,
   _revisionId: null,
   _exposedObject: null,
   _cursor: null,
   _shuttingdown: false,
+  _eventTarget: null,
 
   init: function(aWindow, aName, aOwner, aReadOnly) {
     debug("DataStore init");
 
     this._window = aWindow;
     this._name = aName;
     this._owner = aOwner;
     this._readOnly = aReadOnly;
@@ -111,16 +112,20 @@ this.DataStore.prototype = {
 
       cpmm.removeMessageListener("DataStore:Changed:Return:OK", this);
       cpmm.sendAsyncMessage("DataStore:UnregisterForMessages");
       this._shuttingdown = true;
       this._db.close();
     }
   },
 
+  setEventTarget: function(aEventTarget) {
+    this._eventTarget = aEventTarget;
+  },
+
   newDBPromise: function(aTxnType, aFunction) {
     let self = this;
     return new this._window.Promise(function(aResolve, aReject) {
       debug("DBPromise started");
       self._db.txn(
         aTxnType,
         function(aTxn, aStore, aRevisionStore) {
           debug("DBPromise success");
@@ -360,17 +365,17 @@ this.DataStore.prototype = {
 
         // If we have an active cursor we don't emit events.
         if (self._cursor) {
           return;
         }
 
         let event = new self._window.DataStoreChangeEvent('change',
                                                           aMessage.data.message);
-        self.__DOM_IMPL__.dispatchEvent(event);
+        self._eventTarget.dispatchEvent(event);
       }
     );
   },
 
   get exposedObject() {
     debug("get exposedObject");
     return this._exposedObject;
   },
@@ -514,24 +519,20 @@ this.DataStore.prototype = {
     // Promise<int>
     return this.newDBPromise("readonly",
       function(aResolve, aReject, aTxn, aStore, aRevisionStore) {
         self.getLengthInternal(aResolve, aStore);
       }
     );
   },
 
-  set onchange(aCallback) {
-    debug("Set OnChange");
-    this.__DOM_IMPL__.setEventHandler("onchange", aCallback);
-  },
-
-  get onchange() {
-    debug("Get OnChange");
-    return this.__DOM_IMPL__.getEventHandler("onchange");
-  },
-
   sync: function(aRevisionId) {
     debug("Sync");
     this._cursor = new DataStoreCursor(this._window, this, aRevisionId);
-    return this._window.DataStoreCursor._create(this._window, this._cursor);
+
+    let cursorImpl = this._window.DataStoreCursorImpl.
+                                  _create(this._window, this._cursor);
+
+    let exposedCursor = new this._window.DataStoreCursor();
+    exposedCursor.setDataStoreCursorImpl(cursorImpl);
+    return exposedCursor;
   }
 };
--- a/dom/datastore/DataStoreService.js
+++ b/dom/datastore/DataStoreService.js
@@ -11,17 +11,17 @@
 function debug(s) {
   //dump('DEBUG DataStoreService: ' + s + '\n');
 }
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
-Cu.import('resource://gre/modules/DataStore.jsm');
+Cu.import('resource://gre/modules/DataStoreImpl.jsm');
 Cu.import("resource://gre/modules/DataStoreDB.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
@@ -347,20 +347,25 @@ DataStoreService.prototype = {
     if (!callbackPending) {
       aResolve(results);
       return;
     }
 
     for (let i = 0; i < aStores.length; ++i) {
       let obj = new DataStore(aWindow, aStores[i].name,
                               aStores[i].owner, aStores[i].readOnly);
-      let exposedObj = aWindow.DataStore._create(aWindow, obj);
-      obj.exposedObject = exposedObj;
+
+      let storeImpl = aWindow.DataStoreImpl._create(aWindow, obj);
 
-      results.push(exposedObj);
+      let exposedStore = new aWindow.DataStore();
+      exposedStore.setDataStoreImpl(storeImpl);
+
+      obj.exposedObject = exposedStore;
+
+      results.push(exposedStore);
 
       obj.retrieveRevisionId(
         function() {
           --callbackPending;
           if (!callbackPending) {
             aResolve(results);
           }
         }
--- a/dom/datastore/moz.build
+++ b/dom/datastore/moz.build
@@ -5,22 +5,38 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPIDL_SOURCES += [
     'nsIDataStoreService.idl',
 ]
 
 XPIDL_MODULE = 'dom_datastore'
 
+EXPORTS.mozilla.dom += [
+    'DataStore.h',
+    'DataStoreCursor.h',
+]
+
+SOURCES += [
+    'DataStore.cpp',
+    'DataStoreCursor.cpp',
+]
+
+LOCAL_INCLUDES += [
+    '/js/xpconnect/wrappers',
+]
+
 EXTRA_COMPONENTS += [
     'DataStore.manifest',
     'DataStoreService.js',
 ]
 
 EXTRA_JS_MODULES += [
-    'DataStore.jsm',
     'DataStoreChangeNotifier.jsm',
-    'DataStoreCursor.jsm',
+    'DataStoreCursorImpl.jsm',
     'DataStoreDB.jsm',
+    'DataStoreImpl.jsm',
     'DataStoreServiceInternal.jsm',
 ]
 
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+
+FINAL_LIBRARY = 'xul'
--- a/dom/messages/moz.build
+++ b/dom/messages/moz.build
@@ -1,14 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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_DIRS += ['test']
+
 PARALLEL_DIRS += ['interfaces']
 
 EXTRA_COMPONENTS += [
     'SystemMessageInternal.js',
     'SystemMessageManager.js',
     'SystemMessageManager.manifest',
 ]
 
new file mode 100644
--- /dev/null
+++ b/dom/messages/test/mochitest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+skip-if = e10s
+
+[test_bug_993732.html]
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
new file mode 100644
--- /dev/null
+++ b/dom/messages/test/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+MOCHITEST_MANIFESTS += ['mochitest.ini']
+
new file mode 100644
--- /dev/null
+++ b/dom/messages/test/test_bug_993732.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test Bug 993732</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+  <script type="application/javascript">
+
+  "use strict";
+
+  // The syndrome of Bug 993732 is that the running app (either foreground or background)
+  // is not able to receive system messages. Even worse, the app will be killed when the
+  // listening system message is broadcast. So this test case uses the alarm message
+  // to test if a running app can receive the system message.
+
+  function testAlarm(aMillisecondsFromNow) {
+    var at = new Date();
+    at.setTime(at.getTime() + aMillisecondsFromNow);
+
+    navigator.mozSetMessageHandler('alarm', function(message) {
+      ok(true, "We got alarm message!");
+      SimpleTest.finish();
+    });
+
+    var domRequest;
+    try {
+      domRequest = navigator.mozAlarms.add(at, "honorTimezone", {});
+    } catch (e) {
+      ok(false,
+         "Unexpected exception while adding alarm " + aMillisecondsFromNow + " ms from now.");
+      SimpleTest.finish();
+    }
+    domRequest.onsuccess = function(e) {
+      // Waiting for alarm message.
+    };
+    domRequest.onerror = function(e) {
+      ok(false, "Unable to add alarm for tomorrow`.");
+      SimpleTest.finish();
+    };
+  }
+
+  function startTests() {
+
+    SpecialPowers.pushPrefEnv({"set": [["dom.mozAlarms.enabled", true]]}, function() {
+      // Currently applicable only on FxOS
+      if (navigator.userAgent.indexOf("Mobile") != -1 &&
+          navigator.appVersion.indexOf("Android") == -1)
+      {
+        testAlarm(10000);
+      } else {
+        ok(true, "mozAlarms on Firefox OS only.");
+        SimpleTest.finish();
+      }
+    });
+  }
+
+  SimpleTest.expectAssertions(0, 9);
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPermissions([{'type': 'alarms', 'allow': true, 'context': document}], startTests);
+
+  </script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/DataStore.webidl
+++ b/dom/webidl/DataStore.webidl
@@ -1,68 +1,101 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 typedef (DOMString or unsigned long) DataStoreKey;
 
+// TODO Bug 957086 - The constructor and the setDataStoreImpl(...) will be
+//                   removed once the DataStore API is fully rewritten in C++,
+//                   which currently plays a role of C++ proxy directing to the
+//                   JS codes implemented by the DataStoreImpl WebIDL.
+
 [Func="Navigator::HasDataStoreSupport",
- JSImplementation="@mozilla.org/dom/datastore;1"]
+ ChromeConstructor]
 interface DataStore : EventTarget {
   // Returns the label of the DataSource.
+  [GetterThrows]
   readonly attribute DOMString name;
 
   // Returns the origin of the DataSource (e.g., 'facebook.com').
   // This value is the manifest URL of the owner app.
+  [GetterThrows]
   readonly attribute DOMString owner;
 
   // is readOnly a F(current_app, datastore) function? yes
+  [GetterThrows]
   readonly attribute boolean readOnly;
 
   // Promise<any>
+  [Throws]
   Promise get(DataStoreKey... id);
 
   // Promise<void>
+  [Throws]
   Promise put(any obj, DataStoreKey id, optional DOMString revisionId = "");
 
   // Promise<DataStoreKey>
+  [Throws]
   Promise add(any obj, optional DataStoreKey id,
               optional DOMString revisionId = "");
 
   // Promise<boolean>
+  [Throws]
   Promise remove(DataStoreKey id, optional DOMString revisionId = "");
 
   // Promise<void>
+  [Throws]
   Promise clear(optional DOMString revisionId = "");
 
+  [GetterThrows]
   readonly attribute DOMString revisionId;
 
   attribute EventHandler onchange;
 
   // Promise<unsigned long>
+  [Throws]
   Promise getLength();
 
+  [NewObject, Throws]
   DataStoreCursor sync(optional DOMString revisionId = "");
 };
 
+partial interface DataStore {
+  [ChromeOnly, Throws]
+  void setDataStoreImpl(DataStoreImpl store);
+};
+
+// TODO Bug 957086 - The constructor and the setDataStoreCursorImpl(...) will be
+//                   removed once the DataStore API is fully rewritten in C++,
+//                   which currently plays a role of C++ proxy directing to the
+//                   JS codes implemented by the DataStoreCursorImpl WebIDL.
+
 [Pref="dom.datastore.enabled",
- JSImplementation="@mozilla.org/dom/datastore-cursor;1"]
+ ChromeConstructor]
 interface DataStoreCursor {
-
   // the DataStore
+  [GetterThrows]
   readonly attribute DataStore store;
 
   // Promise<DataStoreTask>
+  [Throws]
   Promise next();
 
+  [Throws]
   void close();
 };
 
+partial interface DataStoreCursor {
+  [ChromeOnly]
+  void setDataStoreCursorImpl(DataStoreCursorImpl cursor);
+};
+
 enum DataStoreOperation {
   "add",
   "update",
   "remove",
   "clear",
   "done"
 };
 
new file mode 100644
--- /dev/null
+++ b/dom/webidl/DataStoreImpl.webidl
@@ -0,0 +1,68 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// TODO Bug 957086 - The DataStoreImpl WebIDL will be removed once the
+//                   DataStore API is fully rewritten in C++ (i.e. should be
+//                   directly implemented by the DataStore WebIDL).
+
+[HeaderFile="mozilla/dom/DataStore.h",
+ Func="mozilla::dom::DataStore::EnabledForScope",
+ JSImplementation="@mozilla.org/dom/datastore-impl;1"]
+interface DataStoreImpl {
+  void setEventTarget(EventTarget eventTarget);
+
+  // Returns the label of the DataSource.
+  readonly attribute DOMString name;
+
+  // Returns the origin of the DataSource (e.g., 'facebook.com').
+  // This value is the manifest URL of the owner app.
+  readonly attribute DOMString owner;
+
+  // is readOnly a F(current_app, datastore) function? yes
+  readonly attribute boolean readOnly;
+
+  // Promise<any>
+  Promise get(DataStoreKey... id);
+
+  // Promise<void>
+  Promise put(any obj, DataStoreKey id, optional DOMString revisionId = "");
+
+  // Promise<DataStoreKey>
+  Promise add(any obj, optional DataStoreKey id,
+              optional DOMString revisionId = "");
+
+  // Promise<boolean>
+  Promise remove(DataStoreKey id, optional DOMString revisionId = "");
+
+  // Promise<void>
+  Promise clear(optional DOMString revisionId = "");
+
+  readonly attribute DOMString revisionId;
+
+  // Promise<unsigned long>
+  Promise getLength();
+
+  [NewObject]
+  DataStoreCursor sync(optional DOMString revisionId = "");
+};
+
+
+// TODO Bug 957086 - The DataStoreCursorImpl WebIDL will be removed once the
+//                   DataStore API is fully rewritten in C++ (i.e. should be
+//                   directly implemented by the DataStoreCursor WebIDL).
+
+[HeaderFile="mozilla/dom/DataStore.h",
+ Func="mozilla::dom::DataStore::EnabledForScope",
+ JSImplementation="@mozilla.org/dom/datastore-cursor-impl;1"]
+interface DataStoreCursorImpl {
+  // the DataStore
+  readonly attribute DataStore store;
+
+  // Promise<DataStoreTask>
+  Promise next();
+
+  void close();
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -63,16 +63,17 @@ WEBIDL_FILES = [
     'CSS.webidl',
     'CSSPrimitiveValue.webidl',
     'CSSStyleDeclaration.webidl',
     'CSSStyleSheet.webidl',
     'CSSValue.webidl',
     'CSSValueList.webidl',
     'DataContainerEvent.webidl',
     'DataStore.webidl',
+    'DataStoreImpl.webidl',
     'DataTransfer.webidl',
     'DedicatedWorkerGlobalScope.webidl',
     'DelayNode.webidl',
     'DesktopNotification.webidl',
     'DeviceMotionEvent.webidl',
     'DeviceStorage.webidl',
     'Directory.webidl',
     'Document.webidl',
--- a/ipc/dbus/RawDBusConnection.cpp
+++ b/ipc/dbus/RawDBusConnection.cpp
@@ -15,19 +15,16 @@
 
 #if defined(MOZ_WIDGET_GONK)
 #include <android/log.h>
 #define CHROMIUM_LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Gonk", args);
 #else
 #define CHROMIUM_LOG(args...)  printf(args);
 #endif
 
-/* TODO: Remove BlueZ constant */
-#define BLUEZ_DBUS_BASE_IFC "org.bluez"
-
 namespace mozilla {
 namespace ipc {
 
 //
 // DBusWatcher
 //
 
 class DBusWatcher : public MessageLoopForIO::Watcher
@@ -329,45 +326,48 @@ bool RawDBusConnection::SendWithReply(DB
   dbus_message_unref(aMessage);
 
   return true;
 }
 
 bool RawDBusConnection::SendWithReply(DBusReplyCallback aCallback,
                                       void* aData,
                                       int aTimeout,
+                                      const char* aDestination,
                                       const char* aPath,
                                       const char* aIntf,
                                       const char* aFunc,
                                       int aFirstArgType,
                                       ...)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   va_list args;
 
   va_start(args, aFirstArgType);
-  DBusMessage* msg = BuildDBusMessage(aPath, aIntf, aFunc,
+  DBusMessage* msg = BuildDBusMessage(aDestination, aPath, aIntf, aFunc,
                                       aFirstArgType, args);
   va_end(args);
 
   if (!msg) {
     return false;
   }
 
   return SendWithReply(aCallback, aData, aTimeout, msg);
 }
 
-DBusMessage* RawDBusConnection::BuildDBusMessage(const char* aPath,
+DBusMessage* RawDBusConnection::BuildDBusMessage(const char* aDestination,
+                                                 const char* aPath,
                                                  const char* aIntf,
                                                  const char* aFunc,
                                                  int aFirstArgType,
                                                  va_list aArgs)
 {
-  DBusMessage* msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
-                                                  aPath, aIntf, aFunc);
+  DBusMessage* msg = dbus_message_new_method_call(aDestination,
+                                                  aPath, aIntf,
+                                                  aFunc);
   if (!msg) {
     CHROMIUM_LOG("Could not allocate D-Bus message object!");
     return nullptr;
   }
 
   /* append arguments */
   if (!dbus_message_append_args_valist(msg, aFirstArgType, aArgs)) {
     CHROMIUM_LOG("Could not append argument to method call!");
--- a/ipc/dbus/RawDBusConnection.h
+++ b/ipc/dbus/RawDBusConnection.h
@@ -39,21 +39,24 @@ public:
   }
 
   bool Send(DBusMessage* aMessage);
 
   bool SendWithReply(DBusReplyCallback aCallback, void* aData,
                      int aTimeout, DBusMessage* aMessage);
 
   bool SendWithReply(DBusReplyCallback aCallback, void* aData,
-                     int aTimeout, const char* aPath, const char* aIntf,
+                     int aTimeout,
+                     const char* aDestination,
+                     const char* aPath, const char* aIntf,
                      const char *aFunc, int aFirstArgType, ...);
 
 protected:
-  DBusMessage* BuildDBusMessage(const char* aPath, const char* aIntf,
+  DBusMessage* BuildDBusMessage(const char* aDestination,
+                                const char* aPath, const char* aIntf,
                                 const char* aFunc, int aFirstArgType,
                                 va_list args);
 
   Scoped<ScopedDBusConnectionPtrTraits> mConnection;
 
 private:
   static bool sDBusIsInit;
 };
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -149,17 +149,16 @@ public abstract class GeckoApp
     public static final String ACTION_LAUNCH_SETTINGS      = "org.mozilla.gecko.SETTINGS";
     public static final String ACTION_LOAD                 = "org.mozilla.gecko.LOAD";
     public static final String ACTION_INIT_PW              = "org.mozilla.gecko.INIT_PW";
     public static final String ACTION_WEBAPP_PREFIX        = "org.mozilla.gecko.WEBAPP";
 
     public static final String EXTRA_STATE_BUNDLE          = "stateBundle";
 
     public static final String PREFS_ALLOW_STATE_BUNDLE    = "allowStateBundle";
-    public static final String PREFS_CRASHED               = "crashed";
     public static final String PREFS_OOM_EXCEPTION         = "OOMException";
     public static final String PREFS_VERSION_CODE          = "versionCode";
     public static final String PREFS_WAS_STOPPED           = "wasStopped";
     public static final String PREFS_CLEANUP_TEMP_FILES    = "cleanupTempFiles";
 
     public static final String SAVED_STATE_IN_BACKGROUND   = "inBackground";
     public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
 
@@ -1767,19 +1766,22 @@ public abstract class GeckoApp
                 public void run() {
                     prefs.edit()
                          .putInt(PREFS_VERSION_CODE, versionCode)
                          .commit();
                 }
             });
 
             shouldRestore = true;
-        } else if (savedInstanceState != null || getSessionRestorePreference().equals("always") || getRestartFromIntent()) {
+        } else if (savedInstanceState != null ||
+                   getSessionRestorePreference().equals("always") ||
+                   getRestartFromIntent() ||
+                   prefs.getBoolean(GeckoApp.PREFS_WAS_STOPPED, false)) {
             // We're coming back from a background kill by the OS, the user
-            // has chosen to always restore, or we just restarted.
+            // has chosen to always restore, we restarted, or we crashed.
             shouldRestore = true;
         }
 
         return shouldRestore;
     }
 
     private String getSessionRestorePreference() {
         return getSharedPreferences().getString(GeckoPreferences.PREFS_RESTORE_SESSION, "quit");
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -685,17 +685,16 @@ sync_java_files = [
     'sync/net/WBORequestDelegate.java',
     'sync/NoCollectionKeysSetException.java',
     'sync/NodeAuthenticationException.java',
     'sync/NonArrayJSONException.java',
     'sync/NonObjectJSONException.java',
     'sync/NullClusterURLException.java',
     'sync/PersistedMetaGlobal.java',
     'sync/PrefsBackoffHandler.java',
-    'sync/PrefsSource.java',
     'sync/receivers/SyncAccountDeletedReceiver.java',
     'sync/receivers/SyncAccountDeletedService.java',
     'sync/receivers/UpgradeReceiver.java',
     'sync/repositories/android/AndroidBrowserBookmarksDataAccessor.java',
     'sync/repositories/android/AndroidBrowserBookmarksRepository.java',
     'sync/repositories/android/AndroidBrowserBookmarksRepositorySession.java',
     'sync/repositories/android/AndroidBrowserHistoryDataAccessor.java',
     'sync/repositories/android/AndroidBrowserHistoryDataExtender.java',
--- a/mobile/android/base/background/fxa/FxAccountClient10.java
+++ b/mobile/android/base/background/fxa/FxAccountClient10.java
@@ -171,42 +171,45 @@ public class FxAccountClient10 {
    */
   protected abstract class ResourceDelegate<T> extends BaseResourceDelegate {
     protected abstract void handleSuccess(final int status, HttpResponse response, final ExtendedJSONObject body);
 
     protected final RequestDelegate<T> delegate;
 
     protected final byte[] tokenId;
     protected final byte[] reqHMACKey;
-    protected final boolean payload;
     protected final SkewHandler skewHandler;
 
     /**
      * Create a delegate for an un-authenticated resource.
      */
     public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate) {
-      this(resource, delegate, null, null, false);
+      this(resource, delegate, null, null);
     }
 
     /**
      * Create a delegate for a Hawk-authenticated resource.
+     * <p>
+     * Every Hawk request that encloses an entity (PATCH, POST, and PUT) will
+     * include the payload verification hash.
      */
-    public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, final byte[] tokenId, final byte[] reqHMACKey, final boolean authenticatePayload) {
+    public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, final byte[] tokenId, final byte[] reqHMACKey) {
       super(resource);
       this.delegate = delegate;
       this.reqHMACKey = reqHMACKey;
       this.tokenId = tokenId;
-      this.payload = authenticatePayload;
       this.skewHandler = SkewHandler.getSkewHandlerForResource(resource);
     }
 
     @Override
     public AuthHeaderProvider getAuthHeaderProvider() {
       if (tokenId != null && reqHMACKey != null) {
-        return new HawkAuthHeaderProvider(Utils.byte2Hex(tokenId), reqHMACKey, payload, skewHandler.getSkewInSeconds());
+        // We always include the payload verification hash for FxA Hawk-authenticated requests.
+        final boolean includePayloadVerificationHash = true;
+        return new HawkAuthHeaderProvider(Utils.byte2Hex(tokenId), reqHMACKey, includePayloadVerificationHash, skewHandler.getSkewInSeconds());
       }
       return super.getAuthHeaderProvider();
     }
 
     @Override
     public String getUserAgent() {
       return FxAccountConstants.USER_AGENT;
     }
@@ -478,17 +481,17 @@ public class FxAccountClient10 {
     BaseResource resource;
     try {
       resource = new BaseResource(new URI(serverURI + "session/create"));
     } catch (URISyntaxException e) {
       invokeHandleError(delegate, e);
       return;
     }
 
-    resource.delegate = new ResourceDelegate<TwoTokens>(resource, delegate, tokenId, reqHMACKey, false) {
+    resource.delegate = new ResourceDelegate<TwoTokens>(resource, delegate, tokenId, reqHMACKey) {
       @Override
       public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
         try {
           byte[] keyFetchToken = new byte[32];
           byte[] sessionToken = new byte[32];
           unbundleBody(body, requestKey, FxAccountUtils.KW("session/create"), keyFetchToken, sessionToken);
           delegate.handleSuccess(new TwoTokens(keyFetchToken, sessionToken));
           return;
@@ -514,17 +517,17 @@ public class FxAccountClient10 {
     BaseResource resource;
     try {
       resource = new BaseResource(new URI(serverURI + "session/destroy"));
     } catch (URISyntaxException e) {
       invokeHandleError(delegate, e);
       return;
     }
 
-    resource.delegate = new ResourceDelegate<Void>(resource, delegate, tokenId, reqHMACKey, false) {
+    resource.delegate = new ResourceDelegate<Void>(resource, delegate, tokenId, reqHMACKey) {
       @Override
       public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
         delegate.handleSuccess(null);
       }
     };
     post(resource, null, delegate);
   }
 
@@ -603,17 +606,17 @@ public class FxAccountClient10 {
     BaseResource resource;
     try {
       resource = new BaseResource(new URI(serverURI + "account/keys"));
     } catch (URISyntaxException e) {
       invokeHandleError(delegate, e);
       return;
     }
 
-    resource.delegate = new ResourceDelegate<TwoKeys>(resource, delegate, tokenId, reqHMACKey, false) {
+    resource.delegate = new ResourceDelegate<TwoKeys>(resource, delegate, tokenId, reqHMACKey) {
       @Override
       public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
         try {
           byte[] kA = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
           byte[] wrapkB = new byte[FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES];
           unbundleBody(body, requestKey, FxAccountUtils.KW("account/keys"), kA, wrapkB);
           delegate.handleSuccess(new TwoKeys(kA, wrapkB));
           return;
@@ -665,17 +668,17 @@ public class FxAccountClient10 {
     BaseResource resource;
     try {
       resource = new BaseResource(new URI(serverURI + "recovery_email/status"));
     } catch (URISyntaxException e) {
       invokeHandleError(delegate, e);
       return;
     }
 
-    resource.delegate = new ResourceDelegate<StatusResponse>(resource, delegate, tokenId, reqHMACKey, false) {
+    resource.delegate = new ResourceDelegate<StatusResponse>(resource, delegate, tokenId, reqHMACKey) {
       @Override
       public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
         try {
           String[] requiredStringFields = new String[] { JSON_KEY_EMAIL };
           body.throwIfFieldsMissingOrMisTyped(requiredStringFields, String.class);
           String email = body.getString(JSON_KEY_EMAIL);
           Boolean verified = body.getBoolean(JSON_KEY_VERIFIED);
           delegate.handleSuccess(new StatusResponse(email, verified));
@@ -707,17 +710,17 @@ public class FxAccountClient10 {
     BaseResource resource;
     try {
       resource = new BaseResource(new URI(serverURI + "certificate/sign"));
     } catch (URISyntaxException e) {
       invokeHandleError(delegate, e);
       return;
     }
 
-    resource.delegate = new ResourceDelegate<String>(resource, delegate, tokenId, reqHMACKey, true) {
+    resource.delegate = new ResourceDelegate<String>(resource, delegate, tokenId, reqHMACKey) {
       @Override
       public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
         String cert = body.getString("cert");
         if (cert == null) {
           delegate.handleError(new FxAccountClientException("cert must be a non-null string"));
           return;
         }
         delegate.handleSuccess(cert);
@@ -748,17 +751,17 @@ public class FxAccountClient10 {
     BaseResource resource;
     try {
       resource = new BaseResource(new URI(serverURI + "recovery_email/resend_code"));
     } catch (URISyntaxException e) {
       invokeHandleError(delegate, e);
       return;
     }
 
-    resource.delegate = new ResourceDelegate<Void>(resource, delegate, tokenId, reqHMACKey, false) {
+    resource.delegate = new ResourceDelegate<Void>(resource, delegate, tokenId, reqHMACKey) {
       @Override
       public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
         try {
           delegate.handleSuccess(null);
           return;
         } catch (Exception e) {
           delegate.handleError(e);
           return;
--- a/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
@@ -1,16 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.fxa.activities;
 
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.background.fxa.PasswordStretcher;
@@ -19,27 +22,32 @@ import org.mozilla.gecko.fxa.FxAccountCo
 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.ProgressDisplay;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Engaged;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.setup.Constants;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
+import android.accounts.Account;
 import android.accounts.AccountManager;
+import android.content.Context;
 import android.content.Intent;
+import android.os.AsyncTask;
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.text.method.PasswordTransformationMethod;
 import android.text.method.SingleLineTransformationMethod;
 import android.util.Patterns;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
 abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity implements ProgressDisplay {
   public FxAccountAbstractSetupActivity() {
@@ -49,17 +57,17 @@ abstract public class FxAccountAbstractS
   protected FxAccountAbstractSetupActivity(int resume) {
     super(resume);
   }
 
   private static final String LOG_TAG = FxAccountAbstractSetupActivity.class.getSimpleName();
 
   protected int minimumPasswordLength = 8;
 
-  protected EditText emailEdit;
+  protected AutoCompleteTextView emailEdit;
   protected EditText passwordEdit;
   protected Button showPasswordButton;
   protected TextView remoteErrorTextView;
   protected Button button;
   protected ProgressBar progressBar;
 
   protected void createShowPasswordButton() {
     showPasswordButton.setOnClickListener(new OnClickListener() {
@@ -307,9 +315,59 @@ abstract public class FxAccountAbstractS
   /**
    * Factory function that produces a new PasswordStretcher instance.
    *
    * @return PasswordStretcher instance.
    */
   protected PasswordStretcher makePasswordStretcher(String password) {
     return new QuickPasswordStretcher(password);
   }
+
+  protected abstract static class GetAccountsAsyncTask extends AsyncTask<Void, Void, Account[]> {
+    protected final Context context;
+
+    public GetAccountsAsyncTask(Context context) {
+      super();
+      this.context = context;
+    }
+
+    @Override
+    protected Account[] doInBackground(Void... params) {
+      return AccountManager.get(context).getAccounts();
+    }
+  }
+
+  /**
+   * This updates UI, so needs to be done on the foreground thread.
+   */
+  protected void populateEmailAddressAutocomplete(Account[] accounts) {
+    // First a set, since we don't want repeats.
+    final Set<String> emails = new HashSet<String>();
+    for (Account account : accounts) {
+      if (!Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) {
+        continue;
+      }
+      emails.add(account.name);
+    }
+
+    // And then sorted in alphabetical order.
+    final String[] sortedEmails = emails.toArray(new String[0]);
+    Arrays.sort(sortedEmails);
+
+    final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, sortedEmails);
+    emailEdit.setAdapter(adapter);
+  }
+
+  @Override
+  public void onResume() {
+    super.onResume();
+
+    // Getting Accounts accesses databases on disk, so needs to be done on a
+    // background thread.
+    final GetAccountsAsyncTask task = new GetAccountsAsyncTask(this) {
+      @Override
+      public void onPostExecute(Account[] accounts) {
+        populateEmailAddressAutocomplete(accounts);
+      }
+    };
+    task.execute();
+  }
 }
--- a/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
@@ -30,16 +30,17 @@ import android.content.Intent;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.text.Spannable;
 import android.text.Spanned;
 import android.text.method.LinkMovementMethod;
 import android.text.style.ClickableSpan;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.widget.AutoCompleteTextView;
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.EditText;
 import android.widget.ListView;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
 /**
@@ -61,17 +62,17 @@ public class FxAccountCreateAccountActiv
    */
   @Override
   public void onCreate(Bundle icicle) {
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
     super.onCreate(icicle);
     setContentView(R.layout.fxaccount_create_account);
 
-    emailEdit = (EditText) ensureFindViewById(null, R.id.email, "email edit");
+    emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit");
     passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
     showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
     yearEdit = (EditText) ensureFindViewById(null, R.id.year_edit, "year edit");
     remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");
     button = (Button) ensureFindViewById(null, R.id.button, "create account button");
     progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar");
     chooseCheckBox = (CheckBox) ensureFindViewById(null, R.id.choose_what_to_sync_checkbox, "choose what to sync check box");
     selectedEngines = new HashMap<String, Boolean>();
deleted file mode 100644
--- a/mobile/android/base/fxa/activities/FxAccountCreateAccountFragment.java
+++ /dev/null
@@ -1,191 +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/. */
-
-package org.mozilla.gecko.fxa.activities;
-
-import android.support.v4.app.Fragment;
-
-public class FxAccountCreateAccountFragment extends Fragment { // implements OnClickListener {
-  protected static final String LOG_TAG = FxAccountCreateAccountFragment.class.getSimpleName();
-//
-//  protected FxAccountSetupActivity activity;
-//
-//  protected EditText emailEdit;
-//  protected EditText passwordEdit;
-//  protected EditText password2Edit;
-//  protected Button button;
-//
-//  protected TextView emailError;
-//  protected TextView passwordError;
-//
-//  protected TextChangedListener textChangedListener;
-//  protected EditorActionListener editorActionListener;
-//  protected OnFocusChangeListener focusChangeListener;
-//  @Override
-//  public void onCreate(Bundle savedInstanceState) {
-//    super.onCreate(savedInstanceState);
-//    // Retain this fragment across configuration changes. See, for example,
-//    // http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
-//    // This fragment will own AsyncTask instances which should not be
-//    // interrupted by configuration changes (and activity changes).
-//    setRetainInstance(true);
-//  }
-
-//  @Override
-//  public View onCreateView(LayoutInflater inflater, ViewGroup container,
-//      Bundle savedInstanceState) {
-//    View v = inflater.inflate(R.layout.fxaccount_create_account_fragment, container, false);
-//
-//    FxAccountSetupActivity.linkifyTextViews(v, new int[] { R.id.description, R.id.policy });
-//
-//    emailEdit = (EditText) ensureFindViewById(v, R.id.email, "email");
-//    passwordEdit = (EditText) ensureFindViewById(v, R.id.password, "password");
-//    // Second password can be null.
-//    password2Edit = (EditText) v.findViewById(R.id.password2);
-//
-//    emailError = (TextView) ensureFindViewById(v, R.id.email_error, "email error");
-//    passwordError = (TextView) ensureFindViewById(v, R.id.password_error, "password error");
-//
-//    textChangedListener = new TextChangedListener();
-//    editorActionListener = new EditorActionListener();
-//    focusChangeListener = new FocusChangeListener();
-//
-//    addListeners(emailEdit);
-//    addListeners(passwordEdit);
-//    if (password2Edit != null) {
-//      addListeners(password2Edit);
-//    }
-//
-//    button = (Button) ensureFindViewById(v, R.id.create_account_button, "button");
-//    button.setOnClickListener(this);
-//    return v;
-//  }
-
-//  protected void onCreateAccount(View button) {
-//    Logger.debug(LOG_TAG, "onCreateAccount: Asking for username/password for new account.");
-//    String email = emailEdit.getText().toString();
-//    String password = passwordEdit.getText().toString();
-//    activity.signUp(email, password);
-//  }
-//
-//  @Override
-//  public void onClick(View v) {
-//    switch (v.getId()) {
-//    case R.id.create_account_button:
-//      if (!validate(false)) {
-//        return;
-//      }
-//      onCreateAccount(v);
-//      break;
-//    }
-//  }
-//
-//  protected void addListeners(EditText editText) {
-//    editText.addTextChangedListener(textChangedListener);
-//    editText.setOnEditorActionListener(editorActionListener);
-//    editText.setOnFocusChangeListener(focusChangeListener);
-//  }
-//
-//  protected class FocusChangeListener implements OnFocusChangeListener {
-//    @Override
-//    public void onFocusChange(View v, boolean hasFocus) {
-//      if (hasFocus) {
-//        return;
-//      }
-//      validate(false);
-//    }
-//  }
-//
-//  protected class EditorActionListener implements OnEditorActionListener {
-//    @Override
-//    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
-//      validate(false);
-//      return false;
-//    }
-//  }
-//
-//  protected class TextChangedListener implements TextWatcher {
-//    @Override
-//    public void afterTextChanged(Editable s) {
-//      validate(true);
-//    }
-//
-//    @Override
-//    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-//      // Do nothing.
-//    }
-//
-//    @Override
-//    public void onTextChanged(CharSequence s, int start, int before, int count) {
-//      // Do nothing.
-//    }
-//  }
-//
-//  /**
-//   * Show or hide error messaging.
-//   *
-//   * @param removeOnly
-//   *          if true, possibly remove existing error messages but do not set an
-//   *          error message if one was not present.
-//   * @param errorResourceId
-//   *          of error string, or -1 to hide.
-//   * @param errorView
-//   *          <code>TextView</code> instance to display error message in.
-//   * @param edits
-//   *          <code>EditText</code> instances to style.
-//   */
-//  protected void setError(boolean removeOnly, int errorResourceId, TextView errorView, EditText... edits) {
-//    if (removeOnly && errorResourceId != -1) {
-//      return;
-//    }
-//
-//    int res = errorResourceId == -1 ? R.drawable.fxaccount_textfield_background : R.drawable.fxaccount_textfield_error_background;
-//    for (EditText edit : edits) {
-//      if (edit == null) {
-//        continue;
-//      }
-//      edit.setBackgroundResource(res);
-//    }
-//    if (errorResourceId == -1) {
-//      errorView.setVisibility(View.GONE);
-//      errorView.setText(null);
-//    } else {
-//      errorView.setText(errorResourceId);
-//      errorView.setVisibility(View.VISIBLE);
-//    }
-//  }
-//
-//  protected boolean validate(boolean removeOnly) {
-//    boolean enabled = true;
-//    final String email = emailEdit.getText().toString();
-//    final String password = passwordEdit.getText().toString();
-//
-//    if (email.length() == 0 || Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
-//      setError(removeOnly, -1, emailError, emailEdit);
-//    } else {
-//      enabled = false;
-//      setError(removeOnly, R.string.fxaccount_bad_email, emailError, emailEdit);
-//    }
-//
-//    if (password2Edit != null) {
-//      final String password2 = password2Edit.getText().toString();
-//      enabled = enabled && password2.length() > 0;
-//
-//      boolean passwordsMatch = password.equals(password2);
-//      if (passwordsMatch) {
-//        setError(removeOnly, -1, passwordError, passwordEdit, password2Edit);
-//      } else {
-//        enabled = false;
-//        setError(removeOnly, R.string.fxaccount_bad_passwords, passwordError, passwordEdit, password2Edit);
-//      }
-//    }
-//
-//    if (enabled != button.isEnabled()) {
-//      Logger.debug(LOG_TAG, (enabled ? "En" : "Dis") + "abling button.");
-//      button.setEnabled(enabled);
-//    }
-//
-//    return enabled;
-//  }
-}
--- a/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
@@ -18,16 +18,17 @@ import org.mozilla.gecko.background.fxa.
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.widget.AutoCompleteTextView;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
 /**
  * Activity which displays sign in screen to the user.
  */
@@ -41,17 +42,17 @@ public class FxAccountSignInActivity ext
    */
   @Override
   public void onCreate(Bundle icicle) {
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
     super.onCreate(icicle);
     setContentView(R.layout.fxaccount_sign_in);
 
-    emailEdit = (EditText) ensureFindViewById(null, R.id.email, "email edit");
+    emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit");
     passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
     showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
     remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");
     button = (Button) ensureFindViewById(null, R.id.button, "sign in button");
     progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar");
 
     minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in.
     createSignInButton();
--- a/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
@@ -23,16 +23,17 @@ import org.mozilla.gecko.fxa.authenticat
 import org.mozilla.gecko.fxa.login.Engaged;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.StateLabel;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.widget.AutoCompleteTextView;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
 /**
  * Activity which displays a screen for updating the local password.
  */
@@ -54,17 +55,17 @@ public class FxAccountUpdateCredentialsA
    */
   @Override
   public void onCreate(Bundle icicle) {
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
     super.onCreate(icicle);
     setContentView(R.layout.fxaccount_update_credentials);
 
-    emailEdit = (EditText) ensureFindViewById(null, R.id.email, "email edit");
+    emailEdit = (AutoCompleteTextView) ensureFindViewById(null, R.id.email, "email edit");
     passwordEdit = (EditText) ensureFindViewById(null, R.id.password, "password edit");
     showPasswordButton = (Button) ensureFindViewById(null, R.id.show_password, "show password button");
     remoteErrorTextView = (TextView) ensureFindViewById(null, R.id.remote_error, "remote error text view");
     button = (Button) ensureFindViewById(null, R.id.button, "update credentials");
     progressBar = (ProgressBar) ensureFindViewById(null, R.id.progress, "progress bar");
 
     minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in.
     createButton();
--- a/mobile/android/base/fxa/sync/FxAccountGlobalSession.java
+++ b/mobile/android/base/fxa/sync/FxAccountGlobalSession.java
@@ -1,45 +1,39 @@
 /* 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/. */
 
 package org.mozilla.gecko.fxa.sync;
 
 import java.io.IOException;
-import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Collections;
 import java.util.HashMap;
 
 import org.json.simple.parser.ParseException;
-import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.SyncConfigurationException;
 import org.mozilla.gecko.sync.delegates.BaseGlobalSessionCallback;
 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
 import org.mozilla.gecko.sync.stage.CheckPreconditionsStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 
 import android.content.Context;
 
 public class FxAccountGlobalSession extends GlobalSession {
-  private static final String LOG_TAG = FxAccountGlobalSession.class.getSimpleName();
-
-  public FxAccountGlobalSession(String storageEndpoint, SyncConfiguration config, BaseGlobalSessionCallback callback,
-      Context context, ClientsDataDelegate clientsDelegate)
-      throws SyncConfigurationException, IllegalArgumentException, IOException,
-      ParseException, NonObjectJSONException, URISyntaxException {
+  public FxAccountGlobalSession(SyncConfiguration config,
+                                BaseGlobalSessionCallback callback,
+                                Context context,
+                                ClientsDataDelegate clientsDelegate)
+                                    throws SyncConfigurationException, IllegalArgumentException, IOException, ParseException, NonObjectJSONException, URISyntaxException {
     super(config, callback, context, clientsDelegate, null);
-    URI storageURI = new URI(storageEndpoint);
-    this.config.setClusterURL(storageURI);
-    FxAccountConstants.pii(LOG_TAG, "clusterURL is " + config.getClusterURLString());
   }
 
   @Override
   public void prepareStages() {
     super.prepareStages();
     HashMap<Stage, GlobalSyncStage> stages = new HashMap<Stage, GlobalSyncStage>();
     stages.putAll(this.stages);
     stages.put(Stage.ensureClusterURL, new CheckPreconditionsStage());
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -15,32 +15,29 @@ import java.util.concurrent.ExecutorServ
 import java.util.concurrent.Executors;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.SkewHandler;
 import org.mozilla.gecko.browserid.BrowserIDKeyPair;
 import org.mozilla.gecko.browserid.JSONWebTokenUtils;
-import org.mozilla.gecko.browserid.verifier.BrowserIDRemoteVerifierClient;
-import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierDelegate;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AccountPickler;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
 import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine;
 import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
 import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
 import org.mozilla.gecko.fxa.login.Married;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.StateLabel;
 import org.mozilla.gecko.fxa.login.StateFactory;
 import org.mozilla.gecko.sync.BackoffHandler;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.PrefsBackoffHandler;
 import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.ThreadPool;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.delegates.BaseGlobalSessionCallback;
@@ -363,35 +360,40 @@ public class FxAccountSyncAdapter extend
           ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs);
 
           // We compute skew over time using SkewHandler. This yields an unchanging
           // skew adjustment that the HawkAuthHeaderProvider uses to adjust its
           // timestamps. Eventually we might want this to adapt within the scope of a
           // global session.
           final SkewHandler storageServerSkewHandler = SkewHandler.getSkewHandlerForHostname(storageHostname);
           final long storageServerSkew = storageServerSkewHandler.getSkewInSeconds();
-          final AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), false, storageServerSkew);
+          // We expect Sync to upload large sets of records. Calculating the
+          // payload verification hash for these record sets could be expensive,
+          // so we explicitly do not send payload verification hashes to the
+          // Sync storage endpoint.
+          final boolean includePayloadVerificationHash = false;
+          final AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), includePayloadVerificationHash, storageServerSkew);
 
           final Context context = getContext();
           final SyncConfiguration syncConfig = new SyncConfiguration(token.uid, authHeaderProvider, sharedPrefs, syncKeyBundle);
 
           Collection<String> knownStageNames = SyncConfiguration.validEngineNames();
           syncConfig.stagesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras);
+          syncConfig.setClusterURL(storageServerURI);
 
-          globalSession = new FxAccountGlobalSession(token.endpoint, syncConfig, callback, context, clientsDataDelegate);
+          globalSession = new FxAccountGlobalSession(syncConfig, callback, context, clientsDataDelegate);
           globalSession.start();
         } catch (Exception e) {
           callback.handleError(globalSession, e);
           return;
         }
       }
 
       @Override
       public void handleFailure(TokenServerException e) {
-        debugAssertion(audience, assertion);
         handleError(e);
       }
 
       @Override
       public void handleError(Exception e) {
         Logger.error(LOG_TAG, "Failed to get token.", e);
         callback.handleError(null, e);
       }
@@ -606,39 +608,9 @@ public class FxAccountSyncAdapter extend
     } catch (Exception e) {
       Logger.error(LOG_TAG, "Got error syncing.", e);
       syncDelegate.handleError(e);
     }
 
     Logger.info(LOG_TAG, "Syncing done.");
     lastSyncRealtimeMillis = SystemClock.elapsedRealtime();
   }
-
-  protected void debugAssertion(String audience, String assertion) {
-    final CountDownLatch verifierLatch = new CountDownLatch(1);
-    BrowserIDRemoteVerifierClient client = new BrowserIDRemoteVerifierClient(URI.create(BrowserIDRemoteVerifierClient.DEFAULT_VERIFIER_URL));
-    client.verify(audience, assertion, new BrowserIDVerifierDelegate() {
-      @Override
-      public void handleSuccess(ExtendedJSONObject response) {
-        Logger.info(LOG_TAG, "Remote verifier returned success: " + response.toJSONString());
-        verifierLatch.countDown();
-      }
-
-      @Override
-      public void handleFailure(ExtendedJSONObject response) {
-        Logger.warn(LOG_TAG, "Remote verifier returned failure: " + response.toJSONString());
-        verifierLatch.countDown();
-      }
-
-      @Override
-      public void handleError(Exception e) {
-        Logger.error(LOG_TAG, "Remote verifier returned error.", e);
-        verifierLatch.countDown();
-      }
-    });
-
-    try {
-      verifierLatch.await();
-    } catch (InterruptedException e) {
-      Logger.error(LOG_TAG, "Got error.", e);
-    }
-  }
 }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -49,16 +49,17 @@
 <!ENTITY go "Go">
 <!ENTITY search "Search">
 <!ENTITY reload "Reload">
 <!ENTITY forward "Forward">
 <!ENTITY menu "Menu">
 <!ENTITY back "Back">
 <!ENTITY stop "Stop">
 <!ENTITY site_security "Site Security">
+<!ENTITY edit_mode_cancel "Cancel">
 
 <!ENTITY close_tab "Close Tab">
 <!ENTITY one_tab "1 tab">
 <!-- Localization note (num_tabs2) : Number of tabs is always more than one.
      We can't use android plural forms, sadly. See bug #753859. -->
 <!ENTITY num_tabs2 "&formatD; tabs">
 <!ENTITY new_tab_opened "New tab opened">
 
--- a/mobile/android/base/menu/MenuItemActionView.java
+++ b/mobile/android/base/menu/MenuItemActionView.java
@@ -56,17 +56,19 @@ public class MenuItemActionView extends 
         mMenuItem = (MenuItemDefault) findViewById(R.id.menu_item);
         mMenuButton = (MenuItemActionBar) findViewById(R.id.menu_item_button);
         mActionButtons = new ArrayList<ImageButton>();
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         View parent = (View) getParent();
-        if ((right - left) < parent.getMeasuredWidth() || mActionButtons.size() != 0) {
+        final int padding = getPaddingLeft() + getPaddingRight();
+        final int parentPadding = parent.getPaddingLeft() + parent.getPaddingRight();
+        if ((right - left - padding) < (parent.getMeasuredWidth() - parentPadding) || mActionButtons.size() != 0) {
             // Use the icon.
             mMenuItem.setVisibility(View.GONE);
             mMenuButton.setVisibility(View.VISIBLE);
         } else {
             // Use the button.
             mMenuItem.setVisibility(View.VISIBLE);
             mMenuButton.setVisibility(View.GONE);
         }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -434,16 +434,17 @@ gbjar.generated_sources += [
     'org/mozilla/gecko/SysInfo.java',
     'org/mozilla/gecko/widget/ThemedEditText.java',
     'org/mozilla/gecko/widget/ThemedImageButton.java',
     'org/mozilla/gecko/widget/ThemedImageView.java',
     'org/mozilla/gecko/widget/ThemedLinearLayout.java',
     'org/mozilla/gecko/widget/ThemedRelativeLayout.java',
     'org/mozilla/gecko/widget/ThemedTextSwitcher.java',
     'org/mozilla/gecko/widget/ThemedTextView.java',
+    'org/mozilla/gecko/widget/ThemedView.java',
 ]
 if CONFIG['MOZ_CRASHREPORTER']:
     gbjar.sources += [ 'CrashReporter.java' ]
     ANDROID_RES_DIRS += [ SRCDIR + '/crashreporter/res' ]
 
 gbjar.sources += sync_java_files
 gbjar.generated_sources += sync_generated_java_files
 gbjar.extra_jars = [
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..70caf842d33e52296d730f9016c4c50bf89b0838
GIT binary patch
literal 301
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUtFP<)rAr-gY-rUP|*no%S!MVDl
ze6qW~$THV%UUVzLAmjq~PKD^VyirCj-yQ36ekP@@^k!gW`H_0y?LUpJ+oG=bzxH{{
zb*<{#y{Fm?E%nznMXm4SVB8Y7cllSIryL7juD$N?q2cdC_gsO7d=mkc2Lb*J9Ig`{
z+A2snwta8NV3EAXyht#mg1^HJEXa6Rm_hDfTR#&6i=Z7xgP_X~4F)%Xhl?2`7!Ucg
zC9rVVOE(BA{HbAZ`|;h0!PZHA-;rNE|9<zsxw5oHta!D~@~sOaQ@vOE);4YQns+el
x*Ttk?5A%L~top^=cg5YuYN?0=1LL)|=CA)`mdn@tv;g{_!PC{xWt~$(69E0Qd+`7O
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..608a80f47210fc07d464d5b672dbb2ebcf4cf2d3
GIT binary patch
literal 205
zc%17D@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJDV{ElAr-gYUfalfKtX`np>1L6
zGLg(py3Hva=f&?U%ADkw`G~u`!Jmt<Yo_Pq29`P8JKim?+O@orYo=Is2-^kQXO*5V
z-8-MXWKj@XAn-VbMM1E}lI=ss%Lpb1g#$B68T5Xz>M;ihYTRd(5L{5ho#67I$kyRM
z=a<<5|D|8?FH-JH`kAV-zwar((9g*_@kcp54(Q0Y);#-k?Askzp!*m+UHx3vIVCg!
E0H9e+EC2ui
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/drawable-v12/fxaccount_password_active.xml
@@ -0,0 +1,19 @@
+<?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/. -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+  <solid
+      android:color="@android:color/white" />
+  <stroke
+      android:width="@dimen/fxaccount_stroke_width"
+      android:color="@color/fxaccount_input_borderActive" />
+  <corners
+      android:radius="@dimen/fxaccount_corner_radius"
+      android:topLeftRadius="@dimen/fxaccount_corner_radius"
+      android:topRightRadius="0dp"
+      android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
+      android:bottomRightRadius="0dp" />
+</shape>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/drawable-v12/fxaccount_password_button_hide_active.xml
@@ -0,0 +1,19 @@
+<?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/. -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+  <solid
+      android:color="@color/fxaccount_password_hide_backgroundcolor" />
+  <stroke
+      android:width="@dimen/fxaccount_stroke_width"
+      android:color="@color/fxaccount_input_borderActive" />
+  <corners
+      android:radius="@dimen/fxaccount_corner_radius"
+      android:topLeftRadius="0dp"
+      android:topRightRadius="@dimen/fxaccount_corner_radius"
+      android:bottomLeftRadius="0dp"
+      android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
+</shape>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/drawable-v12/fxaccount_password_button_show_active.xml
@@ -0,0 +1,19 @@
+<?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/. -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+  <solid
+      android:color="@color/fxaccount_password_show_backgroundcolor" />
+  <stroke
+      android:width="@dimen/fxaccount_stroke_width"
+      android:color="@color/fxaccount_input_borderActive" />
+  <corners
+      android:radius="@dimen/fxaccount_corner_radius"
+      android:topLeftRadius="0dp"
+      android:topRightRadius="@dimen/fxaccount_corner_radius"
+      android:bottomLeftRadius="0dp"
+      android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
+</shape>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/drawable-v12/fxaccount_password_inactive.xml
@@ -0,0 +1,19 @@
+<?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/. -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+  <solid
+      android:color="@android:color/white" />
+  <stroke
+      android:width="@dimen/fxaccount_stroke_width"
+      android:color="@color/fxaccount_input_borderInactive" />
+  <corners
+      android:radius="@dimen/fxaccount_corner_radius"
+      android:topLeftRadius="@dimen/fxaccount_corner_radius"
+      android:topRightRadius="0dp"
+      android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
+      android:bottomRightRadius="0dp" />
+</shape>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4b6f1f34fd46470288a7a999b0479f55ce51871c
GIT binary patch
literal 370
zc%17D@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEV6^mfaSW-r_4c-5(IE!`m%zLO
zXLm3)3f~VF3@_UB?m#G?8pCz>+l}=XR)+mu#gJLPW6joor7Qvt42(dc;rY(`nz+fz
zI~!T&uXQZ;?t5~IG3}@Orb+833l#jEUmbn&acIl_Y4PexdshFE;t+W7WABTV9DDwp
zX=HrHB)pKN;ax7%7r|}Q<}=i`tbMBPAfK51lk<Z6ji@@9FiVKtPJ;$JCav>~zc^Od
zu)k1V@JVJtf5WL_nBW2%<`qgGfP!5IP8T<5F=^Q|GB|KHPJhl2$`Ut^xkRv{lJA21
zjHj#~e>ha?IMx2~sM&F-+HtAx<5Atmr=HiLlGmx4*QJ)%tuEK2BG>y=O0G{$u3w$c
yL#6M83ZJK?+VcfV7H{J7w$^(uuLk$|%>2U~$xb_eg>VDIlEKr}&t;ucLK6VOFpa(d
--- a/mobile/android/base/resources/drawable/fxaccount_password_active.xml
+++ b/mobile/android/base/resources/drawable/fxaccount_password_active.xml
@@ -5,15 +5,18 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
   <solid
       android:color="@android:color/white" />
   <stroke
       android:width="@dimen/fxaccount_stroke_width"
       android:color="@color/fxaccount_input_borderActive" />
+  <!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom
+       right are swapped. These values correct this bug; the resources
+       that don't need correction are in res/drawable-v12. -->
   <corners
       android:radius="@dimen/fxaccount_corner_radius"
       android:topLeftRadius="@dimen/fxaccount_corner_radius"
       android:topRightRadius="0dp"
-      android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
-      android:bottomRightRadius="0dp" />
+      android:bottomLeftRadius="0dp"
+      android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
 </shape>
--- a/mobile/android/base/resources/drawable/fxaccount_password_button_hide_active.xml
+++ b/mobile/android/base/resources/drawable/fxaccount_password_button_hide_active.xml
@@ -5,15 +5,18 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
   <solid
       android:color="@color/fxaccount_password_hide_backgroundcolor" />
   <stroke
       android:width="@dimen/fxaccount_stroke_width"
       android:color="@color/fxaccount_input_borderActive" />
+  <!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom
+       right are swapped. These values correct this bug; the resources
+       that don't need correction are in res/drawable-v12. -->
   <corners
       android:radius="@dimen/fxaccount_corner_radius"
       android:topLeftRadius="0dp"
       android:topRightRadius="@dimen/fxaccount_corner_radius"
-      android:bottomLeftRadius="0dp"
-      android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
+      android:bottomRightRadius="0dp"
+      android:bottomLeftRadius="@dimen/fxaccount_corner_radius" />
 </shape>
--- a/mobile/android/base/resources/drawable/fxaccount_password_button_show_active.xml
+++ b/mobile/android/base/resources/drawable/fxaccount_password_button_show_active.xml
@@ -5,15 +5,18 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
   <solid
       android:color="@color/fxaccount_password_show_backgroundcolor" />
   <stroke
       android:width="@dimen/fxaccount_stroke_width"
       android:color="@color/fxaccount_input_borderActive" />
+  <!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom
+       right are swapped. These values correct this bug; the resources
+       that don't need correction are in res/drawable-v12. -->
   <corners
       android:radius="@dimen/fxaccount_corner_radius"
       android:topLeftRadius="0dp"
       android:topRightRadius="@dimen/fxaccount_corner_radius"
-      android:bottomLeftRadius="0dp"
-      android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
+      android:bottomRightRadius="0dp"
+      android:bottomLeftRadius="@dimen/fxaccount_corner_radius" />
 </shape>
--- a/mobile/android/base/resources/drawable/fxaccount_password_inactive.xml
+++ b/mobile/android/base/resources/drawable/fxaccount_password_inactive.xml
@@ -5,15 +5,18 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
   <solid
       android:color="@android:color/white" />
   <stroke
       android:width="@dimen/fxaccount_stroke_width"
       android:color="@color/fxaccount_input_borderInactive" />
+  <!-- On Android pre v12/3.0/Gingerbread, bottom left and bottom
+       right are swapped. These values correct this bug; the resources
+       that don't need correction are in res/drawable-v12. -->
   <corners
       android:radius="@dimen/fxaccount_corner_radius"
       android:topLeftRadius="@dimen/fxaccount_corner_radius"
       android:topRightRadius="0dp"
-      android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
-      android:bottomRightRadius="0dp" />
+      android:bottomLeftRadius="0dp"
+      android:bottomRightRadius="@dimen/fxaccount_corner_radius" />
 </shape>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/drawable/toolbar_separator.xml
@@ -0,0 +1,12 @@
+<?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/. -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+
+    <solid android:color="#a6aeb4"/>
+    <size android:width="1dp" />
+
+</shape>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/drawable/toolbar_separator_pb.xml
@@ -0,0 +1,12 @@
+<?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/. -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+
+    <solid android:color="#222"/>
+    <size android:width="1dp" />
+
+</shape>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/drawable/toolbar_separator_selector.xml
@@ -0,0 +1,15 @@
+<?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/. -->
+
+<!-- Note that android:color="@color/toolbar_separator" (which uses a selector)
+     directly in the <shape> does not appear to work, so instead we select
+     between two shapes with different colors. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:gecko="http://schemas.android.com/apk/res-auto">
+
+    <item gecko:state_private="true" android:drawable="@drawable/toolbar_separator_pb"/>
+    <item android:drawable="@drawable/toolbar_separator"/>
+
+</selector>
rename from mobile/android/base/resources/drawable/url_bar_right_edge.xml
rename to mobile/android/base/resources/drawable/url_bar_translating_edge.xml
--- a/mobile/android/base/resources/layout/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout/browser_toolbar.xml
@@ -7,43 +7,54 @@
        xmlns:gecko="http://schemas.android.com/apk/res-auto">
 
     <ImageButton android:id="@+id/back"
                  style="@style/UrlBar.ImageButton.Unused"/>
 
     <ImageButton android:id="@+id/forward"
                  style="@style/UrlBar.ImageButton.Unused"/>
 
+    <!-- Note: any layout parameters setting the right edge of
+         this View should be matched in the url_bar_translating_edge.
+
+         Note 2: On devices where the editing mode cancel items are
+         wider than the tabs and similar buttons (e.g. hardware menu
+         button), the url bar will shrink, in which case the LayoutParams
+         of this View are changed dynamically. -->
     <ImageView android:id="@+id/url_bar_entry"
                style="@style/UrlBar.Button"
                android:layout_marginLeft="4dp"
                android:layout_marginRight="-19dp"
                android:layout_marginTop="5dp"
                android:layout_marginBottom="5dp"
                android:layout_centerVertical="true"
                android:layout_toLeftOf="@+id/tabs"
                android:paddingRight="4dp"
                android:duplicateParentState="true"
                android:clickable="false"
                android:focusable="false"
                android:src="@drawable/url_bar_entry"
                android:scaleType="fitXY"/>
 
-    <ImageView android:id="@+id/url_bar_right_edge"
+    <!-- A View that clips with url_bar_entry and translates
+         around it to animate shrinking or growing the url bar,
+         which occurs in the display/editing mode transitions. -->
+    <ImageView android:id="@+id/url_bar_translating_edge"
                style="@style/UrlBar.Button"
                android:layout_alignLeft="@id/url_bar_entry"
-               android:layout_alignRight="@id/url_bar_entry"
+               android:layout_toLeftOf="@+id/tabs"
                android:layout_alignTop="@id/url_bar_entry"
                android:layout_alignBottom="@id/url_bar_entry"
+               android:layout_marginRight="-19dp"
                android:paddingRight="4dp"
                android:duplicateParentState="true"
                android:clickable="false"
                android:focusable="false"
                android:visibility="invisible"
-               android:src="@drawable/url_bar_right_edge"
+               android:src="@drawable/url_bar_translating_edge"
                android:scaleType="fitXY"/>
 
     <LinearLayout android:id="@+id/menu_items"
                   style="@style/UrlBar.ImageButton.Unused"/>
 
     <org.mozilla.gecko.toolbar.ShapedButton android:id="@+id/menu"
                                             style="@style/UrlBar.ImageButton"
                                             android:layout_width="48dip"
@@ -81,18 +92,39 @@
                         style="@style/UrlBar.ImageButton.TabCount"
                         android:layout_width="24dip"
                         android:layout_height="24dip"
                         android:layout_marginLeft="40dip"
                         android:layout_marginRight="8dip"
                         android:layout_marginTop="12dip"
                         android:layout_alignRight="@id/tabs"/>
 
+    <!-- Note that the edit components are invisible so that the views
+         depending on their location can properly layout. -->
+    <ImageView android:id="@+id/edit_cancel"
+               style="@style/UrlBar.ImageButton.Icon"
+               android:layout_alignParentRight="true"
+               android:src="@drawable/close_edit_mode"
+               android:contentDescription="@string/edit_mode_cancel"
+               android:visibility="invisible"/>
+
+    <org.mozilla.gecko.widget.ThemedView android:id="@+id/edit_separator"
+                                         android:layout_toLeftOf="@id/edit_cancel"
+                                         android:layout_width="1dip"
+                                         android:layout_height="match_parent"
+                                         android:layout_marginTop="12dp"
+                                         android:layout_marginBottom="12dp"
+                                         android:layout_marginLeft="8dp"
+                                         android:layout_marginRight="2dp"
+                                         android:background="@drawable/toolbar_separator_selector"
+                                         android:visibility="invisible"/>
+
     <org.mozilla.gecko.toolbar.ToolbarEditLayout android:id="@+id/edit_layout"
                   style="@style/UrlBar.Button"
+                  android:layout_toLeftOf="@id/edit_separator"
                   android:layout_marginLeft="4dp"
                   android:layout_marginRight="4dp"
                   android:paddingLeft="8dp"
                   android:visibility="gone"/>
 
     <org.mozilla.gecko.toolbar.ToolbarDisplayLayout android:id="@+id/display_layout"
                   style="@style/UrlBar.Button"
                   android:layout_toLeftOf="@id/tabs"
--- a/mobile/android/base/resources/layout/fxaccount_email_password_view.xml
+++ b/mobile/android/base/resources/layout/fxaccount_email_password_view.xml
@@ -7,26 +7,27 @@
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android" >
 
     <LinearLayout
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical" >
 
-        <EditText
+        <AutoCompleteTextView
             android:id="@+id/email"
             style="@style/FxAccountEditItem"
             android:layout_marginBottom="10dp"
+            android:completionThreshold="2"
             android:ems="10"
             android:hint="@string/fxaccount_email_hint"
             android:inputType="textEmailAddress" >
 
             <requestFocus />
-        </EditText>
+        </AutoCompleteTextView>
 
         <LinearLayout
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:orientation="horizontal" >
 
             <EditText
                 android:id="@+id/password"
@@ -34,25 +35,51 @@
                 android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
                 android:background="@drawable/fxaccount_password_background"
                 android:ems="10"
                 android:hint="@string/fxaccount_password_hint"
                 android:inputType="textPassword" />
 
-            <Button
-                android:id="@+id/show_password"
-                style="@android:style/Widget.Button"
+            <!-- For the following, I beg forgiveness. The show/hide button is a
+                 toggle button; its text depends on its state. The text for each
+                 state could be a different length. We want to maintain the
+                 button's width regardless of its state. To achieve this, we
+                 size the actual button to its container, and include two
+                 invisible (but present for layout purposes) buttons, one of
+                 each state. The container wraps the larger of the two dummy
+                 buttons; the actual button sizes to the container; and we're
+                 happy. Be thankful there are not three buttons! -->
+            <FrameLayout
                 android:layout_width="wrap_content"
                 android:layout_height="fill_parent"
                 android:layout_weight="0"
-                android:background="@drawable/fxaccount_password_button_show_background"
-                android:minHeight="0dp"
-                android:padding="0dp"
-                android:text="@string/fxaccount_password_show"
-                android:textColor="@color/fxaccount_input_textColor"
-                android:textSize="20sp" >
-            </Button>
+                android:orientation="horizontal" >
+
+                <Button
+                    android:id="@+id/show_password"
+                    style="@style/FxAccountShowHidePasswordButton"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    android:text="@string/fxaccount_password_show" >
+                </Button>
+
+                <Button
+                    style="@style/FxAccountShowHidePasswordButton"
+                    android:layout_width="wrap_content"
+                    android:layout_height="fill_parent"
+                    android:text="@string/fxaccount_password_show"
+                    android:visibility="invisible" >
+                </Button>
+
+                <Button
+                    style="@style/FxAccountShowHidePasswordButton"
+                    android:layout_width="wrap_content"
+                    android:layout_height="fill_parent"
+                    android:text="@string/fxaccount_password_hide"
+                    android:visibility="invisible" >
+                </Button>
+            </FrameLayout>
         </LinearLayout>
     </LinearLayout>
 
-</merge>
\ No newline at end of file
+</merge>
--- a/mobile/android/base/resources/values-large-v11/fxaccount_styles.xml
+++ b/mobile/android/base/resources/values-large-v11/fxaccount_styles.xml
@@ -8,16 +8,25 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="FxAccountMiddle">
         <item name="android:orientation">vertical</item>
         <item name="android:layout_width">500dp</item>
         <item name="android:minWidth">500dp</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_weight">1</item>
-        <item name="android:layout_gravity">center</item>
+        <!-- layout_gravity controls where the middle container goes
+             in its parent, and gravity controls where the container
+             places its internal content.  Usually, the middle
+             container is a LinearLayout and the container is a
+             ScrollView.  In this case, layout_gravity="center"
+             interacts badly with vertical scrolling.  What is needed
+             is layout_gravity="center_horizontal" and
+             gravity="center". -->
+        <item name="android:gravity">center</item>
+        <item name="android:layout_gravity">center_horizontal</item>
         <item name="android:paddingTop">25dp</item>
         <item name="android:paddingLeft">12dp</item>
         <item name="android:paddingRight">12dp</item>
         <item name="android:paddingBottom">15dp</item>
     </style>
 
-</resources>
\ No newline at end of file
+</resources>
--- a/mobile/android/base/resources/values/fxaccount_dimens.xml
+++ b/mobile/android/base/resources/values/fxaccount_dimens.xml
@@ -9,15 +9,20 @@
 
   <!-- The amount of horizontal padding that appears inside the email,
        password, etc input fields. -->
   <dimen name="fxaccount_input_padding_horizontal">20dp</dimen>
   <!-- And the amount of vertical padding that appears inside input
        fields. -->
   <dimen name="fxaccount_input_padding_vertical">10dp</dimen>
 
+  <!-- The amount of horizontal padding that appears around the show/hide
+       password button.  Vertical padding is provided by the adjacent input
+       field. -->
+  <dimen name="fxaccount_show_password_padding_horizontal">8dp</dimen>
+
   <!-- Preference fragment padding, bottom -->
   <dimen name="preference_fragment_padding_bottom">0dp</dimen>
   <!-- Preference fragment padding, sides -->
   <dimen name="preference_fragment_padding_side">16dp</dimen>
 
   <integer name="preference_fragment_scrollbarStyle">0x02000000</integer> <!-- outsideOverlay -->
 </resources>
--- a/mobile/android/base/resources/values/fxaccount_styles.xml
+++ b/mobile/android/base/resources/values/fxaccount_styles.xml
@@ -125,9 +125,19 @@
 
     <style name="FxAccountCheckBox">
         <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginBottom">10dp</item>
         <item name="android:textColor">@drawable/fxaccount_checkbox_textcolor</item>
     </style>
 
+    <style name="FxAccountShowHidePasswordButton" parent="@android:style/Widget.Button">
+        <item name="android:background">@drawable/fxaccount_password_button_show_background</item>
+        <item name="android:minHeight">0dp</item>
+        <item name="android:minWidth">0dp</item>
+        <item name="android:paddingLeft">@dimen/fxaccount_show_password_padding_horizontal</item>
+        <item name="android:paddingRight">@dimen/fxaccount_show_password_padding_horizontal</item>
+        <item name="android:textColor">@color/fxaccount_input_textColor</item>
+        <item name="android:textSize">20sp</item>
+    </style>
+
 </resources>
\ No newline at end of file
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -232,16 +232,17 @@
   <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="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>
   <string name="site_settings_no_settings">&site_settings_no_settings;</string>
 
   <string name="reading_list_added">&reading_list_added;</string>
   <string name="reading_list_removed">&reading_list_removed;</string>
--- a/mobile/android/base/sync/GlobalSession.java
+++ b/mobile/android/base/sync/GlobalSession.java
@@ -52,20 +52,19 @@ import org.mozilla.gecko.sync.stage.Form
 import org.mozilla.gecko.sync.stage.GlobalSyncStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 import org.mozilla.gecko.sync.stage.NoSuchStageException;
 import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage;
 import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
 import org.mozilla.gecko.sync.stage.UploadMetaGlobalStage;
 
 import android.content.Context;
-import android.content.SharedPreferences;
 import ch.boye.httpclientandroidlib.HttpResponse;
 
-public class GlobalSession implements PrefsSource, HttpResponseObserver {
+public class GlobalSession implements HttpResponseObserver {
   private static final String LOG_TAG = "GlobalSession";
 
   public static final long STORAGE_VERSION = 5;
 
   public SyncConfiguration config = null;
 
   protected Map<Stage, GlobalSyncStage> stages;
   public Stage currentState = Stage.idle;
@@ -272,24 +271,16 @@ public class GlobalSession implements Pr
       nextStage.execute(this);
     } catch (Exception ex) {
       Logger.warn(LOG_TAG, "Caught exception " + ex + " running stage " + next);
       this.abort(ex, "Uncaught exception in stage.");
       return;
     }
   }
 
-  /*
-   * PrefsSource methods.
-   */
-  @Override
-  public SharedPreferences getPrefs(String name, int mode) {
-    return this.getContext().getSharedPreferences(name, mode);
-  }
-
   public Context getContext() {
     return this.context;
   }
 
   /**
    * Begin a sync.
    * <p>
    * The caller is responsible for:
deleted file mode 100644
--- a/mobile/android/base/sync/PrefsSource.java
+++ /dev/null
@@ -1,30 +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/. */
-
-package org.mozilla.gecko.sync;
-
-import android.content.SharedPreferences;
-
-/**
- * Implement PrefsSource to allow other components to fetch a SharedPreferences
- * instance via a Context that you provide.
- *
- * This allows components to use SharedPreferences without being tightly
- * coupled to an Activity.
- *
- * @author rnewman
- *
- */
-public interface PrefsSource {
-  /**
-   * Return a SharedPreferences instance.
-   * @param name
-   *        A String, used to identify a preferences 'branch'. Must not be null.
-   * @param mode
-   *        A bitmask mode, as described in http://developer.android.com/reference/android/content/Context.html#getSharedPreferences%28java.lang.String,%20int%29.
-   * @return
-   *        A new or existing SharedPreferences instance.
-   */
-  public SharedPreferences getPrefs(String name, int mode);
-}
--- a/mobile/android/base/sync/SyncConfiguration.java
+++ b/mobile/android/base/sync/SyncConfiguration.java
@@ -256,24 +256,16 @@ public class SyncConfiguration {
   public static final String PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale";
 
   public static final String PREF_ACCOUNT_GUID = "account.guid";
   public static final String PREF_CLIENT_NAME = "account.clientName";
   public static final String PREF_NUM_CLIENTS = "account.numClients";
 
   private static final String API_VERSION = "1.5";
 
-  /**
-   * Create a new SyncConfiguration instance. Pass in a PrefsSource to
-   * provide access to preferences.
-   */
-  public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, String prefsPath, PrefsSource prefsSource) {
-    this(username, authHeaderProvider, prefsSource.getPrefs(prefsPath, Utils.SHARED_PREFERENCES_MODE));
-  }
-
   public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs) {
     this.username = username;
     this.authHeaderProvider = authHeaderProvider;
     this.prefs = prefs;
     this.loadFromPrefs(prefs);
   }
 
   public SyncConfiguration(String username, AuthHeaderProvider authHeaderProvider, SharedPreferences prefs, KeyBundle syncKeyBundle) {
--- a/mobile/android/base/sync/net/HawkAuthHeaderProvider.java
+++ b/mobile/android/base/sync/net/HawkAuthHeaderProvider.java
@@ -13,16 +13,17 @@ import java.security.InvalidKeyException
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Locale;
 
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 
 import org.mozilla.apache.commons.codec.binary.Base64;
+import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.Utils;
 
 import ch.boye.httpclientandroidlib.Header;
 import ch.boye.httpclientandroidlib.HttpEntity;
 import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest;
 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
 import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
@@ -50,24 +51,30 @@ public class HawkAuthHeaderProvider impl
   protected final boolean includePayloadHash;
   protected final long skewSeconds;
 
   /**
    * Create a Hawk Authorization header provider.
    * <p>
    * Hawk specifies no mechanism by which a client receives an
    * identifier-and-key pair from the server.
+   * <p>
+   * Hawk requests can include a payload verification hash with requests that
+   * enclose an entity (PATCH, POST, and PUT requests).  <b>You should default
+   * to including the payload verification hash<b> unless you have a good reason
+   * not to -- the server can always ignore payload verification hashes provided
+   * by the client.
    *
    * @param id
    *          to name requests with.
    * @param key
    *          to sign request with.
    *
    * @param includePayloadHash
-   *          true if message integrity hash should be included in signed
+   *          true if payload verification hash should be included in signed
    *          request header. See <a href="https://github.com/hueniverse/hawk#payload-validation">https://github.com/hueniverse/hawk#payload-validation</a>.
    *
    * @param skewSeconds
    *          a number of seconds by which to skew the current time when
    *          computing a header.
    */
   public HawkAuthHeaderProvider(String id, byte[] key, boolean includePayloadHash, long skewSeconds) {
     if (id == null) {
@@ -77,21 +84,16 @@ public class HawkAuthHeaderProvider impl
       throw new IllegalArgumentException("key must not be null");
     }
     this.id = id;
     this.key = key;
     this.includePayloadHash = includePayloadHash;
     this.skewSeconds = skewSeconds;
   }
 
-  public HawkAuthHeaderProvider(String id, byte[] key, boolean includePayloadHash) {
-    this(id, key, includePayloadHash, 0L);
-  }
-
-
   /**
    * @return the current time in milliseconds.
    */
   @SuppressWarnings("static-method")
   protected long now() {
     return System.currentTimeMillis();
   }
 
@@ -136,24 +138,19 @@ public class HawkAuthHeaderProvider impl
       throw new IllegalArgumentException("nonce must not be null.");
     }
     if (nonce.length() == 0) {
       throw new IllegalArgumentException("nonce must not be empty.");
     }
 
     String payloadHash = null;
     if (includePayloadHash) {
-      if (!(request instanceof HttpEntityEnclosingRequest)) {
-        throw new IllegalArgumentException("cannot specify payload for request without an entity");
-      }
-      HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
-      if (entity == null) {
-        throw new IllegalArgumentException("cannot specify payload for request with a null entity");
-      }
-      payloadHash = Base64.encodeBase64String(getPayloadHash(entity));
+      payloadHash = getPayloadHashString(request);
+    } else {
+      Logger.debug(LOG_TAG, "Configured to not include payload hash for this request.");
     }
 
     String app = null;
     String dlg = null;
     String requestString = getRequestString(request, "header", timestamp, nonce, payloadHash, extra, app, dlg);
     String macString = getSignature(requestString.getBytes("UTF-8"), this.key);
 
     StringBuilder sb = new StringBuilder();
@@ -179,16 +176,48 @@ public class HawkAuthHeaderProvider impl
     sb.append("mac=\"");
     sb.append(macString);
     sb.append("\"");
 
     return new BasicHeader("Authorization", sb.toString());
   }
 
   /**
+   * Get the payload verification hash for the given request, if possible.
+   * <p>
+   * Returns null if the request does not enclose an entity (is not an HTTP
+   * PATCH, POST, or PUT). Throws if the payload verification hash cannot be
+   * computed.
+   *
+   * @param request
+   *          to compute hash for.
+   * @return verification hash, or null if the request does not enclose an entity.
+   * @throws IllegalArgumentException if the request does not enclose a valid non-null entity.
+   * @throws UnsupportedEncodingException
+   * @throws NoSuchAlgorithmException
+   * @throws IOException
+   */
+  protected static String getPayloadHashString(HttpRequestBase request)
+      throws UnsupportedEncodingException, NoSuchAlgorithmException, IOException, IllegalArgumentException {
+    final boolean shouldComputePayloadHash = request instanceof HttpEntityEnclosingRequest;
+    if (!shouldComputePayloadHash) {
+      Logger.debug(LOG_TAG, "Not computing payload verification hash for non-enclosing request.");
+      return null;
+    }
+    if (!(request instanceof HttpEntityEnclosingRequest)) {
+      throw new IllegalArgumentException("Cannot compute payload verification hash for enclosing request without an entity");
+    }
+    final HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
+    if (entity == null) {
+      throw new IllegalArgumentException("Cannot compute payload verification hash for enclosing request with a null entity");
+    }
+    return Base64.encodeBase64String(getPayloadHash(entity));
+  }
+
+  /**
    * Escape the user-provided extra string for the ext="" header attribute.
    * <p>
    * Hawk escapes the header ext="" attribute differently than it does the extra
    * line in the normalized request string.
    * <p>
    * See <a href="https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/browser.js#L385">https://github.com/hueniverse/hawk/blob/871cc597973110900467bd3dfb84a3c892f678fb/lib/browser.js#L385</a>.
    *
    * @param extra to escape.
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -29,16 +29,17 @@ import org.mozilla.gecko.toolbar.Toolbar
 import org.mozilla.gecko.toolbar.ToolbarDisplayLayout.UpdateFlags;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.MenuUtils;
 import org.mozilla.gecko.widget.ThemedImageButton;
 import org.mozilla.gecko.widget.ThemedImageView;
 import org.mozilla.gecko.widget.ThemedRelativeLayout;
+import org.mozilla.gecko.widget.ThemedView;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.StateListDrawable;
 import android.os.Build;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -52,16 +53,17 @@ import android.view.View;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
+import android.widget.RelativeLayout;
 
 /**
 * {@code BrowserToolbar} is single entry point for users of the toolbar
 * subsystem i.e. this should be the only import outside the 'toolbar'
 * package.
 *
 * {@code BrowserToolbar} serves at the single event bus for all
 * sub-components in the toolbar. It tracks tab events and gecko messages
@@ -110,149 +112,159 @@ public class BrowserToolbar extends Them
         DISPLAY
     }
 
     enum ForwardButtonAnimation {
         SHOW,
         HIDE
     }
 
-    private ToolbarDisplayLayout mUrlDisplayLayout;
-    private ToolbarEditLayout mUrlEditLayout;
-    private View mUrlBarEntry;
-    private ImageView mUrlBarRightEdge;
-    private boolean mSwitchingTabs;
-    private ShapedButton mTabs;
-    private ImageButton mBack;
-    private ImageButton mForward;
+    private ToolbarDisplayLayout urlDisplayLayout;
+    private ToolbarEditLayout urlEditLayout;
+    private View urlBarEntry;
+    private RelativeLayout.LayoutParams urlBarEntryDefaultLayoutParams;
+    private RelativeLayout.LayoutParams urlBarEntryShrunkenLayoutParams;
+    private ImageView urlBarTranslatingEdge;
+    private boolean isSwitchingTabs;
+    private ShapedButton tabsButton;
+    private ImageButton backButton;
+    private ImageButton forwardButton;
 
-    private ToolbarProgressView mProgressBar;
-    private TabCounter mTabsCounter;
-    private ThemedImageButton mMenu;
-    private ThemedImageView mMenuIcon;
-    private LinearLayout mActionItemBar;
-    private MenuPopup mMenuPopup;
-    private List<View> mFocusOrder;
+    private ToolbarProgressView progressBar;
+    private TabCounter tabsCounter;
+    private ThemedImageButton menuButton;
+    private ThemedImageView menuIcon;
+    private LinearLayout actionItemBar;
+    private MenuPopup menuPopup;
+    private List<View> focusOrder;
 
-    private OnActivateListener mActivateListener;
-    private OnCommitListener mCommitListener;
-    private OnDismissListener mDismissListener;
-    private OnFilterListener mFilterListener;
-    private OnFocusChangeListener mFocusChangeListener;
-    private OnStartEditingListener mStartEditingListener;
-    private OnStopEditingListener mStopEditingListener;
+    private final ThemedView editSeparator;
+    private final View editCancel;
+
+    private boolean shouldShrinkURLBar = false;
+
+    private OnActivateListener activateListener;
+    private OnFocusChangeListener focusChangeListener;
+    private OnStartEditingListener startEditingListener;
+    private OnStopEditingListener stopEditingListener;
 
-    final private BrowserApp mActivity;
-    private boolean mHasSoftMenuButton;
+    final private BrowserApp activity;
+    private boolean hasSoftMenuButton;
 
-    private UIMode mUIMode;
-    private boolean mAnimatingEntry;
+    private UIMode uiMode;
+    private boolean isAnimatingEntry;
 
-    private int mUrlBarViewOffset;
-    private int mDefaultForwardMargin;
+    private int urlBarViewOffset;
+    private int defaultForwardMargin;
 
-    private static final Interpolator sButtonsInterpolator = new AccelerateInterpolator();
+    private static final Interpolator buttonsInterpolator = new AccelerateInterpolator();
 
     private static final int FORWARD_ANIMATION_DURATION = 450;
 
-    private final LightweightTheme mTheme;
+    private final LightweightTheme theme;
 
     public BrowserToolbar(Context context) {
         this(context, null);
     }
 
     public BrowserToolbar(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
+        theme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
 
         // BrowserToolbar is attached to BrowserApp only.
-        mActivity = (BrowserApp) context;
+        activity = (BrowserApp) context;
 
         // Inflate the content.
         LayoutInflater.from(context).inflate(R.layout.browser_toolbar, this);
 
         Tabs.registerOnTabsChangedListener(this);
-        mSwitchingTabs = true;
-        mAnimatingEntry = false;
+        isSwitchingTabs = true;
+        isAnimatingEntry = false;
 
         registerEventListener("Reader:Click");
         registerEventListener("Reader:LongClick");
 
-        mAnimatingEntry = false;
+        final Resources res = getResources();
+        urlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left);
+        defaultForwardMargin = res.getDimensionPixelSize(R.dimen.forward_default_offset);
+        urlDisplayLayout = (ToolbarDisplayLayout) findViewById(R.id.display_layout);
+        urlBarEntry = findViewById(R.id.url_bar_entry);
+        urlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
 
-        final Resources res = getResources();
-        mUrlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left);
-        mDefaultForwardMargin = res.getDimensionPixelSize(R.dimen.forward_default_offset);
-        mUrlDisplayLayout = (ToolbarDisplayLayout) findViewById(R.id.display_layout);
-        mUrlBarEntry = findViewById(R.id.url_bar_entry);
-        mUrlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
+        urlBarEntryDefaultLayoutParams = (RelativeLayout.LayoutParams) urlBarEntry.getLayoutParams();
+        urlBarEntryShrunkenLayoutParams = new RelativeLayout.LayoutParams(urlBarEntryDefaultLayoutParams);
+        urlBarEntryShrunkenLayoutParams.addRule(RelativeLayout.ALIGN_RIGHT, R.id.edit_layout);
+        urlBarEntryShrunkenLayoutParams.rightMargin = 0;
 
-        // This will clip the right edge's image at 60% of its width
-        mUrlBarRightEdge = (ImageView) findViewById(R.id.url_bar_right_edge);
-        if (mUrlBarRightEdge != null) {
-            mUrlBarRightEdge.getDrawable().setLevel(6000);
+        // This will clip the translating edge's image at 60% of its width
+        urlBarTranslatingEdge = (ImageView) findViewById(R.id.url_bar_translating_edge);
+        if (urlBarTranslatingEdge != null) {
+            urlBarTranslatingEdge.getDrawable().setLevel(6000);
         }
 
-        mTabs = (ShapedButton) findViewById(R.id.tabs);
-        mTabsCounter = (TabCounter) findViewById(R.id.tabs_counter);
+        tabsButton = (ShapedButton) findViewById(R.id.tabs);
+        tabsCounter = (TabCounter) findViewById(R.id.tabs_counter);
         if (Build.VERSION.SDK_INT >= 11) {
-            mTabsCounter.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+            tabsCounter.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
         }
 
-        mBack = (ImageButton) findViewById(R.id.back);
-        setButtonEnabled(mBack, false);
-        mForward = (ImageButton) findViewById(R.id.forward);
-        setButtonEnabled(mForward, false);
+        backButton = (ImageButton) findViewById(R.id.back);
+        setButtonEnabled(backButton, false);
+        forwardButton = (ImageButton) findViewById(R.id.forward);
+        setButtonEnabled(forwardButton, false);
 
-        mMenu = (ThemedImageButton) findViewById(R.id.menu);
-        mMenuIcon = (ThemedImageView) findViewById(R.id.menu_icon);
-        mActionItemBar = (LinearLayout) findViewById(R.id.menu_items);
-        mHasSoftMenuButton = !HardwareUtils.hasMenuButton();
+        menuButton = (ThemedImageButton) findViewById(R.id.menu);
+        menuIcon = (ThemedImageView) findViewById(R.id.menu_icon);
+        actionItemBar = (LinearLayout) findViewById(R.id.menu_items);
+        hasSoftMenuButton = !HardwareUtils.hasMenuButton();
+
+        editSeparator = (ThemedView) findViewById(R.id.edit_separator);
+        editCancel = findViewById(R.id.edit_cancel);
 
         // We use different layouts on phones and tablets, so adjust the focus
         // order appropriately.
-        mFocusOrder = new ArrayList<View>();
+        focusOrder = new ArrayList<View>();
         if (HardwareUtils.isTablet()) {
-            mFocusOrder.addAll(Arrays.asList(mTabs, mBack, mForward, this));
-            mFocusOrder.addAll(mUrlDisplayLayout.getFocusOrder());
-            mFocusOrder.addAll(Arrays.asList(mActionItemBar, mMenu));
+            focusOrder.addAll(Arrays.asList(tabsButton, backButton, forwardButton, this));
+            focusOrder.addAll(urlDisplayLayout.getFocusOrder());
+            focusOrder.addAll(Arrays.asList(actionItemBar, menuButton));
         } else {
-            mFocusOrder.add(this);
-            mFocusOrder.addAll(mUrlDisplayLayout.getFocusOrder());
-            mFocusOrder.addAll(Arrays.asList(mTabs, mMenu));
+            focusOrder.add(this);
+            focusOrder.addAll(urlDisplayLayout.getFocusOrder());
+            focusOrder.addAll(Arrays.asList(tabsButton, menuButton));
         }
 
         setUIMode(UIMode.DISPLAY);
     }
 
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
 
         setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
-                if (mActivateListener != null) {
-                    mActivateListener.onActivate();
+                if (activateListener != null) {
+                    activateListener.onActivate();
                 }
             }
         });
 
         setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
             @Override
             public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
                 // We don't the context menu while editing
                 if (isEditing()) {
                     return;
                 }
 
                 // NOTE: Use MenuUtils.safeSetVisible because some actions might
                 // be on the Page menu
 
-                MenuInflater inflater = mActivity.getMenuInflater();
+                MenuInflater inflater = activity.getMenuInflater();
                 inflater.inflate(R.menu.titlebar_contextmenu, menu);
 
                 String clipboard = Clipboard.getText();
                 if (TextUtils.isEmpty(clipboard)) {
                     menu.findItem(R.id.pasteandgo).setVisible(false);
                     menu.findItem(R.id.paste).setVisible(false);
                 }
 
@@ -275,117 +287,126 @@ public class BrowserToolbar extends Them
                     MenuUtils.safeSetVisible(menu, R.id.subscribe, false);
                     MenuUtils.safeSetVisible(menu, R.id.add_search_engine, false);
                 }
 
                 MenuUtils.safeSetVisible(menu, R.id.share, !GeckoProfile.get(getContext()).inGuestMode());
             }
         });
 
-        mUrlDisplayLayout.setOnStopListener(new OnStopListener() {
+        urlDisplayLayout.setOnStopListener(new OnStopListener() {
             @Override
             public Tab onStop() {
                 final Tab tab = Tabs.getInstance().getSelectedTab();
                 if (tab != null) {
                     tab.doStop();
                     return tab;
                 }
 
                 return null;
             }
         });
 
-        mUrlDisplayLayout.setOnTitleChangeListener(new OnTitleChangeListener() {
+        urlDisplayLayout.setOnTitleChangeListener(new OnTitleChangeListener() {
             @Override
             public void onTitleChange(CharSequence title) {
                 final String contentDescription;
                 if (title != null) {
                     contentDescription = title.toString();
                 } else {
-                    contentDescription = mActivity.getString(R.string.url_bar_default_text);
+                    contentDescription = activity.getString(R.string.url_bar_default_text);
                 }
 
                 // The title and content description should
                 // always be sync.
                 setContentDescription(contentDescription);
             }
         });
 
-        mUrlEditLayout.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+        urlEditLayout.setOnFocusChangeListener(new View.OnFocusChangeListener() {
             @Override
             public void onFocusChange(View v, boolean hasFocus) {
                 setSelected(hasFocus);
-                if (mFocusChangeListener != null) {
-                    mFocusChangeListener.onFocusChange(v, hasFocus);
+                if (focusChangeListener != null) {
+                    focusChangeListener.onFocusChange(v, hasFocus);
                 }
             }
         });
 
-        mTabs.setOnClickListener(new Button.OnClickListener() {
+        tabsButton.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
                 toggleTabs();
             }
         });
-        mTabs.setImageLevel(0);
+        tabsButton.setImageLevel(0);
 
-        mBack.setOnClickListener(new Button.OnClickListener() {
+        backButton.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View view) {
                 Tabs.getInstance().getSelectedTab().doBack();
             }
         });
-        mBack.setOnLongClickListener(new Button.OnLongClickListener() {
+        backButton.setOnLongClickListener(new Button.OnLongClickListener() {
             @Override
             public boolean onLongClick(View view) {
                 return Tabs.getInstance().getSelectedTab().showBackHistory();
             }
         });
 
-        mForward.setOnClickListener(new Button.OnClickListener() {
+        forwardButton.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View view) {
                 Tabs.getInstance().getSelectedTab().doForward();
             }
         });
-        mForward.setOnLongClickListener(new Button.OnLongClickListener() {
+        forwardButton.setOnLongClickListener(new Button.OnLongClickListener() {
             @Override
             public boolean onLongClick(View view) {
                 return Tabs.getInstance().getSelectedTab().showForwardHistory();
             }
         });
 
-        if (mHasSoftMenuButton) {
-            mMenu.setVisibility(View.VISIBLE);
-            mMenuIcon.setVisibility(View.VISIBLE);
+        if (editCancel != null) {
+            editCancel.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    cancelEdit();
+                }
+            });
+        }
 
-            mMenu.setOnClickListener(new Button.OnClickListener() {
+        if (hasSoftMenuButton) {
+            menuButton.setVisibility(View.VISIBLE);
+            menuIcon.setVisibility(View.VISIBLE);
+
+            menuButton.setOnClickListener(new Button.OnClickListener() {
                 @Override
                 public void onClick(View view) {
-                    mActivity.openOptionsMenu();
+                    activity.openOptionsMenu();
                 }
             });
         }
     }
 
     public void setProgressBar(ToolbarProgressView progressBar) {
-        mProgressBar = progressBar;
+        this.progressBar = progressBar;
     }
 
     public void refresh() {
-        mUrlDisplayLayout.dismissSiteIdentityPopup();
+        urlDisplayLayout.dismissSiteIdentityPopup();
     }
 
     public boolean onBackPressed() {
         if (isEditing()) {
             stopEditing();
             return true;
         }
 
-        return mUrlDisplayLayout.dismissSiteIdentityPopup();
+        return urlDisplayLayout.dismissSiteIdentityPopup();
     }
 
     public boolean onKey(int keyCode, KeyEvent event) {
         if (event.getAction() != KeyEvent.ACTION_DOWN) {
             return false;
         }
 
         // Galaxy Note sends key events for the stylus that are outside of the
@@ -403,17 +424,17 @@ public class BrowserToolbar extends Them
             keyCode == KeyEvent.KEYCODE_DPAD_LEFT ||
             keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ||
             keyCode == KeyEvent.KEYCODE_DPAD_CENTER ||
             keyCode == KeyEvent.KEYCODE_DEL ||
             keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
             keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
             return false;
         } else if (isEditing()) {
-            return mUrlEditLayout.onKey(keyCode, event);
+            return urlEditLayout.onKey(keyCode, event);
         }
 
         return false;
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         // If the motion event has occured below the toolbar (due to the scroll
@@ -430,17 +451,17 @@ public class BrowserToolbar extends Them
         super.onSizeChanged(w, h, oldw, oldh);
 
         if (h != oldh) {
             // Post this to happen outside of onSizeChanged, as this may cause
             // a layout change and relayouts within a layout change don't work.
             post(new Runnable() {
                 @Override
                 public void run() {
-                    mActivity.refreshToolbarHeight();
+                    activity.refreshToolbarHeight();
                 }
             });
         }
     }
 
     @Override
     public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
         Log.d(LOGTAG, "onTabChanged: " + msg);
@@ -454,19 +475,19 @@ public class BrowserToolbar extends Them
         switch (msg) {
             case ADDED:
             case CLOSED:
                 updateTabCount(tabs.getDisplayCount());
                 break;
             case RESTORED:
                 // TabCount fixup after OOM
             case SELECTED:
-                mUrlDisplayLayout.dismissSiteIdentityPopup();
+                urlDisplayLayout.dismissSiteIdentityPopup();
                 updateTabCount(tabs.getDisplayCount());
-                mSwitchingTabs = true;
+                isSwitchingTabs = true;
                 break;
         }
 
         if (tabs.isSelectedTab(tab)) {
             final EnumSet<UpdateFlags> flags = EnumSet.noneOf(UpdateFlags.class);
 
             // Progress-related handling
             switch (msg) {
@@ -474,18 +495,18 @@ public class BrowserToolbar extends Them
                     updateProgressVisibility(tab, Tab.LOAD_PROGRESS_INIT);
                     // Fall through.
                 case ADDED:
                 case LOCATION_CHANGE:
                 case LOAD_ERROR:
                 case LOADED:
                 case STOP:
                     flags.add(UpdateFlags.PROGRESS);
-                    if (mProgressBar.getVisibility() == View.VISIBLE) {
-                        mProgressBar.animateProgress(tab.getLoadProgress());
+                    if (progressBar.getVisibility() == View.VISIBLE) {
+                        progressBar.animateProgress(tab.getLoadProgress());
                     }
                     break;
 
                 case SELECTED:
                     flags.add(UpdateFlags.PROGRESS);
                     updateProgressVisibility();
                     break;
             }
@@ -537,167 +558,175 @@ public class BrowserToolbar extends Them
                 updateDisplayLayout(tab, flags);
             }
         }
 
         switch (msg) {
             case SELECTED:
             case LOAD_ERROR:
             case LOCATION_CHANGE:
-                mSwitchingTabs = false;
+                isSwitchingTabs = false;
         }
     }
 
     private void updateProgressVisibility() {
         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
         updateProgressVisibility(selectedTab, selectedTab.getLoadProgress());
     }
 
     private void updateProgressVisibility(Tab selectedTab, int progress) {
         if (!isEditing() && selectedTab.getState() == Tab.STATE_LOADING) {
-            mProgressBar.setProgress(progress);
-            mProgressBar.setVisibility(View.VISIBLE);
+            progressBar.setProgress(progress);
+            progressBar.setVisibility(View.VISIBLE);
         } else {
-            mProgressBar.setVisibility(View.GONE);
+            progressBar.setVisibility(View.GONE);
         }
     }
 
     public boolean isVisible() {
         return ViewHelper.getTranslationY(this) == 0;
     }
 
     @Override
     public void setNextFocusDownId(int nextId) {
         super.setNextFocusDownId(nextId);
-        mTabs.setNextFocusDownId(nextId);
-        mBack.setNextFocusDownId(nextId);
-        mForward.setNextFocusDownId(nextId);
-        mUrlDisplayLayout.setNextFocusDownId(nextId);
-        mMenu.setNextFocusDownId(nextId);
+        tabsButton.setNextFocusDownId(nextId);
+        backButton.setNextFocusDownId(nextId);
+        forwardButton.setNextFocusDownId(nextId);
+        urlDisplayLayout.setNextFocusDownId(nextId);
+        menuButton.setNextFocusDownId(nextId);
     }
 
     private int getUrlBarEntryTranslation() {
-        return getWidth() - mUrlBarEntry.getRight();
+        if (editSeparator == null) {
+            // We are on tablet, and there is no animation so return a translation of 0.
+            return 0;
+        }
+
+        // We would ideally use the right-most point of the edit layout instead of the
+        // edit separator and its margin, but it is not inflated when this code initially runs.
+        final LayoutParams lp = (LayoutParams) editSeparator.getLayoutParams();
+        return editSeparator.getLeft() - lp.leftMargin - urlBarEntry.getRight();
     }
 
     private int getUrlBarCurveTranslation() {
-        return getWidth() - mTabs.getLeft();
+        return getWidth() - tabsButton.getLeft();
     }
 
     private boolean canDoBack(Tab tab) {
         return (tab.canDoBack() && !isEditing());
     }
 
     private boolean canDoForward(Tab tab) {
         return (tab.canDoForward() && !isEditing());
     }
 
     private void addTab() {
-        mActivity.addTab();
+        activity.addTab();
     }
 
     private void toggleTabs() {
-        if (mActivity.areTabsShown()) {
-            if (mActivity.hasTabsSideBar())
-                mActivity.hideTabs();
+        if (activity.areTabsShown()) {
+            if (activity.hasTabsSideBar())
+                activity.hideTabs();
         } else {
             // hide the virtual keyboard
             InputMethodManager imm =
-                    (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
-            imm.hideSoftInputFromWindow(mTabs.getWindowToken(), 0);
+                    (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+            imm.hideSoftInputFromWindow(tabsButton.getWindowToken(), 0);
 
             Tab tab = Tabs.getInstance().getSelectedTab();
             if (tab != null) {
                 if (!tab.isPrivate())
-                    mActivity.showNormalTabs();
+                    activity.showNormalTabs();
                 else
-                    mActivity.showPrivateTabs();
+                    activity.showPrivateTabs();
             }
         }
     }
 
     private void updateTabCountAndAnimate(int count) {
         // Don't animate if the toolbar is hidden.
         if (!isVisible()) {
             updateTabCount(count);
             return;
         }
 
         // If toolbar is in edit mode on a phone, this means the entry is expanded
         // and the tabs button is translated offscreen. Don't trigger tabs counter
         // updates until the tabs button is back on screen.
         // See stopEditing()
         if (!isEditing() || HardwareUtils.isTablet()) {
-            mTabsCounter.setCount(count);
+            tabsCounter.setCount(count);
 
-            mTabs.setContentDescription((count > 1) ?
-                                        mActivity.getString(R.string.num_tabs, count) :
-                                        mActivity.getString(R.string.one_tab));
+            tabsButton.setContentDescription((count > 1) ?
+                                             activity.getString(R.string.num_tabs, count) :
+                                             activity.getString(R.string.one_tab));
         }
     }
 
     private void updateTabCount(int count) {
         // If toolbar is in edit mode on a phone, this means the entry is expanded
         // and the tabs button is translated offscreen. Don't trigger tabs counter
         // updates until the tabs button is back on screen.
         // See stopEditing()
         if (isEditing() && !HardwareUtils.isTablet()) {
             return;
         }
 
         // Set TabCounter based on visibility
-        if (isVisible() && ViewHelper.getAlpha(mTabsCounter) != 0 && !isEditing()) {
-            mTabsCounter.setCountWithAnimation(count);
+        if (isVisible() && ViewHelper.getAlpha(tabsCounter) != 0 && !isEditing()) {
+            tabsCounter.setCountWithAnimation(count);
         } else {
-            mTabsCounter.setCount(count);
+            tabsCounter.setCount(count);
         }
 
         // Update A11y information
-        mTabs.setContentDescription((count > 1) ?
-                                    mActivity.getString(R.string.num_tabs, count) :
-                                    mActivity.getString(R.string.one_tab));
+        tabsButton.setContentDescription((count > 1) ?
+                                         activity.getString(R.string.num_tabs, count) :
+                                         activity.getString(R.string.one_tab));
     }
 
     private void updateDisplayLayout(Tab tab, EnumSet<UpdateFlags> flags) {
-        if (mSwitchingTabs) {
+        if (isSwitchingTabs) {
             flags.add(UpdateFlags.DISABLE_ANIMATIONS);
         }
 
-        mUrlDisplayLayout.updateFromTab(tab, flags);
+        urlDisplayLayout.updateFromTab(tab, flags);
 
         if (flags.contains(UpdateFlags.TITLE)) {
             if (!isEditing()) {
-                mUrlEditLayout.setText(tab.getURL());
+                urlEditLayout.setText(tab.getURL());
             }
         }
 
         if (flags.contains(UpdateFlags.PROGRESS)) {
             updateFocusOrder();
         }
     }
 
     private void updateFocusOrder() {
         View prevView = null;
 
         // If the element that has focus becomes disabled or invisible, focus
         // is given to the URL bar.
         boolean needsNewFocus = false;
 
-        for (View view : mFocusOrder) {
+        for (View view : focusOrder) {
             if (view.getVisibility() != View.VISIBLE || !view.isEnabled()) {
                 if (view.hasFocus()) {
                     needsNewFocus = true;
                 }
                 continue;
             }
 
-            if (view == mActionItemBar) {
-                final int childCount = mActionItemBar.getChildCount();
+            if (view == actionItemBar) {
+                final int childCount = actionItemBar.getChildCount();
                 for (int child = 0; child < childCount; child++) {
-                    View childView = mActionItemBar.getChildAt(child);
+                    View childView = actionItemBar.getChildAt(child);
                     if (prevView != null) {
                         childView.setNextFocusLeftId(prevView.getId());
                         prevView.setNextFocusRightId(childView.getId());
                     }
                     prevView = childView;
                 }
             } else {
                 if (prevView != null) {
@@ -713,99 +742,96 @@ public class BrowserToolbar extends Them
         }
     }
 
     public void onEditSuggestion(String suggestion) {
         if (!isEditing()) {
             return;
         }
 
-        mUrlEditLayout.onEditSuggestion(suggestion);
+        urlEditLayout.onEditSuggestion(suggestion);
     }
 
     public void setTitle(CharSequence title) {
-        mUrlDisplayLayout.setTitle(title);
+        urlDisplayLayout.setTitle(title);
     }
 
     public void prepareTabsAnimation(PropertyAnimator animator, boolean tabsAreShown) {
         if (!tabsAreShown) {
             PropertyAnimator buttonsAnimator =
-                    new PropertyAnimator(animator.getDuration(), sButtonsInterpolator);
+                    new PropertyAnimator(animator.getDuration(), buttonsInterpolator);
 
-            buttonsAnimator.attach(mTabsCounter,
+            buttonsAnimator.attach(tabsCounter,
                                    PropertyAnimator.Property.ALPHA,
                                    1.0f);
 
-            if (mHasSoftMenuButton && !HardwareUtils.isTablet()) {
-                buttonsAnimator.attach(mMenuIcon,
+            if (hasSoftMenuButton && !HardwareUtils.isTablet()) {
+                buttonsAnimator.attach(menuIcon,
                                        PropertyAnimator.Property.ALPHA,
                                        1.0f);
             }
 
             buttonsAnimator.start();
 
             return;
         }
 
-        ViewHelper.setAlpha(mTabsCounter, 0.0f);
+        ViewHelper.setAlpha(tabsCounter, 0.0f);
 
-        if (mHasSoftMenuButton && !HardwareUtils.isTablet()) {
-            ViewHelper.setAlpha(mMenuIcon, 0.0f);
+        if (hasSoftMenuButton && !HardwareUtils.isTablet()) {
+            ViewHelper.setAlpha(menuIcon, 0.0f);
         }
     }
 
     public void finishTabsAnimation(boolean tabsAreShown) {
         if (tabsAreShown) {
             return;
         }
 
         PropertyAnimator animator = new PropertyAnimator(150);
 
-        animator.attach(mTabsCounter,
+        animator.attach(tabsCounter,
                         PropertyAnimator.Property.ALPHA,
                         1.0f);
 
-        if (mHasSoftMenuButton && !HardwareUtils.isTablet()) {
-            animator.attach(mMenuIcon,
+        if (hasSoftMenuButton && !HardwareUtils.isTablet()) {
+            animator.attach(menuIcon,
                             PropertyAnimator.Property.ALPHA,
                             1.0f);
         }
 
         animator.start();
     }
 
     public void setOnActivateListener(OnActivateListener listener) {
-        mActivateListener = listener;
+        activateListener = listener;
     }
 
     public void setOnCommitListener(OnCommitListener listener) {
-        mCommitListener = listener;
-        mUrlEditLayout.setOnCommitListener(listener);
+        urlEditLayout.setOnCommitListener(listener);
     }
 
     public void setOnDismissListener(OnDismissListener listener) {
-        mDismissListener = listener;
-        mUrlEditLayout.setOnDismissListener(listener);
+        urlEditLayout.setOnDismissListener(listener);
     }
 
     public void setOnFilterListener(OnFilterListener listener) {
-        mFilterListener = listener;
-        mUrlEditLayout.setOnFilterListener(listener);
+        urlEditLayout.setOnFilterListener(listener);
     }
 
     public void setOnFocusChangeListener(OnFocusChangeListener listener) {
-        mFocusChangeListener = listener;
+        focusChangeListener = listener;
     }
 
     public void setOnStartEditingListener(OnStartEditingListener listener) {
-        mStartEditingListener = listener;
+        startEditingListener = listener;
     }
 
     public void setOnStopEditingListener(OnStopEditingListener listener) {
-        mStopEditingListener = listener;
+        stopEditingListener = listener;
     }
 
     private void showUrlEditLayout() {
         setUrlEditLayoutVisibility(true, null);
     }
 
     private void showUrlEditLayout(PropertyAnimator animator) {
         setUrlEditLayoutVisibility(true, animator);
@@ -815,52 +841,66 @@ public class BrowserToolbar extends Them
         setUrlEditLayoutVisibility(false, null);
     }
 
     private void hideUrlEditLayout(PropertyAnimator animator) {
         setUrlEditLayoutVisibility(false, animator);
     }
 
     private void setUrlEditLayoutVisibility(final boolean showEditLayout, PropertyAnimator animator) {
-        final View viewToShow = (showEditLayout ? mUrlEditLayout : mUrlDisplayLayout);
-        final View viewToHide = (showEditLayout ? mUrlDisplayLayout : mUrlEditLayout);
+        urlEditLayout.prepareAnimation(showEditLayout, animator);
 
-        if (showEditLayout) {
-            mUrlEditLayout.prepareShowAnimation(animator);
-        }
+        final View viewToShow = (showEditLayout ? urlEditLayout : urlDisplayLayout);
+        final View viewToHide = (showEditLayout ? urlDisplayLayout : urlEditLayout);
 
         if (animator == null) {
             viewToHide.setVisibility(View.GONE);
             viewToShow.setVisibility(View.VISIBLE);
+
+            final int cancelVisibility = (showEditLayout ? View.VISIBLE : View.INVISIBLE);
+            setCancelVisibility(cancelVisibility);
             return;
         }
 
         ViewHelper.setAlpha(viewToShow, 0.0f);
         animator.attach(viewToShow,
                         PropertyAnimator.Property.ALPHA,
                         1.0f);
 
         animator.attach(viewToHide,
                         PropertyAnimator.Property.ALPHA,
                         0.0f);
 
         animator.addPropertyAnimationListener(new PropertyAnimationListener() {
             @Override
             public void onPropertyAnimationStart() {
                 viewToShow.setVisibility(View.VISIBLE);
+                if (!showEditLayout) {
+                    setCancelVisibility(View.INVISIBLE);
+                }
             }
 
             @Override
             public void onPropertyAnimationEnd() {
                 viewToHide.setVisibility(View.GONE);
                 ViewHelper.setAlpha(viewToHide, 1.0f);
+                if (showEditLayout) {
+                    setCancelVisibility(View.VISIBLE);
+                }
             }
         });
     }
 
+    private void setCancelVisibility(final int visibility) {
+        if (editSeparator != null && editCancel != null) {
+            editSeparator.setVisibility(visibility);
+            editCancel.setVisibility(visibility);
+        }
+    }
+
     /**
      * Disables and dims all toolbar elements which are not
      * related to editing mode.
      */
     private void updateChildrenForEditing() {
         // This is for the tablet UI only
         if (!HardwareUtils.isTablet()) {
             return;
@@ -869,154 +909,165 @@ public class BrowserToolbar extends Them
         // Disable toolbar elemens while in editing mode
         final boolean enabled = !isEditing();
 
         // This alpha value has to be in sync with the one used
         // in setButtonEnabled().
         final float alpha = (enabled ? 1.0f : 0.24f);
 
         if (!enabled) {
-            mTabsCounter.onEnterEditingMode();
+            tabsCounter.onEnterEditingMode();
         }
 
-        mTabs.setEnabled(enabled);
-        ViewHelper.setAlpha(mTabsCounter, alpha);
-        mMenu.setEnabled(enabled);
-        ViewHelper.setAlpha(mMenuIcon, alpha);
+        tabsButton.setEnabled(enabled);
+        ViewHelper.setAlpha(tabsCounter, alpha);
+        menuButton.setEnabled(enabled);
+        ViewHelper.setAlpha(menuIcon, alpha);
 
-        final int actionItemsCount = mActionItemBar.getChildCount();
+        final int actionItemsCount = actionItemBar.getChildCount();
         for (int i = 0; i < actionItemsCount; i++) {
-            mActionItemBar.getChildAt(i).setEnabled(enabled);
+            actionItemBar.getChildAt(i).setEnabled(enabled);
         }
-        ViewHelper.setAlpha(mActionItemBar, alpha);
+        ViewHelper.setAlpha(actionItemBar, alpha);
 
         final Tab tab = Tabs.getInstance().getSelectedTab();
         if (tab != null) {
-            setButtonEnabled(mBack, canDoBack(tab));
-            setButtonEnabled(mForward, canDoForward(tab));
+            setButtonEnabled(backButton, canDoBack(tab));
+            setButtonEnabled(forwardButton, canDoForward(tab));
 
             // Once the editing mode is finished, we have to ensure that the
             // forward button slides away if necessary. This is because we might
             // have only disabled it (without hiding it) when the toolbar entered
             // editing mode.
             if (!isEditing()) {
                 animateForwardButton(canDoForward(tab) ?
                                      ForwardButtonAnimation.SHOW : ForwardButtonAnimation.HIDE);
             }
         }
     }
 
     private void setUIMode(final UIMode uiMode) {
-        mUIMode = uiMode;
-        mUrlEditLayout.setEnabled(uiMode == UIMode.EDIT);
+        this.uiMode = uiMode;
+        urlEditLayout.setEnabled(uiMode == UIMode.EDIT);
     }
 
     /**
      * Returns whether or not the URL bar is in editing mode (url bar is expanded, hiding the new
      * tab button). Note that selection state is independent of editing mode.
      */
     public boolean isEditing() {
-        return (mUIMode == UIMode.EDIT);
+        return (uiMode == UIMode.EDIT);
     }
 
     public void startEditing(String url, PropertyAnimator animator) {
         if (isEditing()) {
             return;
         }
 
-        mUrlEditLayout.setText(url != null ? url : "");
+        urlEditLayout.setText(url != null ? url : "");
 
         setUIMode(UIMode.EDIT);
         updateChildrenForEditing();
 
         updateProgressVisibility();
 
-        if (mStartEditingListener != null) {
-            mStartEditingListener.onStartEditing();
+        if (startEditingListener != null) {
+            startEditingListener.onStartEditing();
         }
 
-        if (mUrlBarRightEdge != null) {
-            mUrlBarRightEdge.setVisibility(View.VISIBLE);
+        final int curveTranslation = getUrlBarCurveTranslation();
+        final int entryTranslation = getUrlBarEntryTranslation();
+        shouldShrinkURLBar = (entryTranslation < 0);
+
+        if (urlBarTranslatingEdge != null) {
+            urlBarTranslatingEdge.setVisibility(View.VISIBLE);
+            if (shouldShrinkURLBar) {
+                urlBarEntry.setLayoutParams(urlBarEntryShrunkenLayoutParams);
+            }
         }
 
-        final int entryTranslation = getUrlBarEntryTranslation();
-        final int curveTranslation = getUrlBarCurveTranslation();
-
-        // This animation doesn't make much sense in a sidebar UI
-        if (HardwareUtils.isTablet() || Build.VERSION.SDK_INT < 11) {
+        if (Build.VERSION.SDK_INT < 11) {
+            showEditingWithoutAnimation(entryTranslation, curveTranslation);
+        } else if (HardwareUtils.isTablet()) {
+            // No animation.
             showUrlEditLayout();
-
-            if (!HardwareUtils.isTablet()) {
-                if (mUrlBarRightEdge != null) {
-                    ViewHelper.setTranslationX(mUrlBarRightEdge, entryTranslation);
-                }
+        } else {
+            showEditingWithPhoneAnimation(animator, entryTranslation, curveTranslation);
+        }
+    }
 
-                ViewHelper.setTranslationX(mTabs, curveTranslation);
-                ViewHelper.setTranslationX(mTabsCounter, curveTranslation);
-                ViewHelper.setTranslationX(mActionItemBar, curveTranslation);
+    private void showEditingWithoutAnimation(final int entryTranslation,
+            final int curveTranslation) {
+        showUrlEditLayout();
 
-                if (mHasSoftMenuButton) {
-                    ViewHelper.setTranslationX(mMenu, curveTranslation);
-                    ViewHelper.setTranslationX(mMenuIcon, curveTranslation);
-                }
-            }
-
-            return;
+        if (urlBarTranslatingEdge != null) {
+            ViewHelper.setTranslationX(urlBarTranslatingEdge, entryTranslation);
         }
 
-        if (mAnimatingEntry)
+        ViewHelper.setTranslationX(tabsButton, curveTranslation);
+        ViewHelper.setTranslationX(tabsCounter, curveTranslation);
+        ViewHelper.setTranslationX(actionItemBar, curveTranslation);
+
+        if (hasSoftMenuButton) {
+            ViewHelper.setTranslationX(menuButton, curveTranslation);
+            ViewHelper.setTranslationX(menuIcon, curveTranslation);
+        }
+    }
+
+    private void showEditingWithPhoneAnimation(final PropertyAnimator animator,
+            final int entryTranslation, final int curveTranslation) {
+        if (isAnimatingEntry)
             return;
 
         // Highlight the toolbar from the start of the animation.
         setSelected(true);
 
-        mUrlDisplayLayout.prepareStartEditingAnimation();
+        urlDisplayLayout.prepareStartEditingAnimation();
 
-        // Slide the right side elements of the toolbar
-
-        if (mUrlBarRightEdge != null) {
-            animator.attach(mUrlBarRightEdge,
+        // Slide toolbar elements.
+        if (urlBarTranslatingEdge != null) {
+            animator.attach(urlBarTranslatingEdge,
                             PropertyAnimator.Property.TRANSLATION_X,
                             entryTranslation);
         }
 
-        animator.attach(mTabs,
+        animator.attach(tabsButton,
                         PropertyAnimator.Property.TRANSLATION_X,
                         curveTranslation);
-        animator.attach(mTabsCounter,
+        animator.attach(tabsCounter,
                         PropertyAnimator.Property.TRANSLATION_X,
                         curveTranslation);
-        animator.attach(mActionItemBar,
+        animator.attach(actionItemBar,
                         PropertyAnimator.Property.TRANSLATION_X,
                         curveTranslation);
 
-        if (mHasSoftMenuButton) {
-            animator.attach(mMenu,
+        if (hasSoftMenuButton) {
+            animator.attach(menuButton,
                             PropertyAnimator.Property.TRANSLATION_X,
                             curveTranslation);
 
-            animator.attach(mMenuIcon,
+            animator.attach(menuIcon,
                             PropertyAnimator.Property.TRANSLATION_X,
                             curveTranslation);
         }
 
         showUrlEditLayout(animator);
 
         animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
             @Override
             public void onPropertyAnimationStart() {
             }
 
             @Override
             public void onPropertyAnimationEnd() {
-                mAnimatingEntry = false;
+                isAnimatingEntry = false;
             }
         });
 
-        mAnimatingEntry = true;
+        isAnimatingEntry = true;
     }
 
     /**
      * Exits edit mode without updating the toolbar title.
      *
      * @return the url that was entered
      */
     public String cancelEdit() {
@@ -1032,309 +1083,329 @@ public class BrowserToolbar extends Them
         final String url = stopEditing();
         if (!TextUtils.isEmpty(url)) {
             setTitle(url);
         }
         return url;
     }
 
     private String stopEditing() {
-        final String url = mUrlEditLayout.getText();
+        final String url = urlEditLayout.getText();
         if (!isEditing()) {
             return url;
         }
         setUIMode(UIMode.DISPLAY);
 
         updateChildrenForEditing();
 
-        if (mStopEditingListener != null) {
-            mStopEditingListener.onStopEditing();
+        if (stopEditingListener != null) {
+            stopEditingListener.onStopEditing();
         }
 
         updateProgressVisibility();
 
-        if (HardwareUtils.isTablet() || Build.VERSION.SDK_INT < 11) {
+        if (Build.VERSION.SDK_INT < 11) {
+            stopEditingWithoutAnimation();
+        } else if (HardwareUtils.isTablet()) {
+            // No animation.
             hideUrlEditLayout();
-
-            if (!HardwareUtils.isTablet()) {
-                updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount());
-
-                if (mUrlBarRightEdge != null) {
-                    ViewHelper.setTranslationX(mUrlBarRightEdge, 0);
-                }
-
-                ViewHelper.setTranslationX(mTabs, 0);
-                ViewHelper.setTranslationX(mTabsCounter, 0);
-                ViewHelper.setTranslationX(mActionItemBar, 0);
-
-                if (mHasSoftMenuButton) {
-                    ViewHelper.setTranslationX(mMenu, 0);
-                    ViewHelper.setTranslationX(mMenuIcon, 0);
-                }
-            }
-
-            return url;
+        } else {
+            stopEditingWithPhoneAnimation();
         }
 
+        return url;
+    }
+
+    private void stopEditingWithoutAnimation() {
+        hideUrlEditLayout();
+
+        updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount());
+
+        if (urlBarTranslatingEdge != null) {
+            urlBarTranslatingEdge.setVisibility(View.INVISIBLE);
+            ViewHelper.setTranslationX(urlBarTranslatingEdge, 0);
+            if (shouldShrinkURLBar) {
+                urlBarEntry.setLayoutParams(urlBarEntryDefaultLayoutParams);
+            }
+        }
+
+        ViewHelper.setTranslationX(tabsButton, 0);
+        ViewHelper.setTranslationX(tabsCounter, 0);
+        ViewHelper.setTranslationX(actionItemBar, 0);
+
+        if (hasSoftMenuButton) {
+            ViewHelper.setTranslationX(menuButton, 0);
+            ViewHelper.setTranslationX(menuIcon, 0);
+        }
+    }
+
+    private void stopEditingWithPhoneAnimation() {
         final PropertyAnimator contentAnimator = new PropertyAnimator(250);
         contentAnimator.setUseHardwareLayer(false);
 
-        // Shrink the urlbar entry back to its original size
-
-        if (mUrlBarRightEdge != null) {
-            contentAnimator.attach(mUrlBarRightEdge,
+        // Slide the toolbar back to its original size.
+        if (urlBarTranslatingEdge != null) {
+            contentAnimator.attach(urlBarTranslatingEdge,
                                    PropertyAnimator.Property.TRANSLATION_X,
                                    0);
         }
 
-        contentAnimator.attach(mTabs,
+        contentAnimator.attach(tabsButton,
                                PropertyAnimator.Property.TRANSLATION_X,
                                0);
-        contentAnimator.attach(mTabsCounter,
+        contentAnimator.attach(tabsCounter,
                                PropertyAnimator.Property.TRANSLATION_X,
                                0);
-        contentAnimator.attach(mActionItemBar,
+        contentAnimator.attach(actionItemBar,
                                PropertyAnimator.Property.TRANSLATION_X,
                                0);
 
-        if (mHasSoftMenuButton) {
-            contentAnimator.attach(mMenu,
+        if (hasSoftMenuButton) {
+            contentAnimator.attach(menuButton,
                                    PropertyAnimator.Property.TRANSLATION_X,
                                    0);
 
-            contentAnimator.attach(mMenuIcon,
+            contentAnimator.attach(menuIcon,
                                    PropertyAnimator.Property.TRANSLATION_X,
                                    0);
         }
 
         hideUrlEditLayout(contentAnimator);
 
         contentAnimator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
             @Override
             public void onPropertyAnimationStart() {
             }
 
             @Override
             public void onPropertyAnimationEnd() {
-                if (mUrlBarRightEdge != null) {
-                    mUrlBarRightEdge.setVisibility(View.INVISIBLE);
+                if (urlBarTranslatingEdge != null) {
+                    urlBarTranslatingEdge.setVisibility(View.INVISIBLE);
+                    if (shouldShrinkURLBar) {
+                        urlBarEntry.setLayoutParams(urlBarEntryDefaultLayoutParams);
+                    }
                 }
 
                 PropertyAnimator buttonsAnimator = new PropertyAnimator(300);
-                mUrlDisplayLayout.prepareStopEditingAnimation(buttonsAnimator);
+                urlDisplayLayout.prepareStopEditingAnimation(buttonsAnimator);
                 buttonsAnimator.start();
 
-                mAnimatingEntry = false;
+                isAnimatingEntry = false;
 
                 // Trigger animation to update the tabs counter once the
                 // tabs button is back on screen.
                 updateTabCountAndAnimate(Tabs.getInstance().getDisplayCount());
             }
         });
 
-        mAnimatingEntry = true;
+        isAnimatingEntry = true;
         contentAnimator.start();
-
-        return url;
     }
 
     public void setButtonEnabled(ImageButton button, boolean enabled) {
         final Drawable drawable = button.getDrawable();
         if (drawable != null) {
             // This alpha value has to be in sync with the one used
             // in updateChildrenForEditing().
             drawable.setAlpha(enabled ? 255 : 61);
         }
 
         button.setEnabled(enabled);
     }
 
     public void updateBackButton(Tab tab) {
-        setButtonEnabled(mBack, canDoBack(tab));
+        setButtonEnabled(backButton, canDoBack(tab));
     }
 
     private void animateForwardButton(final ForwardButtonAnimation animation) {
         // If the forward button is not visible, we must be
         // in the phone UI.
-        if (mForward.getVisibility() != View.VISIBLE) {
+        if (forwardButton.getVisibility() != View.VISIBLE) {
             return;
         }
 
         final boolean showing = (animation == ForwardButtonAnimation.SHOW);
 
         // if the forward button's margin is non-zero, this means it has already
         // been animated to be visible¸ and vice-versa.
-        MarginLayoutParams fwdParams = (MarginLayoutParams) mForward.getLayoutParams();
-        if ((fwdParams.leftMargin > mDefaultForwardMargin && showing) ||
-            (fwdParams.leftMargin == mDefaultForwardMargin && !showing)) {
+        MarginLayoutParams fwdParams = (MarginLayoutParams) forwardButton.getLayoutParams();
+        if ((fwdParams.leftMargin > defaultForwardMargin && showing) ||
+            (fwdParams.leftMargin == defaultForwardMargin && !showing)) {
             return;
         }
 
         // We want the forward button to show immediately when switching tabs
         final PropertyAnimator forwardAnim =
-                new PropertyAnimator(mSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION);
-        final int width = mForward.getWidth() / 2;
+                new PropertyAnimator(isSwitchingTabs ? 10 : FORWARD_ANIMATION_DURATION);
+        final int width = forwardButton.getWidth() / 2;
 
         forwardAnim.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener() {
             @Override
             public void onPropertyAnimationStart() {
                 if (!showing) {
                     // Set the margin before the transition when hiding the forward button. We
                     // have to do this so that the favicon isn't clipped during the transition
                     MarginLayoutParams layoutParams =
-                        (MarginLayoutParams) mUrlDisplayLayout.getLayoutParams();
+                        (MarginLayoutParams) urlDisplayLayout.getLayoutParams();
                     layoutParams.leftMargin = 0;
 
                     // Do the same on the URL edit container
-                    layoutParams = (MarginLayoutParams) mUrlEditLayout.getLayoutParams();
+                    layoutParams = (MarginLayoutParams) urlEditLayout.getLayoutParams();
                     layoutParams.leftMargin = 0;
 
                     requestLayout();
                     // Note, we already translated the favicon, site security, and text field
                     // in prepareForwardAnimation, so they should appear to have not moved at
                     // all at this point.
                 }
             }
 
             @Override
             public void onPropertyAnimationEnd() {
                 if (showing) {
                     MarginLayoutParams layoutParams =
-                        (MarginLayoutParams) mUrlDisplayLayout.getLayoutParams();
-                    layoutParams.leftMargin = mUrlBarViewOffset;
+                        (MarginLayoutParams) urlDisplayLayout.getLayoutParams();
+                    layoutParams.leftMargin = urlBarViewOffset;
 
-                    layoutParams = (MarginLayoutParams) mUrlEditLayout.getLayoutParams();
-                    layoutParams.leftMargin = mUrlBarViewOffset;
+                    layoutParams = (MarginLayoutParams) urlEditLayout.getLayoutParams();
+                    layoutParams.leftMargin = urlBarViewOffset;
                 }
 
-                mUrlDisplayLayout.finishForwardAnimation();
+                urlDisplayLayout.finishForwardAnimation();
 
-                MarginLayoutParams layoutParams = (MarginLayoutParams) mForward.getLayoutParams();
-                layoutParams.leftMargin = mDefaultForwardMargin + (showing ? width : 0);
-                ViewHelper.setTranslationX(mForward, 0);
+                MarginLayoutParams layoutParams = (MarginLayoutParams) forwardButton.getLayoutParams();
+                layoutParams.leftMargin = defaultForwardMargin + (showing ? width : 0);
+                ViewHelper.setTranslationX(forwardButton, 0);
 
                 requestLayout();
             }
         });
 
         prepareForwardAnimation(forwardAnim, animation, width);
         forwardAnim.start();
     }
 
     public void updateForwardButton(Tab tab) {
         final boolean enabled = canDoForward(tab);
-        if (mForward.isEnabled() == enabled)
+        if (forwardButton.isEnabled() == enabled)
             return;
 
         // Save the state on the forward button so that we can skip animations
         // when there's nothing to change
-        setButtonEnabled(mForward, enabled);
+        setButtonEnabled(forwardButton, enabled);
         animateForwardButton(enabled ? ForwardButtonAnimation.SHOW : ForwardButtonAnimation.HIDE);
     }
 
     private void prepareForwardAnimation(PropertyAnimator anim, ForwardButtonAnimation animation, int width) {
         if (animation == ForwardButtonAnimation.HIDE) {
-            anim.attach(mForward,
+            anim.attach(forwardButton,
                       PropertyAnimator.Property.TRANSLATION_X,
                       -width);
-            anim.attach(mForward,
+            anim.attach(forwardButton,
                       PropertyAnimator.Property.ALPHA,
                       0);
 
         } else {
-            anim.attach(mForward,
+            anim.attach(forwardButton,
                       PropertyAnimator.Property.TRANSLATION_X,
                       width);
-            anim.attach(mForward,
+            anim.attach(forwardButton,
                       PropertyAnimator.Property.ALPHA,
                       1);
         }
 
-        mUrlDisplayLayout.prepareForwardAnimation(anim, animation, width);
+        urlDisplayLayout.prepareForwardAnimation(anim, animation, width);
     }
 
     @Override
     public boolean addActionItem(View actionItem) {
-        mActionItemBar.addView(actionItem);
+        actionItemBar.addView(actionItem);
         return true;
     }
 
     @Override
     public void removeActionItem(View actionItem) {
-        mActionItemBar.removeView(actionItem);
+        actionItemBar.removeView(actionItem);
     }
 
     @Override
     public void setPrivateMode(boolean isPrivate) {
         super.setPrivateMode(isPrivate);
 
-        mTabs.setPrivateMode(isPrivate);
-        mMenu.setPrivateMode(isPrivate);
-        mMenuIcon.setPrivateMode(isPrivate);
-        mUrlEditLayout.setPrivateMode(isPrivate);
-
-        if (mBack instanceof BackButton) {
-            ((BackButton) mBack).setPrivateMode(isPrivate);
+        tabsButton.setPrivateMode(isPrivate);
+        menuButton.setPrivateMode(isPrivate);
+        menuIcon.setPrivateMode(isPrivate);
+        urlEditLayout.setPrivateMode(isPrivate);
+        if (editSeparator != null) {
+            editSeparator.setPrivateMode(isPrivate);
         }
 
-        if (mForward instanceof ForwardButton) {
-            ((ForwardButton) mForward).setPrivateMode(isPrivate);
+        if (backButton instanceof BackButton) {
+            ((BackButton) backButton).setPrivateMode(isPrivate);
+        }
+
+        if (forwardButton instanceof ForwardButton) {
+            ((ForwardButton) forwardButton).setPrivateMode(isPrivate);
         }
     }
 
     public void show() {
         setVisibility(View.VISIBLE);
     }
 
     public void hide() {
         setVisibility(View.GONE);
     }
 
     public View getDoorHangerAnchor() {
-        return mUrlDisplayLayout.getDoorHangerAnchor();
+        return urlDisplayLayout.getDoorHangerAnchor();
     }
 
     public void onDestroy() {
         Tabs.unregisterOnTabsChangedListener(this);
 
         unregisterEventListener("Reader:Click");
         unregisterEventListener("Reader:LongClick");
     }
 
     public boolean openOptionsMenu() {
-        if (!mHasSoftMenuButton)
+        if (!hasSoftMenuButton) {
             return false;
+        }
 
         // Initialize the popup.
-        if (mMenuPopup == null) {
-            View panel = mActivity.getMenuPanel();
-            mMenuPopup = new MenuPopup(mActivity);
-            mMenuPopup.setPanelView(panel);
+        if (menuPopup == null) {
+            View panel = activity.getMenuPanel();
+            menuPopup = new MenuPopup(activity);
+            menuPopup.setPanelView(panel);
 
-            mMenuPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
+            menuPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
                 @Override
                 public void onDismiss() {
-                    mActivity.onOptionsMenuClosed(null);
+                    activity.onOptionsMenuClosed(null);
                 }
             });
         }
 
         GeckoAppShell.getGeckoInterface().invalidateOptionsMenu();
-        if (!mMenuPopup.isShowing())
-            mMenuPopup.showAsDropDown(mMenu);
+        if (!menuPopup.isShowing()) {
+            menuPopup.showAsDropDown(menuButton);
+        }
 
         return true;
     }
 
     public boolean closeOptionsMenu() {
-        if (!mHasSoftMenuButton)
+        if (!hasSoftMenuButton) {
             return false;
+        }
 
-        if (mMenuPopup != null && mMenuPopup.isShowing())
-            mMenuPopup.dismiss();
+        if (menuPopup != null && menuPopup.isShowing()) {
+            menuPopup.dismiss();
+        }
 
         return true;
     }
 
     private void registerEventListener(String event) {
         GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
     }
 
@@ -1355,17 +1426,17 @@ public class BrowserToolbar extends Them
             if (tab != null) {
                 tab.addToReadingList();
             }
         }
     }
 
     @Override
     public void onLightweightThemeChanged() {
-        Drawable drawable = mTheme.getDrawable(this);
+        Drawable drawable = theme.getDrawable(this);
         if (drawable == null)
             return;
 
         StateListDrawable stateList = new StateListDrawable();
         stateList.addState(PRIVATE_STATE_SET, getColorDrawable(R.color.background_private));
         stateList.addState(EMPTY_STATE_SET, drawable);
 
         setBackgroundDrawable(stateList);
--- a/mobile/android/base/toolbar/ToolbarEditLayout.java
+++ b/mobile/android/base/toolbar/ToolbarEditLayout.java
@@ -120,17 +120,25 @@ public class ToolbarEditLayout extends T
     }
 
     private void showSoftInput() {
         InputMethodManager imm =
                (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
         imm.showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
     }
 
-    void prepareShowAnimation(PropertyAnimator animator) {
+    void prepareAnimation(final boolean showing, final PropertyAnimator animator) {
+        if (showing) {
+            prepareShowAnimation(animator);
+        } else {
+            prepareHideAnimation(animator);
+        }
+    }
+
+    private void prepareShowAnimation(final PropertyAnimator animator) {
         if (animator == null) {
             mEditText.requestFocus();
             showSoftInput();
             return;
         }
 
         animator.addPropertyAnimationListener(new PropertyAnimationListener() {
             @Override
@@ -142,16 +150,35 @@ public class ToolbarEditLayout extends T
             @Override
             public void onPropertyAnimationEnd() {
                 ViewHelper.setAlpha(mGo, 1.0f);
                 showSoftInput();
             }
         });
     }
 
+    private void prepareHideAnimation(final PropertyAnimator animator) {
+        if (animator == null) {
+            return;
+        }
+
+        animator.addPropertyAnimationListener(new PropertyAnimationListener() {
+            @Override
+            public void onPropertyAnimationStart() {
+                ViewHelper.setAlpha(mGo, 0.0f);
+            }
+
+            @Override
+            public void onPropertyAnimationEnd() {
+                // The enclosing view is invisible, so unhide the go button.
+                ViewHelper.setAlpha(mGo, 1.0f);
+            }
+        });
+    }
+
     void setOnCommitListener(OnCommitListener listener) {
         mCommitListener = listener;
         mEditText.setOnCommitListener(listener);
     }
 
     void setOnDismissListener(OnDismissListener listener) {
         mEditText.setOnDismissListener(listener);
     }
--- a/mobile/android/base/widget/ThemedEditText.java.in
+++ b/mobile/android/base/widget/ThemedEditText.java.in
@@ -1,3 +1,4 @@
 #filter substitution
-#define VIEWTYPE EditText
+#define VIEW_NAME_SUFFIX EditText
+#define BASE_TYPE android.widget.EditText
 #include ThemedView.java.frag
--- a/mobile/android/base/widget/ThemedImageButton.java.in
+++ b/mobile/android/base/widget/ThemedImageButton.java.in
@@ -1,3 +1,4 @@
 #filter substitution
-#define VIEWTYPE ImageButton
+#define VIEW_NAME_SUFFIX ImageButton
+#define BASE_TYPE android.widget.ImageButton
 #include ThemedView.java.frag
--- a/mobile/android/base/widget/ThemedImageView.java.in
+++ b/mobile/android/base/widget/ThemedImageView.java.in
@@ -1,3 +1,4 @@
 #filter substitution
-#define VIEWTYPE ImageView
+#define VIEW_NAME_SUFFIX ImageView
+#define BASE_TYPE android.widget.ImageView
 #include ThemedView.java.frag
--- a/mobile/android/base/widget/ThemedLinearLayout.java.in
+++ b/mobile/android/base/widget/ThemedLinearLayout.java.in
@@ -1,3 +1,4 @@
 #filter substitution
-#define VIEWTYPE LinearLayout
+#define VIEW_NAME_SUFFIX LinearLayout
+#define BASE_TYPE android.widget.LinearLayout
 #include ThemedView.java.frag
--- a/mobile/android/base/widget/ThemedRelativeLayout.java.in
+++ b/mobile/android/base/widget/ThemedRelativeLayout.java.in
@@ -1,3 +1,4 @@
 #filter substitution
-#define VIEWTYPE RelativeLayout
+#define VIEW_NAME_SUFFIX RelativeLayout
+#define BASE_TYPE android.widget.RelativeLayout
 #include ThemedView.java.frag
--- a/mobile/android/base/widget/ThemedTextSwitcher.java.in
+++ b/mobile/android/base/widget/ThemedTextSwitcher.java.in
@@ -1,3 +1,4 @@
 #filter substitution
-#define VIEWTYPE TextSwitcher
+#define VIEW_NAME_SUFFIX TextSwitcher
+#define BASE_TYPE android.widget.TextSwitcher
 #include ThemedView.java.frag
--- a/mobile/android/base/widget/ThemedTextView.java.in
+++ b/mobile/android/base/widget/ThemedTextView.java.in
@@ -1,3 +1,4 @@
 #filter substitution
-#define VIEWTYPE TextView
+#define VIEW_NAME_SUFFIX TextView
+#define BASE_TYPE android.widget.TextView
 #include ThemedView.java.frag
--- a/mobile/android/base/widget/ThemedView.java.frag
+++ b/mobile/android/base/widget/ThemedView.java.frag
@@ -7,36 +7,35 @@ package org.mozilla.gecko.widget;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.LightweightTheme;
 import org.mozilla.gecko.R;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.ColorDrawable;
 import android.util.AttributeSet;
-import android.widget.@VIEWTYPE@;
 
-public class Themed@VIEWTYPE@ extends @VIEWTYPE@
-                              implements LightweightTheme.OnChangeListener {
+public class Themed@VIEW_NAME_SUFFIX@ extends @BASE_TYPE@
+                                     implements LightweightTheme.OnChangeListener {
     private final LightweightTheme mTheme;
 
     private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private };
     private static final int[] STATE_LIGHT = { R.attr.state_light };
     private static final int[] STATE_DARK = { R.attr.state_dark };
 
     protected static final int[] PRIVATE_PRESSED_STATE_SET = { R.attr.state_private, android.R.attr.state_pressed };
     protected static final int[] PRIVATE_FOCUSED_STATE_SET = { R.attr.state_private, android.R.attr.state_focused };
     protected static final int[] PRIVATE_STATE_SET = { R.attr.state_private };
 
     private boolean mIsPrivate = false;
     private boolean mIsLight = false;
     private boolean mIsDark = false;
     private boolean mAutoUpdateTheme = true;
 
-    public Themed@VIEWTYPE@(Context context, AttributeSet attrs) {
+    public Themed@VIEW_NAME_SUFFIX@(Context context, AttributeSet attrs) {
         super(context, attrs);
         mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme();
 
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LightweightTheme);
         mAutoUpdateTheme = a.getBoolean(R.styleable.LightweightTheme_autoUpdateTheme, true);
         a.recycle();
     }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/ThemedView.java.in
@@ -0,0 +1,4 @@
+#filter substitution
+#define VIEW_NAME_SUFFIX View
+#define BASE_TYPE android.view.View
+#include ThemedView.java.frag
--- a/mobile/android/chrome/content/dbg-browser-actors.js
+++ b/mobile/android/chrome/content/dbg-browser-actors.js
@@ -18,16 +18,17 @@
  *
  * * @param aConnection DebuggerServerConnection
  *        The conection to the client.
  */
 function createRootActor(aConnection)
 {
   let parameters = {
     tabList: new MobileTabList(aConnection),
+    addonList: new BrowserAddonList(aConnection),
     globalActorFactories: DebuggerServer.globalActorFactories,
     onShutdown: sendShutdownEvent
   };
   return new RootActor(aConnection, parameters);
 }
 
 /**
  * A live list of BrowserTabActors representing the current browser tabs,
--- a/mobile/android/tests/background/junit3/src/sync/TestSyncConfiguration.java
+++ b/mobile/android/tests/background/junit3/src/sync/TestSyncConfiguration.java
@@ -4,28 +4,23 @@
 package org.mozilla.gecko.background.sync;
 
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
-import org.mozilla.gecko.sync.PrefsSource;
 import org.mozilla.gecko.sync.SyncConfiguration;
 
 import android.content.SharedPreferences;
 
-public class TestSyncConfiguration extends AndroidSyncTestCase implements PrefsSource {
+public class TestSyncConfiguration extends AndroidSyncTestCase {
   public static final String TEST_PREFS_NAME = "test";
 
-  /*
-   * PrefsSource methods.
-   */
-  @Override
   public SharedPreferences getPrefs(String name, int mode) {
     return this.getApplicationContext().getSharedPreferences(name, mode);
   }
 
   /**
    * Ensure that declined engines persist through prefs.
    */
   public void testDeclinedEngineNames() {
@@ -140,11 +135,11 @@ public class TestSyncConfiguration exten
     SyncConfiguration config = null;
     config = newSyncConfiguration();
     config.loadFromPrefs(prefs);
     // Forms should not be selected if history is not present.
     assertTrue(config.userSelectedEngines.isEmpty());
   }
 
   protected SyncConfiguration newSyncConfiguration() {
-    return new SyncConfiguration(null, null, TEST_PREFS_NAME, this);
+    return new SyncConfiguration(null, null, getPrefs(TEST_PREFS_NAME, 0));
   }
 }
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -50,16 +50,19 @@ user_pref("toolkit.telemetry.notifiedOpt
 // Existing tests assume there is no font size inflation.
 user_pref("font.size.inflation.emPerLine", 0);
 user_pref("font.size.inflation.minTwips", 0);
 
 // AddonManager tests require that the experiments feature be enabled.
 user_pref("experiments.enabled", true);
 user_pref("experiments.supported", true);
 user_pref("experiments.logging.level", "Trace");
+// Point the manifest at something local so we don't risk it hitting production
+// data and installing experiments that may vary over time.
+user_pref("experiments.manifest.uri", "http://%(server)s/experiments-dummy/manifest");
 
 // Only load extensions from the application and user profile
 // AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
 user_pref("extensions.enabledScopes", 5);
 // Disable metadata caching for installed add-ons by default
 user_pref("extensions.getAddons.cache.enabled", false);
 // Disable intalling any distribution add-ons
 user_pref("extensions.installDistroAddons", false);
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -3,21 +3,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /* General utilities used throughout devtools. */
 
 // hasChrome is provided as a global by the loader. It is true if we are running
 // on the main thread, and false if we are running on a worker thread.
-if (hasChrome) {
-  var { Ci, Cu } = require("chrome");
-  var Services = require("Services");
-  var setTimeout = Cu.import("resource://gre/modules/Timer.jsm", {}).setTimeout;
-}
+var { Ci, Cu } = require("chrome");
+var Services = require("Services");
+var setTimeout = Cu.import("resource://gre/modules/Timer.jsm", {}).setTimeout;
 
 /**
  * Turn the error |aError| into a string, without fail.
  */
 exports.safeErrorString = function safeErrorString(aError) {
   try {
     let errorString = aError.toString();
     if (typeof errorString == "string") {
@@ -299,8 +297,24 @@ exports.isSafeJSObject = function isSafe
 
   let principal = Services.scriptSecurityManager.getObjectPrincipal(aObj);
   if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
     return true; // allow chrome objects
   }
 
   return Cu.isXrayWrapper(aObj);
 };
+
+exports.dumpn = function dumpn(str) {
+  if (exports.dumpn.wantLogging) {
+    dump("DBG-SERVER: " + str + "\n");
+  }
+}
+
+// We want wantLogging to be writable. The exports object is frozen by the
+// loader, so define it on dumpn instead.
+exports.dumpn.wantLogging = false;
+
+exports.dbg_assert = function dbg_assert(cond, e) {
+  if (!cond) {
+    return e;
+  }
+}
--- a/toolkit/devtools/Loader.jsm
+++ b/toolkit/devtools/Loader.jsm
@@ -3,17 +3,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /**
  * Manages the addon-sdk loader instance used to load the developer tools.
  */
 
-let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+let { Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+// addDebuggerToGlobal only allows adding the Debugger object to a global. The
+// this object is not guaranteed to be a global (in particular on B2G, due to
+// compartment sharing), so add the Debugger object to a sandbox instead.
+let sandbox = Cu.Sandbox(CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')());
+Cu.evalInSandbox(
+  "Components.utils.import('resource://gre/modules/jsdebugger.jsm');" +
+  "addDebuggerToGlobal(this);",
+  sandbox
+);
+let Debugger = sandbox.Debugger;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm");
@@ -29,33 +40,33 @@ this.EXPORTED_SYMBOLS = ["DevToolsLoader
 
 /**
  * Providers are different strategies for loading the devtools.
  */
 
 let loaderGlobals = {
   btoa: btoa,
   console: console,
-  hasChrome: true,
   promise: promise,
   _Iterator: Iterator,
   ChromeWorker: ChromeWorker,
   loader: {
     lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils),
     lazyImporter: XPCOMUtils.defineLazyModuleGetter.bind(XPCOMUtils),
     lazyServiceGetter: XPCOMUtils.defineLazyServiceGetter.bind(XPCOMUtils)
   }
 };
 
 // Used when the tools should be loaded from the Firefox package itself (the default)
 function BuiltinProvider() {}
 BuiltinProvider.prototype = {
   load: function() {
     this.loader = new loader.Loader({
       modules: {
+        "Debugger": Debugger,
         "Services": Object.create(Services),
         "toolkit/loader": loader,
         "source-map": SourceMap,
       },
       paths: {
         // When you add a line to this mapping, don't forget to make a
         // corresponding addition to the SrcdirProvider mapping below as well.
         "": "resource://gre/modules/commonjs/",
@@ -123,16 +134,17 @@ SrcdirProvider.prototype = {
     let prettyFastURI = this.fileURI(OS.Path.join(toolkitDir), "pretty-fast.js");
     let asyncUtilsURI = this.fileURI(OS.Path.join(toolkitDir), "async-utils.js");
     let contentObserverURI = this.fileURI(OS.Path.join(toolkitDir), "content-observer.js");
     let gcliURI = this.fileURI(OS.Path.join(toolkitDir, "gcli", "source", "lib", "gcli"));
     let acornURI = this.fileURI(OS.Path.join(toolkitDir, "acorn"));
     let acornWalkURI = OS.Path.join(acornURI, "walk.js");
     this.loader = new loader.Loader({
       modules: {
+        "Debugger": Debugger,
         "Services": Object.create(Services),
         "toolkit/loader": loader,
         "source-map": SourceMap,
       },
       paths: {
         "": "resource://gre/modules/commonjs/",
         "main": mainURI,
         "devtools": devtoolsURI,
--- a/toolkit/devtools/server/actors/root.js
+++ b/toolkit/devtools/server/actors/root.js
@@ -354,16 +354,20 @@ RootActor.prototype = {
   onEcho: function (aRequest) {
     /*
      * Request packets are frozen. Copy aRequest, so that
      * DebuggerServerConnection.onPacket can attach a 'from' property.
      */
     return JSON.parse(JSON.stringify(aRequest));
   },
 
+  onProtocolDescription: function (aRequest) {
+    return protocol.dumpProtocolSpec()
+  },
+
   /* Support for DebuggerServer.addGlobalActor. */
   _createExtraActors: CommonCreateExtraActors,
   _appendExtraActors: CommonAppendExtraActors,
 
   /* ThreadActor hooks. */
 
   /**
    * Prepare to enter a nested event loop by disabling debuggee events.
@@ -394,10 +398,11 @@ RootActor.prototype = {
       windowUtils.suppressEventHandling(false);
     }
   }
 };
 
 RootActor.prototype.requestTypes = {
   "listTabs": RootActor.prototype.onListTabs,
   "listAddons": RootActor.prototype.onListAddons,
-  "echo": RootActor.prototype.onEcho
+  "echo": RootActor.prototype.onEcho,
+  "protocolDescription": RootActor.prototype.onProtocolDescription
 };
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -1108,16 +1108,17 @@ BrowserAddonList.prototype.onUninstalled
   this._onListChanged();
 };
 
 function BrowserAddonActor(aConnection, aAddon) {
   this.conn = aConnection;
   this._addon = aAddon;
   this._contextPool = null;
   this._threadActor = null;
+  this._global = null;
   AddonManager.addAddonListener(this);
 }
 
 BrowserAddonActor.prototype = {
   actorPrefix: "addon",
 
   get exited() {
     return !this._addon;
@@ -1130,42 +1131,63 @@ BrowserAddonActor.prototype = {
   get url() {
     return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined;
   },
 
   get attached() {
     return this._threadActor;
   },
 
+  get global() {
+    return this._global;
+  },
+
   form: function BAA_form() {
     dbg_assert(this.actorID, "addon should have an actorID.");
 
     return {
       actor: this.actorID,
       id: this.id,
       name: this._addon.name,
-      url: this.url
+      url: this.url,
+      debuggable: this._addon.isDebuggable,
     };
   },
 
   disconnect: function BAA_disconnect() {
+    this._addon = null;
+    this._global = null;
     AddonManager.removeAddonListener(this);
   },
 
+  setOptions: function BAA_setOptions(aOptions) {
+    if ("global" in aOptions) {
+      this._global = aOptions.global;
+    }
+  },
+
+  onDisabled: function BAA_onDisabled(aAddon) {
+    if (aAddon != this._addon) {
+      return;
+    }
+
+    this._global = null;
+  },
+
   onUninstalled: function BAA_onUninstalled(aAddon) {
-    if (aAddon != this._addon)
+    if (aAddon != this._addon) {
       return;
+    }
 
     if (this.attached) {
       this.onDetach();
       this.conn.send({ from: this.actorID, type: "tabDetached" });
     }
 
-    this._addon = null;
-    AddonManager.removeAddonListener(this);
+    this.disconnect();
   },
 
   onAttach: function BAA_onAttach() {
     if (this.exited) {
       return { type: "exited" };
     }
 
     if (!this.attached) {
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -4,49 +4,55 @@
  * 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";
 /**
  * Toolkit glue for the remote debugging protocol, loaded into the
  * debugging global.
  */
+let { Ci, Cc, CC, Cu, Cr } = require("chrome");
+let Debugger = require("Debugger");
+let Services = require("Services");
 let DevToolsUtils = require("devtools/toolkit/DevToolsUtils.js");
+let { dumpn, dbg_assert } = DevToolsUtils;
 let Services = require("Services");
+let EventEmitter = require("devtools/toolkit/event-emitter");
 
 // Until all Debugger server code is converted to SDK modules,
 // imports Components.* alias from chrome module.
 var { Ci, Cc, CC, Cu, Cr } = require("chrome");
 // On B2G, `this` != Global scope, so `Ci` won't be binded on `this`
 // (i.e. this.Ci is undefined) Then later, when using loadSubScript,
 // Ci,... won't be defined for sub scripts.
 this.Ci = Ci;
 this.Cc = Cc;
 this.CC = CC;
 this.Cu = Cu;
 this.Cr = Cr;
+this.Debugger = Debugger;
+this.Services = Services;
 this.DevToolsUtils = DevToolsUtils;
-this.Services = Services;
+this.dumpn = dumpn;
+this.dbg_assert = dbg_assert;
 
 // Overload `Components` to prevent SDK loader exception on Components
 // object usage
 Object.defineProperty(this, "Components", {
   get: function () require("chrome").components
 });
 
 const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties";
 
 const nsFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
 Cu.import("resource://gre/modules/reflect.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
+dumpn.wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 
 Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
-Cu.import("resource://gre/modules/jsdebugger.jsm");
-addDebuggerToGlobal(this);
 
 function loadSubScript(aURL)
 {
   try {
     let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
       .getService(Ci.mozIJSSubScriptLoader);
     loader.loadSubScript(aURL, this);
   } catch(e) {
@@ -71,30 +77,16 @@ Cu.import("resource://gre/modules/devtoo
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
                                   "resource://gre/modules/devtools/Console.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "NetworkMonitorManager", () => {
   return require("devtools/toolkit/webconsole/network-monitor").NetworkMonitorManager;
 });
 
-function dumpn(str) {
-  if (wantLogging) {
-    dump("DBG-SERVER: " + str + "\n");
-  }
-}
-this.dumpn = dumpn;
-
-function dbg_assert(cond, e) {
-  if (!cond) {
-    return e;
-  }
-}
-this.dbg_assert = dbg_assert;
-
 loadSubScript.call(this, "resource://gre/modules/devtools/server/transport.js");
 
 // XPCOM constructors
 const ServerSocket = CC("@mozilla.org/network/server-socket;1",
                         "nsIServerSocket",
                         "initSpecialConnection");
 const UnixDomainServerSocket = CC("@mozilla.org/network/server-socket;1",
                                   "nsIServerSocket",
@@ -180,29 +172,16 @@ var DebuggerServer = {
   /**
    * The windowtype of the chrome window to use for actors that use the global
    * window (i.e the global style editor). Set this to your main window type,
    * for example "navigator:browser".
    */
   chromeWindowType: null,
 
   /**
-   * Set that to a function that will be called anytime a new connection
-   * is opened or one is closed.
-   */
-  onConnectionChange: null,
-
-  _fireConnectionChange: function(aWhat) {
-    if (this.onConnectionChange &&
-        typeof this.onConnectionChange === "function") {
-      this.onConnectionChange(aWhat);
-    }
-  },
-
-  /**
    * Prompt the user to accept or decline the incoming connection. This is the
    * default implementation that products embedding the debugger server may
    * choose to override.
    *
    * @return true if the connection should be permitted, false otherwise
    */
   _defaultAllowConnection: function DS__defaultAllowConnection() {
     let title = L10N.getStr("remoteIncomingPromptTitle");
@@ -239,16 +218,18 @@ var DebuggerServer = {
 
     this.xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
     this.initTransport(aAllowConnectionCallback);
     this.addActors("resource://gre/modules/devtools/server/actors/root.js");
 
     this._initialized = true;
   },
 
+  protocol: require("devtools/server/protocol"),
+
   /**
    * Initialize the debugger server's transport variables.  This can be
    * in place of init() for cases where the jsdebugger isn't needed.
    *
    * @param function aAllowConnectionCallback
    *        The embedder-provider callback, that decides whether an incoming
    *        remote protocol conection should be allowed or refused.
    */
@@ -291,18 +272,16 @@ var DebuggerServer = {
 
     this.closeListener();
     this.globalActorFactories = {};
     this.tabActorFactories = {};
     this._allowConnection = null;
     this._transportInitialized = false;
     this._initialized = false;
 
-    this._fireConnectionChange("closed");
-
     dumpn("Debugger server is shut down.");
   },
 
   /**
    * Load a subscript into the debugging global.
    *
    * @param aURL string A url that will be loaded as a subscript into the
    *        debugging global.  The user must load at least one script
@@ -412,16 +391,40 @@ var DebuggerServer = {
     this.registerModule("devtools/server/actors/memory");
     this.registerModule("devtools/server/actors/eventlooplag");
     if ("nsIProfiler" in Ci) {
       this.addActors("resource://gre/modules/devtools/server/actors/profiler.js");
     }
   },
 
   /**
+   * Passes a set of options to the BrowserAddonActors for the given ID.
+   *
+   * @param aId string
+   *        The ID of the add-on to pass the options to
+   * @param aOptions object
+   *        The options.
+   * @return a promise that will be resolved when complete.
+   */
+  setAddonOptions: function DS_setAddonOptions(aId, aOptions) {
+    if (!this._initialized) {
+      return;
+    }
+
+    let promises = [];
+
+    // Pass to all connections
+    for (let connID of Object.getOwnPropertyNames(this._connections)) {
+      promises.push(this._connections[connID].setAddonOptions(aId, aOptions));
+    }
+
+    return all(promises);
+  },
+
+  /**
    * Listens on the given port or socket file for remote debugger connections.
    *
    * @param aPortOrPath int, string
    *        If given an integer, the port to listen on.
    *        Otherwise, the path to the unix socket domain file to listen on.
    */
   openListener: function DS_openListener(aPortOrPath) {
     if (!Services.prefs.getBoolPref("devtools.debugger.remote-enabled")) {
@@ -703,26 +706,26 @@ var DebuggerServer = {
         conn.rootActor.actorID = aForwardingPrefix + ":root";
       else
         conn.rootActor.actorID = "root";
       conn.addActor(conn.rootActor);
       aTransport.send(conn.rootActor.sayHello());
     }
     aTransport.ready();
 
-    this._fireConnectionChange("opened");
+    this.emit("connectionchange", "opened", conn);
     return conn;
   },
 
   /**
    * Remove the connection from the debugging server.
    */
   _connectionClosed: function DS_connectionClosed(aConnection) {
     delete this._connections[aConnection.prefix];
-    this._fireConnectionChange("closed");
+    this.emit("connectionchange", "closed", aConnection);
   },
 
   // DebuggerServer extension API.
 
   /**
    * Registers handlers for new tab-scoped request types defined dynamically.
    * This is used for example by add-ons to augment the functionality of the tab
    * actor. Note that the name or actorPrefix of the request type is not allowed
@@ -807,16 +810,18 @@ var DebuggerServer = {
       let handler = DebuggerServer.globalActorFactories[name];
       if (handler.name == aFunction.name) {
         delete DebuggerServer.globalActorFactories[name];
       }
     }
   }
 };
 
+EventEmitter.decorate(DebuggerServer);
+
 if (this.exports) {
   exports.DebuggerServer = DebuggerServer;
 }
 // Needed on B2G (See header note)
 this.DebuggerServer = DebuggerServer;
 
 /**
  * Construct an ActorPool.
@@ -1056,16 +1061,41 @@ DebuggerServerConnection.prototype = {
     Cu.reportError(errorString);
     dumpn(errorString);
     return {
       error: "unknownError",
       message: errorString
     };
   },
 
+  /**
+   * Passes a set of options to the BrowserAddonActors for the given ID.
+   *
+   * @param aId string
+   *        The ID of the add-on to pass the options to
+   * @param aOptions object
+   *        The options.
+   * @return a promise that will be resolved when complete.
+   */
+  setAddonOptions: function DSC_setAddonOptions(aId, aOptions) {
+    let addonList = this.rootActor._parameters.addonList;
+    if (!addonList) {
+      return resolve();
+    }
+    return addonList.getList().then((addonActors) => {
+      for (let actor of addonActors) {
+        if (actor.id != aId) {
+          continue;
+        }
+        actor.setOptions(aOptions);
+        return;
+      }
+    });
+  },
+
   /* Forwarding packets to other transports based on actor name prefixes. */
 
   /*
    * Arrange to forward packets to another server. This is how we
    * forward debugging connections to child processes.
    *
    * If we receive a packet for an actor whose name begins with |aPrefix|
    * followed by ':', then we will forward that packet to |aTransport|.
--- a/toolkit/devtools/server/protocol.js
+++ b/toolkit/devtools/server/protocol.js
@@ -173,16 +173,17 @@ types.addArrayType = function(subtype) {
 
   let name = "array:" + subtype.name;
 
   // Arrays of primitive types are primitive types themselves.
   if (subtype.primitive) {
     return types.addType(name);
   }
   return types.addType(name, {
+    category: "array",
     read: (v, ctx) => [subtype.read(i, ctx) for (i of v)],
     write: (v, ctx) => [subtype.write(i, ctx) for (i of v)]
   });
 };
 
 /**
  * Add a dict type to the type system.  This allows you to serialize
  * a JS object that contains non-primitive subtypes.
@@ -190,16 +191,18 @@ types.addArrayType = function(subtype) {
  * Properties of the value that aren't included in the specializations
  * will be serialized as primitive values.
  *
  * @param object specializations
  *    A dict of property names => type
  */
 types.addDictType = function(name, specializations) {
   return types.addType(name, {
+    category: "dict",
+    specializations: specializations,
     read: (v, ctx) => {
       let ret = {};
       for (let prop in v) {
         if (prop in specializations) {
           ret[prop] = types.getType(specializations[prop]).read(v[prop], ctx);
         } else {
           ret[prop] = v[prop];
         }
@@ -236,16 +239,17 @@ types.addDictType = function(name, speci
  * called during actor/front construction.
  *
  * @param string name
  *    The typestring to register.
  */
 types.addActorType = function(name) {
   let type = types.addType(name, {
     _actor: true,
+    category: "actor",
     read: (v, ctx, detail) => {
       // If we're reading a request on the server side, just
       // find the actor registered with this actorID.
       if (ctx instanceof Actor) {
         return ctx.conn.getActor(v);
       }
 
       // Reading a response on the client side, check for an
@@ -281,16 +285,17 @@ types.addActorType = function(name) {
     thawed: true
   });
   return type;
 }
 
 types.addNullableType = function(subtype) {
   subtype = types.getType(subtype);
   return types.addType("nullable:" + subtype.name, {
+    category: "nullable",
     read: (value, ctx) => {
       if (value == null) {
         return value;
       }
       return subtype.read(value, ctx);
     },
     write: (value, ctx) => {
       if (value == null) {
@@ -317,16 +322,17 @@ types.addNullableType = function(subtype
  */
 types.addActorDetail = function(name, actorType, detail) {
   actorType = types.getType(actorType);
   if (!actorType._actor) {
     throw Error("Details only apply to actor types, tried to add detail '" + detail + "'' to " + actorType.name + "\n");
   }
   return types.addType(name, {
     _actor: true,
+    category: "detail",
     read: (v, ctx) => actorType.read(v, ctx, detail),
     write: (v, ctx) => actorType.write(v, ctx, detail)
   });
 }
 
 /**
  * Register an actor lifetime.  This lets the type system find a parent
  * actor that differs from the actor fulfilling the request.
@@ -357,16 +363,17 @@ types.addLifetime = function(name, prop)
  */
 types.addLifetimeType = function(lifetime, subtype) {
   subtype = types.getType(subtype);
   if (!subtype._actor) {
     throw Error("Lifetimes only apply to actor types, tried to apply lifetime '" + lifetime + "'' to " + subtype.name);
   }
   let prop = registeredLifetimes.get(lifetime);
   return types.addType(lifetime + ":" + subtype.name, {
+    category: "lifetime",
     read: (value, ctx) => subtype.read(value, ctx[prop]),
     write: (value, ctx) => subtype.write(value, ctx[prop])
   })
 }
 
 // Add a few named primitive types.
 types.Primitive = types.addType("primitive");
 types.String = types.addType("string");
@@ -402,16 +409,23 @@ let Arg = Class({
   },
 
   write: function(arg, ctx) {
     return this.type.write(arg, ctx);
   },
 
   read: function(v, ctx, outArgs) {
     outArgs[this.index] = this.type.read(v, ctx);
+  },
+
+  describe: function() {
+    return {
+      _arg: this.index,
+      type: this.type.name,
+    }
   }
 });
 exports.Arg = Arg;
 
 /**
  * Placeholder for an options argument value that should be hoisted
  * into the packet.
  *
@@ -447,16 +461,23 @@ let Option = Class({
   read: function(v, ctx, outArgs, name) {
     if (outArgs[this.index] === undefined) {
       outArgs[this.index] = {};
     }
     if (v === undefined) {
       return;
     }
     outArgs[this.index][name] = this.type.read(v, ctx);
+  },
+
+  describe: function() {
+    return {
+      _option: this.index,
+      type: this.type.name,
+    }
   }
 });
 
 exports.Option = Option;
 
 /**
  * Placeholder for return values in a response template.
  *
@@ -469,16 +490,22 @@ let RetVal = Class({
   },
 
   write: function(v, ctx) {
     return this.type.write(v, ctx);
   },
 
   read: function(v, ctx) {
     return this.type.read(v, ctx);
+  },
+
+  describe: function() {
+    return {
+      _retval: this.type.name
+    }
   }
 });
 
 exports.RetVal = RetVal;
 
 /* Template handling functions */
 
 /**
@@ -513,16 +540,25 @@ function findPlaceholders(template, cons
     findPlaceholders(template[name], constructor, path, placeholders);
     path.pop();
   }
 
   return placeholders;
 }
 
 
+function describeTemplate(template) {
+  return JSON.parse(JSON.stringify(template, (key, value) => {
+    if (value.describe) {
+      return value.describe();
+    }
+    return value;
+  }));
+}
+
 /**
  * Manages a request template.
  *
  * @param object template
  *    The request template.
  * @construcor
  */
 let Request = Class({
@@ -565,16 +601,18 @@ let Request = Class({
     for (let templateArg of this.args) {
       let arg = templateArg.placeholder;
       let path = templateArg.path;
       let name = path[path.length - 1];
       arg.read(getPath(packet, path), ctx, fnArgs, name);
     }
     return fnArgs;
   },
+
+  describe: function() { return describeTemplate(this.template); }
 });
 
 /**
  * Manages a response template.
  *
  * @param object template
  *    The response template.
  * @construcor
@@ -619,17 +657,19 @@ let Response = Class({
    *    The object reading the response.
    */
   read: function(packet, ctx) {
     if (!this.retVal) {
       return undefined;
     }
     let v = getPath(packet, this.path);
     return this.retVal.read(v, ctx);
-  }
+  },
+
+  describe: function() { return describeTemplate(this.template); }
 });
 
 /**
  * Actor and Front implementations
  */
 
 /**
  * A protocol object that can manage the lifetime of other protocol
@@ -953,17 +993,20 @@ let actorProto = function(actorProto) {
 exports.ActorClass = function(proto) {
   if (!proto.typeName) {
     throw Error("Actor prototype must have a typeName member.");
   }
   proto.extends = Actor;
   if (!registeredTypes.has(proto.typeName)) {
     types.addActorType(proto.typeName);
   }
-  return Class(actorProto(proto));
+  let cls = Class(actorProto(proto));
+
+  registeredTypes.get(proto.typeName).actorSpec = proto._actorSpec;
+  return cls;
 };
 
 /**
  * Base class for client-side actor fronts.
  */
 let Front = Class({
   extends: Pool,
 
@@ -1234,8 +1277,62 @@ let frontProto = function(proto) {
  */
 exports.FrontClass = function(actorType, proto) {
   proto.actorType = actorType;
   proto.extends = Front;
   let cls = Class(frontProto(proto));
   registeredTypes.get(cls.prototype.typeName).frontClass = cls;
   return cls;
 }
+
+
+exports.dumpActorSpec = function(type) {
+  let actorSpec = type.actorSpec;
+  let ret = {
+    category: "actor",
+    typeName: type.name,
+    methods: [],
+    events: {}
+  };
+
+  for (let method of actorSpec.methods) {
+    ret.methods.push({
+      name: method.name,
+      release: method.release || undefined,
+      oneway: method.oneway || undefined,
+      request: method.request.describe(),
+      response: method.response.describe()
+    });
+  }
+
+  if (actorSpec.events) {
+    for (let [name, request] of actorSpec.events) {
+      ret.events[name] = request.describe();
+    }
+  }
+
+
+  JSON.stringify(ret);
+
+  return ret;
+}
+
+exports.dumpProtocolSpec = function() {
+  let ret = {
+    types: {},
+  };
+
+  for (let [name, type] of registeredTypes) {
+    // Force lazy instantiation if needed.
+    type = types.getType(name);
+    if (type.category === "dict") {
+      ret.types[name] = {
+        category: "dict",
+        typeName: name,
+        specializations: type.specializations
+      }
+    } else if (type.category === "actor") {
+      ret.types[name] = exports.dumpActorSpec(type);
+    }
+  }
+
+  return ret;
+}
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -30,16 +30,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
                                   "resource://gre/modules/PermissionsUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserToolboxProcess",
+                                  "resource:///modules/devtools/ToolboxProcess.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "ChromeRegistry",
                                    "@mozilla.org/chrome/chrome-registry;1",
                                    "nsIChromeRegistry");
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "ResProtocolHandler",
                                    "@mozilla.org/network/protocol;1?name=resource",
@@ -1865,16 +1867,24 @@ this.XPIProvider = {
       this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
                                                             null);
       this.enabledAddons = "";
 
       Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false);
       Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false);
       Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS, false);
 
+      try {
+        BrowserToolboxProcess.on("connectionchange",
+                                 this.onDebugConnectionChange.bind(this));
+      }
+      catch (e) {
+        // BrowserToolboxProcess is not available in all applications
+      }
+
       let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion,
                                              aOldPlatformVersion);
 
       // Changes to installed extensions may have changed which theme is selected
       this.applyThemeChange();
 
       // If the application has been upgraded and there are add-ons outside the
       // application directory then we may need to synchronize compatibility
@@ -3850,16 +3860,25 @@ this.XPIProvider = {
         logger.warn("Attempting to activate an already active default theme");
       }
     }
     else {
       logger.warn("Unable to activate the default theme");
     }
   },
 
+  onDebugConnectionChange: function(aEvent, aWhat, aConnection) {
+    if (aWhat != "opened")
+      return;
+
+    for (let id of Object.keys(this.bootstrapScopes)) {
+      aConnection.setAddonOptions(id, { global: this.bootstrapScopes[id] });
+    }
+  },
+
   /**
    * Notified when a preference we're interested in has changed.
    *
    * @see nsIObserver
    */
   observe: function XPI_observe(aSubject, aTopic, aData) {
     if (aTopic == NOTIFICATION_FLUSH_PERMISSIONS) {
       if (!aData || aData == XPI_PERMISSION) {
@@ -4114,30 +4133,44 @@ this.XPIProvider = {
       Components.utils.evalInSandbox(
         "Components.classes['@mozilla.org/moz/jssubscript-loader;1'] \
                    .createInstance(Components.interfaces.mozIJSSubScriptLoader) \
                    .loadSubScript(__SCRIPT_URI_SPEC__);", this.bootstrapScopes[aId], "ECMAv5");
     }
     catch (e) {
       logger.warn("Error loading bootstrap.js for " + aId, e);
     }
+
+    try {
+      BrowserToolboxProcess.setAddonOptions(aId, { global: this.bootstrapScopes[aId] });
+    }
+    catch (e) {
+      // BrowserToolboxProcess is not available in all applications
+    }
   },
 
   /**
    * Unloads a bootstrap scope by dropping all references to it and then
    * updating the list of active add-ons with the crash reporter.
    *
    * @param  aId
    *         The add-on's ID
    */
   unloadBootstrapScope: function XPI_unloadBootstrapScope(aId) {
     delete this.bootstrapScopes[aId];
     delete this.bootstrappedAddons[aId];
     this.persistBootstrappedAddons();
     this.addAddonsToCrashReporter();
+
+    try {
+      BrowserToolboxProcess.setAddonOptions(aId, { global: null });
+    }
+    catch (e) {
+      // BrowserToolboxProcess is not available in all applications
+    }
   },
 
   /**
    * Calls a bootstrap method for an add-on.
    *
    * @param  aId
    *         The ID of the add-on
    * @param  aVersion
--- a/toolkit/mozapps/extensions/test/browser/browser_experiments.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_experiments.js
@@ -1,55 +1,71 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+let {AddonTestUtils} = Components.utils.import("resource://testing-common/AddonManagerTesting.jsm", {});
+let {HttpServer} = Components.utils.import("resource://testing-common/httpd.js", {});
+
 let gManagerWindow;
 let gCategoryUtilities;
-let gInstalledAddons = [];
-let gContext = this;
+let gExperiments;
+let gHttpServer;
+
+function getExperimentAddons() {
+  let deferred = Promise.defer();
+  AddonManager.getAddonsByTypes(["experiment"], (addons) => {
+    deferred.resolve(addons);
+  });
+  return deferred.promise;
+}
 
 add_task(function* initializeState() {
   gManagerWindow = yield open_manager();
   gCategoryUtilities = new CategoryUtilities(gManagerWindow);
 
+  registerCleanupFunction(() => {
+    if (gHttpServer) {
+      gHttpServer.stop(() => {});
+    }
+  });
+
   // The Experiments Manager will interfere with us by preventing installs
   // of experiments it doesn't know about. We remove it from the equation
   // because here we are only concerned with core Addon Manager operation,
   // not the superset Experiments Manager has imposed.
   if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
-    Components.utils.import("resource:///modules/experiments/Experiments.jsm", gContext);
-
+    let tmp = {};
+    Components.utils.import("resource:///modules/experiments/Experiments.jsm", tmp);
     // There is a race condition between XPCOM service initialization and
     // this test running. We have to initialize the instance first, then
     // uninitialize it to prevent this.
-    let instance = gContext.Experiments.instance();
-    yield instance.uninit();
+    gExperiments = tmp.Experiments.instance();
+    yield gExperiments._mainTask;
+    yield gExperiments.uninit();
   }
 });
 
 // On an empty profile with no experiments, the experiment category
 // should be hidden.
 add_task(function* testInitialState() {
   Assert.ok(gCategoryUtilities.get("experiment", false), "Experiment tab is defined.");
-
   Assert.ok(!gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab hidden by default.");
 });
 
 add_task(function* testExperimentInfoNotVisible() {
   yield gCategoryUtilities.openType("extension");
   let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
   is_element_hidden(el, "Experiment info not visible on other types.");
 });
 
 // If we have an active experiment, we should see the experiments tab
 // and that tab should have some messages.
 add_task(function* testActiveExperiment() {
   let addon = yield install_addon("addons/browser_experiment1.xpi");
-  gInstalledAddons.push(addon);
 
   Assert.ok(addon.userDisabled, "Add-on is disabled upon initial install.");
   Assert.equal(addon.isActive, false, "Add-on is not active.");
 
   Assert.ok(gCategoryUtilities.isTypeVisible("experiment"), "Experiment tab visible.");
 
   yield gCategoryUtilities.openType("experiment");
   let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
@@ -138,19 +154,132 @@ add_task(function* testButtonPresence() 
   // Corresponds to lack of disable permission.
   el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "disable-btn");
   is_element_hidden(el, "Disable button not visible.");
   // Corresponds to lack of enable permission.
   el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "enable-btn");
   is_element_hidden(el, "Enable button not visible.");
 });
 
+// Remove the add-on we've been testing with.
 add_task(function* testCleanup() {
-  for (let addon of gInstalledAddons) {
-    addon.uninstall();
+  yield AddonTestUtils.uninstallAddonByID("test-experiment1@experiments.mozilla.org");
+  // Verify some conditions, just in case.
+  let addons = yield getExperimentAddons();
+  Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
+});
+
+// We need to initialize the experiments service for the following tests.
+add_task(function* initializeExperiments() {
+  if (!gExperiments) {
+    return;
+  }
+
+  // We need to remove the cache file to help ensure consistent state.
+  yield OS.File.remove(gExperiments._cacheFilePath);
+
+  info("Initializing experiments service.");
+  yield gExperiments.init();
+  info("Experiments service finished first run.");
+
+  // Check conditions, just to be sure.
+  let experiments = yield gExperiments.getExperiments();
+  Assert.equal(experiments.length, 0, "No experiments known to the service.");
+});
+
+// The following tests should ideally live in browser/experiments/. However,
+// they rely on some of the helper functions from head.js, which can't easily
+// be consumed from other directories. So, they live here.
+
+add_task(function* testActivateExperiment() {
+  if (!gExperiments) {
+    info("Skipping experiments test because that feature isn't available.");
+    return;
   }
 
+  gHttpServer = new HttpServer();
+  gHttpServer.start(-1);
+  let root = "http://localhost:" + gHttpServer.identity.primaryPort + "/";
+  gHttpServer.registerPathHandler("/manifest", (request, response) => {
+    response.setStatusLine(null, 200, "OK");
+    response.write(JSON.stringify({
+      "version": 1,
+      "experiments": [
+        {
+          id: "experiment-1",
+          xpiURL: TESTROOT + "addons/browser_experiment1.xpi",
+          xpiHash: "IRRELEVANT",
+          startTime: Date.now() / 1000 - 3600,
+          endTime: Date.now() / 1000 + 3600,
+          maxActiveSeconds: 600,
+          appName: [Services.appinfo.name],
+          channel: [gExperiments._policy.updatechannel()],
+        },
+      ],
+    }));
+    response.processAsync();
+    response.finish();
+  });
+
+  Services.prefs.setBoolPref("experiments.manifest.cert.checkAttributes", false);
+  Services.prefs.setCharPref("experiments.manifest.uri", root + "manifest");
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("experiments.manifest.cert.checkAttributes");
+    Services.prefs.clearUserPref("experiments.manifest.uri");
+  });
+
+  // This makes testing easier.
+  gExperiments._policy.ignoreHashes = true;
+  registerCleanupFunction(() => { gExperiments._policy.ignoreHashes = false; });
+
+  info("Manually updating experiments manifest.");
+  yield gExperiments.updateManifest();
+  info("Experiments update complete.");
+
+  let deferred = Promise.defer();
+  gHttpServer.stop(() => {
+    gHttpServer = null;
+
+    info("getting experiment by ID");
+    AddonManager.getAddonByID("test-experiment1@experiments.mozilla.org", (addon) => {
+      Assert.ok(addon, "Add-on installed via Experiments manager.");
+
+      deferred.resolve();
+    });
+  });
+
+  yield deferred.promise;
+
+  Assert.ok(gCategoryUtilities.isTypeVisible, "experiment", "Experiment tab visible.");
+  yield gCategoryUtilities.openType("experiment");
+  let el = gManagerWindow.document.getElementsByClassName("experiment-info-container")[0];
+  is_element_visible(el, "Experiment info is visible on experiment tab.");
+});
+
+add_task(function testDeactivateExperiment() {
+  if (!gExperiments) {
+    return;
+  }
+
+  yield gExperiments._updateExperiments({
+    "version": 1,
+    "experiments": [],
+  });
+
+  yield gExperiments.disableExperiment("testing");
+});
+
+add_task(function* testCleanup() {
+  if (gExperiments) {
+    // We perform the uninit/init cycle to purge any leftover state.
+    yield OS.File.remove(gExperiments._cacheFilePath);
+    yield gExperiments.uninit();
+    yield gExperiments.init();
+  }
+
+  // Check post-conditions.
+  let addons = yield getExperimentAddons();
+  Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
+
   yield close_manager(gManagerWindow);
 
-  if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
-    yield gContext.Experiments.instance().init();
-  }
 });
+
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/installer/windows/nsis/locale-fonts.nsh
@@ -0,0 +1,681 @@
+# 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/.
+
+; Acholi
+!if "${AB_CD}" == "ach"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Afrikaans
+!if "${AB_CD}" == "af"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Akan
+!if "${AB_CD}" == "ak"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Aragonese
+!if "${AB_CD}" == "an"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Arabic
+!if "${AB_CD}" == "ar"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Assamese
+!if "${AB_CD}" == "as"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Asturian
+!if "${AB_CD}" == "ast"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Azerbaijani
+!if "${AB_CD}" == "az"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Belarusian
+!if "${AB_CD}" == "be"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Bulgarian
+!if "${AB_CD}" == "bg"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Bengali
+!if "${AB_CD}" == "bn-BD"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Bengali - India
+!if "${AB_CD}" == "bn-IN"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Breton
+!if "${AB_CD}" == "br"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Bosnian
+!if "${AB_CD}" == "bs"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Catalan
+!if "${AB_CD}" == "ca"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Czech
+!if "${AB_CD}" == "cs"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Kashubian
+!if "${AB_CD}" == "csb"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Welsh
+!if "${AB_CD}" == "cy"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Danish
+!if "${AB_CD}" == "da"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; German
+!if "${AB_CD}" == "de"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Greek
+!if "${AB_CD}" == "el"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; English - Great Britain
+!if "${AB_CD}" == "en-GB"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; English - United States
+!if "${AB_CD}" == "en-US"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; English - South Africa
+!if "${AB_CD}" == "en-ZA"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Esperanto
+!if "${AB_CD}" == "eo"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Spanish - Argentina
+!if "${AB_CD}" == "es-AR"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Spanish - Chile
+!if "${AB_CD}" == "es-CL"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Spanish
+!if "${AB_CD}" == "es-ES"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Spanish - Mexico
+!if "${AB_CD}" == "es-MX"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Estonian
+!if "${AB_CD}" == "et"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Basque
+!if "${AB_CD}" == "eu"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Persian
+!if "${AB_CD}" == "fa"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Fulah
+!if "${AB_CD}" == "ff"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Finnish
+!if "${AB_CD}" == "fi"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; French
+!if "${AB_CD}" == "fr"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Frisian
+!if "${AB_CD}" == "fy-NL"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Irish
+!if "${AB_CD}" == "ga-IE"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Scottish Gaelic
+!if "${AB_CD}" == "gd"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Galician
+!if "${AB_CD}" == "gl"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Gujarati
+!if "${AB_CD}" == "gu-IN"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Hawaiian
+!if "${AB_CD}" == "haw"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Hebrew
+!if "${AB_CD}" == "he"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; hindi
+!if "${AB_CD}" == "hi-IN"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Croatian
+!if "${AB_CD}" == "hr"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Upper Sorbian
+!if "${AB_CD}" == "hsb"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Hungarian
+!if "${AB_CD}" == "hu"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Armenian
+!if "${AB_CD}" == "hy-AM"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Indonesian
+!if "${AB_CD}" == "id"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Iloko
+!if "${AB_CD}" == "ilo"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Icelandic
+!if "${AB_CD}" == "is"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Italian
+!if "${AB_CD}" == "it"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Japanese
+!if "${AB_CD}" == "ja"
+!define FONT_NAME1 "Meiryo UI"
+!define FONT_FILE1 "meiryo.ttc"
+!endif
+
+; Georgian
+!if "${AB_CD}" == "ka"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Kazakh
+!if "${AB_CD}" == "kk"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Khmer
+!if "${AB_CD}" == "km"
+!define FONT_NAME1 "Leelawadee UI"
+!define FONT_FILE1 "LeelawUI.ttf"
+!endif
+
+; Kannada
+!if "${AB_CD}" == "kn"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Korean
+!if "${AB_CD}" == "ko"
+!define FONT_NAME1 "Malgun Gothic"
+!define FONT_FILE1 "malgun.ttf"
+!endif
+
+; Kurdish
+!if "${AB_CD}" == "ku"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Luganda
+!if "${AB_CD}" == "lg"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Ligurian
+!if "${AB_CD}" == "lij"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Lithuanian
+!if "${AB_CD}" == "lt"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Latvian
+!if "${AB_CD}" == "lv"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Maithili
+!if "${AB_CD}" == "mai"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Macedonian
+!if "${AB_CD}" == "mk"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Malayalam
+!if "${AB_CD}" == "ml"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Mongolian
+!if "${AB_CD}" == "mn"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Marathi
+!if "${AB_CD}" == "mr"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Malay
+!if "${AB_CD}" == "ms"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Burmese
+!if "${AB_CD}" == "my"
+!define FONT_NAME1 "Myanmar Text"
+!define FONT_FILE1 "mmrtext.ttf"
+!endif
+
+; Norwegian Bokmål
+!if "${AB_CD}" == "nb-NO"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Nepali Nepal
+!if "${AB_CD}" == "ne-NP"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Dutch
+!if "${AB_CD}" == "nl"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Norwegian Nynorsk
+!if "${AB_CD}" == "nn-NO"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Southern Ndebele
+!if "${AB_CD}" == "nr"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Northern Sotho
+!if "${AB_CD}" == "nso"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Occitan
+!if "${AB_CD}" == "oc"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Oriya
+!if "${AB_CD}" == "or"
+!define FONT_NAME1 "Kalinga"
+!define FONT_FILE1 "kalinga.ttf"
+!endif
+
+; Punjabi
+!if "${AB_CD}" == "pa-IN"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Polish
+!if "${AB_CD}" == "pl"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Portugese - Brazil
+!if "${AB_CD}" == "pt-BR"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Portugese
+!if "${AB_CD}" == "pt-PT"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Romansh
+!if "${AB_CD}" == "rm"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Romanian
+!if "${AB_CD}" == "ro"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Russian
+!if "${AB_CD}" == "ru"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Kinyarwanda
+!if "${AB_CD}" == "rw"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Sakha
+!if "${AB_CD}" == "sah"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Sinhala
+!if "${AB_CD}" == "si"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Slovak
+!if "${AB_CD}" == "sk"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Slovene
+!if "${AB_CD}" == "sl"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Songhay
+!if "${AB_CD}" == "son"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Albanian
+!if "${AB_CD}" == "sq"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Serbian
+!if "${AB_CD}" == "sr"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Swazi
+!if "${AB_CD}" == "ss"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Southern Sotho
+!if "${AB_CD}" == "st"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Swedish
+!if "${AB_CD}" == "sv-Se"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Swahili
+!if "${AB_CD}" == "sw"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Tamil
+!if "${AB_CD}" == "ta"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Tamil - Sri Lanka
+!if "${AB_CD}" == "ta-LK"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Telugu
+!if "${AB_CD}" == "te"
+!define FONT_NAME1 "Nirmala UI"
+!define FONT_FILE1 "Nirmala.ttf"
+!endif
+
+; Thai
+!if "${AB_CD}" == "th"
+!define FONT_NAME1 "Leelawadee UI"
+!define FONT_FILE1 "LeelawUI.ttf"
+!endif
+
+; Tswana
+!if "${AB_CD}" == "tn"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Turkish
+!if "${AB_CD}" == "tr"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Tsonga
+!if "${AB_CD}" == "ts"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Uyghur
+!if "${AB_CD}" == "ug"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Ukrainian
+!if "${AB_CD}" == "uk"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Urdu
+!if "${AB_CD}" == "ur"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Venda
+!if "${AB_CD}" == "ve"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Vietnamese
+!if "${AB_CD}" == "vi"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Wolof
+!if "${AB_CD}" == "wo"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Xhosa
+!if "${AB_CD}" == "xh"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
+
+; Chinese (Simplified)
+!if "${AB_CD}" == "zh-CN"
+!define FONT_NAME1 "Microsoft YaHei UI"
+!define FONT_FILE1 "msyh.ttc"
+!endif
+
+; Chinese (Traditional)
+!if "${AB_CD}" == "zh-TW"
+!define FONT_NAME1 "Microsoft JhengHei UI"
+!define FONT_FILE1 "msjh.ttc"
+!endif
+
+; Zulu
+!if "${AB_CD}" == "zu"
+!define FONT_NAME1 "Segoe UI"
+!define FONT_FILE1 "segoeui.ttf"
+!endif
--- a/toolkit/mozapps/installer/windows/nsis/makensis.mk
+++ b/toolkit/mozapps/installer/windows/nsis/makensis.mk
@@ -10,16 +10,17 @@ include $(MOZILLA_DIR)/toolkit/mozapps/i
 
 ABS_CONFIG_DIR := $(abspath $(CONFIG_DIR))
 
 SFX_MODULE ?= $(error SFX_MODULE is not defined)
 
 TOOLKIT_NSIS_FILES = \
 	common.nsh \
 	locale.nlf \
+	locale-fonts.nsh \
 	locale-rtl.nlf \
 	locales.nsi \
 	overrides.nsh \
 	setup.ico \
 	$(NULL)
 
 CUSTOM_NSIS_PLUGINS = \
 	AccessControl.dll \