Merge m-c to m-i
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 06 Feb 2016 19:14:43 -0800
changeset 283388 1becfdc20e92fc1985ffcbc7b3f8130e49a4b568
parent 283387 64305a23963a6ce00cc88f6eba95b207e54575ce (current diff)
parent 283382 76733110704b975154ac0fa779445e6eae5da559 (diff)
child 283389 b2d2408648755506d0e57b14f1d926d981ca695d
push id29980
push userphilringnalda@gmail.com
push dateSun, 07 Feb 2016 23:30:48 +0000
treeherdermozilla-central@1cfe34ea394c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to m-i
mobile/android/docs/gradle.rst
toolkit/locales/en-US/chrome/mozapps/extensions/selectAddons.dtd
toolkit/locales/en-US/chrome/mozapps/extensions/selectAddons.properties
toolkit/mozapps/extensions/content/selectAddons.css
toolkit/mozapps/extensions/content/selectAddons.js
toolkit/mozapps/extensions/content/selectAddons.xml
toolkit/mozapps/extensions/content/selectAddons.xul
toolkit/mozapps/extensions/test/browser/addons/browser_select_compatoverrides_1/install.rdf
toolkit/mozapps/extensions/test/browser/browser_select_compatoverrides.js
toolkit/mozapps/extensions/test/browser/browser_select_compatoverrides.xml
toolkit/mozapps/extensions/test/browser/browser_select_confirm.js
toolkit/mozapps/extensions/test/browser/browser_select_selection.js
toolkit/mozapps/extensions/test/browser/browser_select_update.js
toolkit/mozapps/extensions/test/xpcshell/test_bug596343.js
toolkit/themes/linux/mozapps/extensions/selectAddons.css
toolkit/themes/osx/mozapps/extensions/selectAddons.css
toolkit/themes/windows/mozapps/extensions/selectAddons.css
--- a/.eslintignore
+++ b/.eslintignore
@@ -77,17 +77,16 @@ browser/extensions/pocket/content/panels
 browser/extensions/shumway/**
 browser/locales/**
 
 # Ignore all of loop since it is imported from github and checked at source.
 browser/extensions/loop/**
 
 # devtools/ exclusions
 devtools/*.js
-devtools/client/aboutdebugging/**
 devtools/client/animationinspector/**
 devtools/client/canvasdebugger/**
 devtools/client/commandline/**
 devtools/client/debugger/**
 devtools/client/eyedropper/**
 devtools/client/framework/**
 # devtools/client/inspector/shared/*.js files are eslint-clean, so they aren't
 # included in the ignore list.
--- a/browser/components/extensions/ext-bookmarks.js
+++ b/browser/components/extensions/ext-bookmarks.js
@@ -3,19 +3,16 @@
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 var Bookmarks = PlacesUtils.bookmarks;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
-var {
-  runSafe,
-} = ExtensionUtils;
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
 function getTree(rootGuid, onlyChildren) {
   function convert(node, parent) {
     let treenode = {
       id: node.guid,
@@ -80,68 +77,48 @@ function convert(result) {
   }
 
   return node;
 }
 
 extensions.registerSchemaAPI("bookmarks", "bookmarks", (extension, context) => {
   return {
     bookmarks: {
-      get: function(idOrIdList, callback) {
+      get: function(idOrIdList) {
         let list = Array.isArray(idOrIdList) ? idOrIdList : [idOrIdList];
 
-        Task.spawn(function* () {
+        return Task.spawn(function* () {
           let bookmarks = [];
           for (let id of list) {
-            let bookmark;
-            try {
-              bookmark = yield Bookmarks.fetch({guid: id});
-              if (!bookmark) {
-                // TODO: set lastError, not found
-                return [];
-              }
-            } catch (e) {
-              // TODO: set lastError, probably an invalid guid
-              return [];
+            let bookmark = yield Bookmarks.fetch({guid: id});
+            if (!bookmark) {
+              throw new Error("Bookmark not found");
             }
             bookmarks.push(convert(bookmark));
           }
           return bookmarks;
-        }).then(results => runSafe(context, callback, results));
-      },
-
-      getChildren: function(id, callback) {
-        // TODO: We should optimize this.
-        getTree(id, true).then(result => {
-          runSafe(context, callback, result);
-        }, reason => {
-          // TODO: Set lastError
-          runSafe(context, callback, []);
         });
       },
 
-      getTree: function(callback) {
-        getTree(Bookmarks.rootGuid, false).then(result => {
-          runSafe(context, callback, result);
-        }, reason => {
-          runSafe(context, callback, []);
-        });
+      getChildren: function(id) {
+        // TODO: We should optimize this.
+        return getTree(id, true);
       },
 
-      getSubTree: function(id, callback) {
-        getTree(id, false).then(result => {
-          runSafe(context, callback, result);
-        }, reason => {
-          runSafe(context, callback, []);
-        });
+      getTree: function() {
+        return getTree(Bookmarks.rootGuid, false);
+      },
+
+      getSubTree: function(id) {
+        return getTree(id, false);
       },
 
       // search
 
-      create: function(bookmark, callback) {
+      create: function(bookmark) {
         let info = {
           title: bookmark.title || "",
         };
 
         // If url is NULL or missing, it will be a folder.
         if (bookmark.url !== null) {
           info.type = Bookmarks.TYPE_BOOKMARK;
           info.url = bookmark.url || "";
@@ -154,111 +131,69 @@ extensions.registerSchemaAPI("bookmarks"
         }
 
         if (bookmark.parentId !== null) {
           info.parentGuid = bookmark.parentId;
         } else {
           info.parentGuid = Bookmarks.unfiledGuid;
         }
 
-        let failure = reason => {
-          // TODO: set lastError.
-          if (callback) {
-            runSafe(context, callback, null);
-          }
-        };
-
         try {
-          Bookmarks.insert(info).then(result => {
-            if (callback) {
-              runSafe(context, callback, convert(result));
-            }
-          }, failure);
+          return Bookmarks.insert(info).then(convert);
         } catch (e) {
-          failure(e);
+          return Promise.reject({ message: `Invalid bookmark: ${JSON.stringify(info)}` });
         }
       },
 
-      move: function(id, destination, callback) {
+      move: function(id, destination) {
         let info = {
           guid: id,
         };
 
         if (destination.parentId !== null) {
           info.parentGuid = destination.parentId;
         }
         if (destination.index !== null) {
           info.index = destination.index;
         }
 
-        let failure = reason => {
-          if (callback) {
-            runSafe(context, callback, null);
-          }
-        };
-
         try {
-          Bookmarks.update(info).then(result => {
-            if (callback) {
-              runSafe(context, callback, convert(result));
-            }
-          }, failure);
+          return Bookmarks.update(info).then(convert);
         } catch (e) {
-          failure(e);
+          return Promise.reject({ message: `Invalid bookmark: ${JSON.stringify(info)}` });
         }
       },
 
-      update: function(id, changes, callback) {
+      update: function(id, changes) {
         let info = {
           guid: id,
         };
 
         if (changes.title !== null) {
           info.title = changes.title;
         }
         if (changes.url !== null) {
           info.url = changes.url;
         }
 
-        let failure = reason => {
-          if (callback) {
-            runSafe(context, callback, null);
-          }
-        };
-
         try {
-          Bookmarks.update(info).then(result => {
-            if (callback) {
-              runSafe(context, callback, convert(result));
-            }
-          }, failure);
+          return Bookmarks.update(info).then(convert);
         } catch (e) {
-          failure(e);
+          return Promise.reject({ message: `Invalid bookmark: ${JSON.stringify(info)}` });
         }
       },
 
-      remove: function(id, callback) {
+      remove: function(id) {
         let info = {
           guid: id,
         };
 
-        let failure = reason => {
-          if (callback) {
-            runSafe(context, callback, null);
-          }
-        };
-
+        // The API doesn't give you the old bookmark at the moment
         try {
-          Bookmarks.remove(info).then(result => {
-            if (callback) {
-              // The API doesn't give you the old bookmark at the moment
-              runSafe(context, callback);
-            }
-          }, failure);
+          return Bookmarks.remove(info).then(result => {});
         } catch (e) {
-          failure(e);
+          return Promise.reject({ message: `Invalid bookmark: ${JSON.stringify(info)}` });
         }
       },
     },
   };
 });
 
-
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -5,17 +5,16 @@
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 
 Cu.import("resource://devtools/shared/event-emitter.js");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
-  runSafe,
 } = ExtensionUtils;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 // WeakMap[Extension -> BrowserAction]
 var browserActionMap = new WeakMap();
 
 function browserActionOf(extension) {
@@ -256,61 +255,62 @@ extensions.registerSchemaAPI("browserAct
         let title = details.title;
         // Clear the tab-specific title when given a null string.
         if (tab && title == "") {
           title = null;
         }
         browserActionOf(extension).setProperty(tab, "title", title);
       },
 
-      getTitle: function(details, callback) {
+      getTitle: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
         let title = browserActionOf(extension).getProperty(tab, "title");
-        runSafe(context, callback, title);
+        return Promise.resolve(title);
       },
 
-      setIcon: function(details, callback) {
+      setIcon: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
         let icon = IconDetails.normalize(details, extension, context);
         browserActionOf(extension).setProperty(tab, "icon", icon);
+        return Promise.resolve();
       },
 
       setBadgeText: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
         browserActionOf(extension).setProperty(tab, "badgeText", details.text);
       },
 
-      getBadgeText: function(details, callback) {
+      getBadgeText: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
         let text = browserActionOf(extension).getProperty(tab, "badgeText");
-        runSafe(context, callback, text);
+        return Promise.resolve(text);
       },
 
       setPopup: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
         // Note: Chrome resolves arguments to setIcon relative to the calling
         // context, but resolves arguments to setPopup relative to the extension
         // root.
         // For internal consistency, we currently resolve both relative to the
         // calling context.
         let url = details.popup && context.uri.resolve(details.popup);
         browserActionOf(extension).setProperty(tab, "popup", url);
       },
 
-      getPopup: function(details, callback) {
+      getPopup: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
         let popup = browserActionOf(extension).getProperty(tab, "popup");
-        runSafe(context, callback, popup);
+        return Promise.resolve(popup);
       },
 
       setBadgeBackgroundColor: function(details) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
         browserActionOf(extension).setProperty(tab, "badgeBackgroundColor", details.color);
       },
 
       getBadgeBackgroundColor: function(details, callback) {
         let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
         let color = browserActionOf(extension).getProperty(tab, "badgeBackgroundColor");
-        runSafe(context, callback, color);
+        return Promise.resolve(color);
       },
     },
   };
 });
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -422,44 +422,38 @@ extensions.registerSchemaAPI("contextMen
         let menuItem = new MenuItem(extension, context, createProperties);
         contextMenuMap.get(extension).set(menuItem.id, menuItem);
         if (callback) {
           runSafe(context, callback);
         }
         return menuItem.id;
       },
 
-      update: function(id, updateProperties, callback) {
+      update: function(id, updateProperties) {
         let menuItem = contextMenuMap.get(extension).get(id);
         if (menuItem) {
           menuItem.setProps(updateProperties);
         }
-        if (callback) {
-          runSafe(context, callback);
-        }
+        return Promise.resolve();
       },
 
-      remove: function(id, callback) {
+      remove: function(id) {
         let menuItem = contextMenuMap.get(extension).get(id);
         if (menuItem) {
           menuItem.remove();
         }
-        if (callback) {
-          runSafe(context, callback);
-        }
+        return Promise.resolve();
       },
 
-      removeAll: function(callback) {
+      removeAll: function() {
         let root = rootItems.get(extension);
         if (root) {
           root.remove();
         }
-        if (callback) {
-          runSafe(context, callback);
-        }
+        return Promise.resolve();
       },
 
       // TODO: implement this once event pages are ready.
       onClicked: new EventManager(context, "contextMenus.onClicked", fire => {
         let callback = menuItem => {
           fire(menuItem.data);
         };
 
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -1,16 +1,15 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
-  runSafe,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> PageAction]
 var pageActionMap = new WeakMap();
 
 
 // Handles URL bar icons, including the |page_action| manifest entry
 // and associated API.
@@ -218,39 +217,40 @@ extensions.registerSchemaAPI("pageAction
 
       setTitle(details) {
         let tab = TabManager.getTab(details.tabId);
 
         // Clear the tab-specific title when given a null string.
         PageAction.for(extension).setProperty(tab, "title", details.title || null);
       },
 
-      getTitle(details, callback) {
+      getTitle(details) {
         let tab = TabManager.getTab(details.tabId);
         let title = PageAction.for(extension).getProperty(tab, "title");
-        runSafe(context, callback, title);
+        return Promise.resolve(title);
       },
 
-      setIcon(details, callback) {
+      setIcon(details) {
         let tab = TabManager.getTab(details.tabId);
         let icon = IconDetails.normalize(details, extension, context);
         PageAction.for(extension).setProperty(tab, "icon", icon);
+        return Promise.resolve();
       },
 
       setPopup(details) {
         let tab = TabManager.getTab(details.tabId);
         // Note: Chrome resolves arguments to setIcon relative to the calling
         // context, but resolves arguments to setPopup relative to the extension
         // root.
         // For internal consistency, we currently resolve both relative to the
         // calling context.
         let url = details.popup && context.uri.resolve(details.popup);
         PageAction.for(extension).setProperty(tab, "popup", url);
       },
 
-      getPopup(details, callback) {
+      getPopup(details) {
         let tab = TabManager.getTab(details.tabId);
         let popup = PageAction.for(extension).getProperty(tab, "popup");
-        runSafe(context, callback, popup);
+        return Promise.resolve(popup);
       },
     },
   };
 });
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -12,17 +12,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   EventManager,
   ignoreEvent,
-  runSafe,
 } = ExtensionUtils;
 
 // This function is pretty tightly tied to Extension.jsm.
 // Its job is to fill in the |tab| property of the sender.
 function getSender(context, target, sender) {
   // The message was sent from a content script to a <browser> element.
   // We can just get the |tab| from |target|.
   if (target instanceof Ci.nsIDOMXULElement) {
@@ -263,82 +262,79 @@ extensions.registerSchemaAPI("tabs", nul
         WindowListManager.addCloseListener(windowListener);
         AllWindowEvents.addListener("TabClose", tabListener);
         return () => {
           WindowListManager.removeCloseListener(windowListener);
           AllWindowEvents.removeListener("TabClose", tabListener);
         };
       }).api(),
 
-      create: function(createProperties, callback) {
-        function createInWindow(window) {
-          let url;
-          if (createProperties.url !== null) {
-            url = context.uri.resolve(createProperties.url);
-          } else {
-            url = window.BROWSER_NEW_TAB_URL;
-          }
+      create: function(createProperties) {
+        return new Promise(resolve => {
+          function createInWindow(window) {
+            let url;
+            if (createProperties.url !== null) {
+              url = context.uri.resolve(createProperties.url);
+            } else {
+              url = window.BROWSER_NEW_TAB_URL;
+            }
+
+            let tab = window.gBrowser.addTab(url);
 
-          let tab = window.gBrowser.addTab(url);
-          let active = true;
+            let active = true;
+            if (createProperties.active !== null) {
+              active = createProperties.active;
+            }
+            if (active) {
+              window.gBrowser.selectedTab = tab;
+            }
 
-          if (createProperties.active !== null) {
-            active = createProperties.active;
-          }
-          if (active) {
-            window.gBrowser.selectedTab = tab;
-          }
+            if (createProperties.index !== null) {
+              window.gBrowser.moveTabTo(tab, createProperties.index);
+            }
 
-          if (createProperties.index !== null) {
-            window.gBrowser.moveTabTo(tab, createProperties.index);
+            if (createProperties.pinned) {
+              window.gBrowser.pinTab(tab);
+            }
+
+            resolve(TabManager.convert(extension, tab));
           }
 
-          if (createProperties.pinned) {
-            window.gBrowser.pinTab(tab);
-          }
-
-          if (callback) {
-            runSafe(context, callback, TabManager.convert(extension, tab));
+          let window = createProperties.windowId !== null ?
+            WindowManager.getWindow(createProperties.windowId) :
+            WindowManager.topWindow;
+          if (!window.gBrowser) {
+            let obs = (finishedWindow, topic, data) => {
+              if (finishedWindow != window) {
+                return;
+              }
+              Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
+              createInWindow(window);
+            };
+            Services.obs.addObserver(obs, "browser-delayed-startup-finished", false);
+          } else {
+            createInWindow(window);
           }
-        }
-
-        let window = createProperties.windowId !== null ?
-          WindowManager.getWindow(createProperties.windowId) :
-          WindowManager.topWindow;
-
-        if (!window.gBrowser) {
-          let obs = (finishedWindow, topic, data) => {
-            if (finishedWindow != window) {
-              return;
-            }
-            Services.obs.removeObserver(obs, "browser-delayed-startup-finished");
-            createInWindow(window);
-          };
-          Services.obs.addObserver(obs, "browser-delayed-startup-finished", false);
-        } else {
-          createInWindow(window);
-        }
+        });
       },
 
-      remove: function(tabs, callback) {
+      remove: function(tabs) {
         if (!Array.isArray(tabs)) {
           tabs = [tabs];
         }
 
         for (let tabId of tabs) {
           let tab = TabManager.getTab(tabId);
           tab.ownerDocument.defaultView.gBrowser.removeTab(tab);
         }
 
-        if (callback) {
-          runSafe(context, callback);
-        }
+        return Promise.resolve();
       },
 
-      update: function(tabId, updateProperties, callback) {
+      update: function(tabId, updateProperties) {
         let tab = tabId !== null ? TabManager.getTab(tabId) : TabManager.activeTab;
         let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
         if (updateProperties.url !== null) {
           tab.linkedBrowser.loadURI(updateProperties.url);
         }
         if (updateProperties.active !== null) {
           if (updateProperties.active) {
             tabbrowser.selectedTab = tab;
@@ -355,56 +351,52 @@ extensions.registerSchemaAPI("tabs", nul
           if (updateProperties.pinned) {
             tabbrowser.pinTab(tab);
           } else {
             tabbrowser.unpinTab(tab);
           }
         }
         // FIXME: highlighted/selected, openerTabId
 
-        if (callback) {
-          runSafe(context, callback, TabManager.convert(extension, tab));
-        }
+        return Promise.resolve(TabManager.convert(extension, tab));
       },
 
-      reload: function(tabId, reloadProperties, callback) {
+      reload: function(tabId, reloadProperties) {
         let tab = tabId !== null ? TabManager.getTab(tabId) : TabManager.activeTab;
         let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
         if (reloadProperties && reloadProperties.bypassCache) {
           flags |= Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
         }
         tab.linkedBrowser.reloadWithFlags(flags);
 
-        if (callback) {
-          runSafe(context, callback);
-        }
+        return Promise.resolve();
       },
 
-      get: function(tabId, callback) {
+      get: function(tabId) {
         let tab = TabManager.getTab(tabId);
-        runSafe(context, callback, TabManager.convert(extension, tab));
+        return Promise.resolve(TabManager.convert(extension, tab));
       },
 
-      getCurrent(callback) {
+      getCurrent() {
         let tab;
         if (context.tabId) {
           tab = TabManager.convert(extension, TabManager.getTab(context.tabId));
         }
-        runSafe(context, callback, tab);
+        return Promise.resolve(tab);
       },
 
-      getAllInWindow: function(windowId, callback) {
+      getAllInWindow: function(windowId) {
         if (windowId === null) {
           windowId = WindowManager.topWindow.windowId;
         }
 
-        return self.tabs.query({windowId}, callback);
+        return self.tabs.query({windowId});
       },
 
-      query: function(queryInfo, callback) {
+      query: function(queryInfo) {
         let pattern = null;
         if (queryInfo.url !== null) {
           pattern = new MatchPattern(queryInfo.url);
         }
 
         function matches(window, tab) {
           let props = ["active", "pinned", "highlighted", "status", "title", "index"];
           for (let prop of props) {
@@ -463,22 +455,22 @@ extensions.registerSchemaAPI("tabs", nul
         for (let window of WindowListManager.browserWindows()) {
           let tabs = TabManager.for(extension).getTabs(window);
           for (let tab of tabs) {
             if (matches(window, tab)) {
               result.push(tab);
             }
           }
         }
-        runSafe(context, callback, result);
+        return Promise.resolve(result);
       },
 
       captureVisibleTab: function(windowId, options) {
         if (!extension.hasPermission("<all_urls>")) {
-          throw new context.contentWindow.Error("The <all_urls> permission is required to use the captureVisibleTab API");
+          return Promise.reject({ message: "The <all_urls> permission is required to use the captureVisibleTab API" });
         }
 
         let window = windowId == null ?
           WindowManager.topWindow :
           WindowManager.getWindow(windowId);
 
         let browser = window.gBrowser.selectedBrowser;
         let recipient = {
@@ -582,17 +574,17 @@ extensions.registerSchemaAPI("tabs", nul
 
         let recipient = {extensionId: extension.id};
         if (options && options.frameId !== null) {
           recipient.frameId = options.frameId;
         }
         return context.messenger.sendMessage(mm, message, recipient, responseCallback);
       },
 
-      move: function(tabIds, moveProperties, callback) {
+      move: function(tabIds, moveProperties) {
         let index = moveProperties.index;
         let tabsMoved = [];
         if (!Array.isArray(tabIds)) {
           tabIds = [tabIds];
         }
 
         let destinationWindow = null;
         if (moveProperties.windowId !== null) {
@@ -647,23 +639,22 @@ extensions.registerSchemaAPI("tabs", nul
             if (tab.pinned) {
               gBrowser.pinTab(newTab);
             }
 
             gBrowser.moveTabTo(newTab, getInsertionPoint());
 
             tab.parentNode._finishAnimateTabMove();
             gBrowser.swapBrowsersAndCloseOther(newTab, tab);
+            tab = newTab;
           } else {
             // If the window we are moving is the same, just move the tab.
             gBrowser.moveTabTo(tab, getInsertionPoint());
           }
           tabsMoved.push(tab);
         }
 
-        if (callback) {
-          runSafe(context, callback, tabsMoved.map(tab => TabManager.convert(extension, tab)));
-        }
+        return Promise.resolve(tabsMoved.map(tab => TabManager.convert(extension, tab)));
       },
     },
   };
   return self;
 });
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -4,17 +4,16 @@
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
-  runSafe,
 } = ExtensionUtils;
 
 extensions.registerSchemaAPI("windows", null, (extension, context) => {
   return {
     windows: {
       onCreated:
       new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
         fire(WindowManager.convert(extension, window));
@@ -35,44 +34,38 @@ extensions.registerSchemaAPI("windows", 
         AllWindowEvents.addListener("focus", listener);
         AllWindowEvents.addListener("blur", listener);
         return () => {
           AllWindowEvents.removeListener("focus", listener);
           AllWindowEvents.removeListener("blur", listener);
         };
       }).api(),
 
-      get: function(windowId, getInfo, callback) {
+      get: function(windowId, getInfo) {
         let window = WindowManager.getWindow(windowId);
-        runSafe(context, callback, WindowManager.convert(extension, window, getInfo));
+        return Promise.resolve(WindowManager.convert(extension, window, getInfo));
       },
 
-      getCurrent: function(getInfo, callback) {
+      getCurrent: function(getInfo) {
         let window = currentWindow(context);
-        runSafe(context, callback, WindowManager.convert(extension, window, getInfo));
+        return Promise.resolve(WindowManager.convert(extension, window, getInfo));
       },
 
-      getLastFocused: function(getInfo, callback) {
+      getLastFocused: function(getInfo) {
         let window = WindowManager.topWindow;
-        runSafe(context, callback, WindowManager.convert(extension, window, getInfo));
+        return Promise.resolve(WindowManager.convert(extension, window, getInfo));
       },
 
-      getAll: function(getInfo, callback) {
-        let e = Services.wm.getEnumerator("navigator:browser");
-        let windows = [];
-        while (e.hasMoreElements()) {
-          let window = e.getNext();
-          if (window.document.readyState == "complete") {
-            windows.push(WindowManager.convert(extension, window, getInfo));
-          }
-        }
-        runSafe(context, callback, windows);
+      getAll: function(getInfo) {
+        let windows = Array.from(WindowListManager.browserWindows(),
+                                 window => WindowManager.convert(extension, window, getInfo));
+        return Promise.resolve(windows);
       },
 
-      create: function(createData, callback) {
+      create: function(createData) {
         function mkstr(s) {
           let result = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
           result.data = s;
           return result;
         }
 
         let args = Cc["@mozilla.org/supports-array;1"].createInstance(Ci.nsISupportsArray);
         if (createData.url !== null) {
@@ -109,43 +102,41 @@ extensions.registerSchemaAPI("windows", 
         if (createData.width !== null || createData.height !== null) {
           let width = createData.width !== null ? createData.width : window.outerWidth;
           let height = createData.height !== null ? createData.height : window.outerHeight;
           window.resizeTo(width, height);
         }
 
         // TODO: focused, type, state
 
-        window.addEventListener("load", function listener() {
-          window.removeEventListener("load", listener);
-          if (callback) {
-            runSafe(context, callback, WindowManager.convert(extension, window));
-          }
+        return new Promise(resolve => {
+          window.addEventListener("load", function listener() {
+            window.removeEventListener("load", listener);
+            resolve(WindowManager.convert(extension, window));
+          });
         });
       },
 
-      update: function(windowId, updateInfo, callback) {
+      update: function(windowId, updateInfo) {
         let window = WindowManager.getWindow(windowId);
         if (updateInfo.focused) {
           Services.focus.activeWindow = window;
         }
         // TODO: All the other properties...
 
-        if (callback) {
-          runSafe(context, callback, WindowManager.convert(extension, window));
-        }
+        return Promise.resolve(WindowManager.convert(extension, window));
       },
 
-      remove: function(windowId, callback) {
+      remove: function(windowId) {
         let window = WindowManager.getWindow(windowId);
         window.close();
 
-        let listener = () => {
-          AllWindowEvents.removeListener("domwindowclosed", listener);
-          if (callback) {
-            runSafe(context, callback);
-          }
-        };
-        AllWindowEvents.addListener("domwindowclosed", listener);
+        return new Promise(resolve => {
+          let listener = () => {
+            AllWindowEvents.removeListener("domwindowclosed", listener);
+            resolve();
+          };
+          AllWindowEvents.addListener("domwindowclosed", listener);
+        });
       },
     },
   };
 });
--- a/browser/components/extensions/schemas/bookmarks.json
+++ b/browser/components/extensions/schemas/bookmarks.json
@@ -104,16 +104,17 @@
         }
       }
     ],
     "functions": [
       {
         "name": "get",
         "type": "function",
         "description": "Retrieves the specified BookmarkTreeNode(s).",
+        "async": "callback",
         "parameters": [
           {
             "name": "idOrIdList",
             "description": "A single string-valued id, or an array of string-valued ids",
             "choices": [
               {
                 "type": "string"
               },
@@ -138,16 +139,17 @@
             ]
           }
         ]
       },
       {
         "name": "getChildren",
         "type": "function",
         "description": "Retrieves the children of the specified BookmarkTreeNode id.",
+        "async": "callback",
         "parameters": [
           {
             "type": "string",
             "name": "id"
           },
           {
             "type": "function",
             "name": "callback",
@@ -161,16 +163,17 @@
           }
         ]
       },
       {
         "name": "getRecent",
         "unsupported": true,
         "type": "function",
         "description": "Retrieves the recently added bookmarks.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "minimum": 1,
             "name": "numberOfItems",
             "description": "The maximum number of items to return."
           },
           {
@@ -185,16 +188,17 @@
             ]
           }
         ]
       },
       {
         "name": "getTree",
         "type": "function",
         "description": "Retrieves the entire Bookmarks hierarchy.",
+        "async": "callback",
         "parameters": [
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
                 "name": "results",
                 "type": "array",
@@ -203,16 +207,17 @@
             ]
           }
         ]
       },
       {
         "name": "getSubTree",
         "type": "function",
         "description": "Retrieves part of the Bookmarks hierarchy, starting at the specified node.",
+        "async": "callback",
         "parameters": [
           {
             "type": "string",
             "name": "id",
             "description": "The ID of the root of the subtree to retrieve."
           },
           {
             "type": "function",
@@ -227,16 +232,17 @@
           }
         ]
       },
       {
         "name": "search",
         "unsupported": true,
         "type": "function",
         "description": "Searches for BookmarkTreeNodes matching the given query. Queries specified with an object produce BookmarkTreeNodes matching all specified properties.",
+        "async": "callback",
         "parameters": [
           {
             "name": "query",
             "description": "Either a string of words and quoted phrases that are matched against bookmark URLs and titles, or an object. If an object, the properties <code>query</code>, <code>url</code>, and <code>title</code> may be specified and bookmarks matching all specified properties will be produced.",
             "choices": [
               {
                 "type": "string",
                 "description": "A string of words and quoted phrases that are matched against bookmark URLs and titles."
@@ -276,16 +282,17 @@
             ]
           }
         ]
       },
       {
         "name": "create",
         "type": "function",
         "description": "Creates a bookmark or folder under the specified parentId.  If url is NULL or missing, it will be a folder.",
+        "async": "callback",
         "parameters": [
           {
             "$ref": "CreateDetails",
             "name": "bookmark"
           },
           {
             "type": "function",
             "name": "callback",
@@ -298,16 +305,17 @@
             ]
           }
         ]
       },
       {
         "name": "move",
         "type": "function",
         "description": "Moves the specified BookmarkTreeNode to the provided location.",
+        "async": "callback",
         "parameters": [
           {
             "type": "string",
             "name": "id"
           },
           {
             "type": "object",
             "name": "destination",
@@ -335,16 +343,17 @@
             ]
           }
         ]
       },
       {
         "name": "update",
         "type": "function",
         "description": "Updates the properties of a bookmark or folder. Specify only the properties that you want to change; unspecified properties will be left unchanged.  <b>Note:</b> Currently, only 'title' and 'url' are supported.",
+        "async": "callback",
         "parameters": [
           {
             "type": "string",
             "name": "id"
           },
           {
             "type": "object",
             "name": "changes",
@@ -371,16 +380,17 @@
             ]
           }
         ]
       },
       {
         "name": "remove",
         "type": "function",
         "description": "Removes a bookmark or an empty bookmark folder.",
+        "async": "callback",
         "parameters": [
           {
             "type": "string",
             "name": "id"
           },
           {
             "type": "function",
             "name": "callback",
@@ -389,16 +399,17 @@
           }
         ]
       },
       {
         "name": "removeTree",
         "unsupported": true,
         "type": "function",
         "description": "Recursively removes a bookmark folder.",
+        "async": "callback",
         "parameters": [
           {
             "type": "string",
             "name": "id"
           },
           {
             "type": "function",
             "name": "callback",
@@ -407,30 +418,32 @@
           }
         ]
       },
       {
         "name": "import",
         "unsupported": true,
         "type": "function",
         "description": "Imports bookmarks from an html bookmark file",
+        "async": "callback",
         "parameters": [
           {
             "type": "function",
             "name": "callback",
             "optional": true,
             "parameters": []
           }
         ]
       },
       {
         "name": "export",
         "unsupported": true,
         "type": "function",
         "description": "Exports bookmarks to an html bookmark file",
+        "async": "callback",
         "parameters": [
           {
             "type": "function",
             "name": "callback",
             "optional": true,
             "parameters": []
           }
         ]
--- a/browser/components/extensions/schemas/browser_action.json
+++ b/browser/components/extensions/schemas/browser_action.json
@@ -67,16 +67,17 @@
             }
           }
         ]
       },
       {
         "name": "getTitle",
         "type": "function",
         "description": "Gets the title of the browser action.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {
                 "type": "integer",
                 "optional": true,
@@ -95,16 +96,17 @@
             ]
           }
         ]
       },
       {
         "name": "setIcon",
         "type": "function",
         "description": "Sets the icon for the browser action. The icon can be specified either as the path to an image file or as the pixel data from a canvas element, or as dictionary of either one of those. Either the <b>path</b> or the <b>imageData</b> property must be specified.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "imageData": {
                 "choices": [
                   { "$ref": "ImageDataType" },
@@ -164,16 +166,17 @@
             }
           }
         ]
       },
       {
         "name": "getPopup",
         "type": "function",
         "description": "Gets the html document set as the popup for this browser action.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {
                 "type": "integer",
                 "optional": true,
@@ -214,16 +217,17 @@
             }
           }
         ]
       },
       {
         "name": "getBadgeText",
         "type": "function",
         "description": "Gets the badge text of the browser action. If no tab is specified, the non-tab-specific badge text is returned.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {
                 "type": "integer",
                 "optional": true,
@@ -267,16 +271,17 @@
             }
           }
         ]
       },
       {
         "name": "getBadgeBackgroundColor",
         "type": "function",
         "description": "Gets the background color of the browser action.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {
                 "type": "integer",
                 "optional": true,
@@ -324,16 +329,17 @@
           }
         ]
       },
       {
         "name": "openPopup",
         "type": "function",
         "description": "Opens the extension popup window in the active window but does not grant tab permissions.",
         "unsupported": true,
+        "async": "callback",
         "parameters": [
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
                 "name": "popupView",
                 "type": "object",
--- a/browser/components/extensions/schemas/context_menus.json
+++ b/browser/components/extensions/schemas/context_menus.json
@@ -204,16 +204,17 @@
             "parameters": []
           }
         ]
       },
       {
         "name": "update",
         "type": "function",
         "description": "Updates a previously created context menu item.",
+        "async": "callback",
         "parameters": [
           {
             "choices": [
               { "type": "integer" },
               { "type": "string" }
             ],
             "name": "id",
             "description": "The ID of the item to update."
@@ -290,16 +291,17 @@
             "description": "Called when the context menu has been updated."
           }
         ]
       },
       {
         "name": "remove",
         "type": "function",
         "description": "Removes a context menu item.",
+        "async": "callback",
         "parameters": [
           {
             "choices": [
               { "type": "integer" },
               { "type": "string" }
             ],
             "name": "menuItemId",
             "description": "The ID of the context menu item to remove."
@@ -312,16 +314,17 @@
             "description": "Called when the context menu has been removed."
           }
         ]
       },
       {
         "name": "removeAll",
         "type": "function",
         "description": "Removes all context menu items added by this extension.",
+        "async": "callback",
         "parameters": [
           {
             "type": "function",
             "name": "callback",
             "optional": true,
             "parameters": [],
             "description": "Called when removal is complete."
           }
--- a/browser/components/extensions/schemas/page_action.json
+++ b/browser/components/extensions/schemas/page_action.json
@@ -65,16 +65,17 @@
             }
           }
         ]
       },
       {
         "name": "getTitle",
         "type": "function",
         "description": "Gets the title of the page action.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {
                 "type": "integer",
                 "description": "Specify the tab to get the title from."
@@ -92,16 +93,17 @@
             ]
           }
         ]
       },
       {
         "name": "setIcon",
         "type": "function",
         "description": "Sets the icon for the page action. The icon can be specified either as the path to an image file or as the pixel data from a canvas element, or as dictionary of either one of those. Either the <b>path</b> or the <b>imageData</b> property must be specified.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
               "imageData": {
                 "choices": [
@@ -152,16 +154,17 @@
             }
           }
         ]
       },
       {
         "name": "getPopup",
         "type": "function",
         "description": "Gets the html document set as the popup for this page action.",
+        "async": "callback",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {
                 "type": "integer",
                 "description": "Specify the tab to get the popup from."
--- a/browser/components/extensions/schemas/tabs.json
+++ b/browser/components/extensions/schemas/tabs.json
@@ -151,16 +151,17 @@
         "description": "An ID which represents the absence of a browser tab."
       }
     },
     "functions": [
       {
         "name": "get",
         "type": "function",
         "description": "Retrieves details about the specified tab.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "name": "tabId",
             "minimum": 0
           },
           {
             "type": "function",
@@ -170,16 +171,17 @@
             ]
           }
         ]
       },
       {
         "name": "getCurrent",
         "type": "function",
         "description": "Gets the tab that this script call is being made from. May be undefined if called from a non-tab context (for example: a background page or popup view).",
+        "async": "callback",
         "parameters": [
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
                 "name": "tab",
                 "$ref": "Tab",
@@ -291,16 +293,17 @@
         ]
       },
       {
         "name": "getSelected",
         "deprecated": "Please use $(ref:tabs.query) <code>{active: true}</code>.",
         "unsupported": true,
         "type": "function",
         "description": "Gets the tab that is selected in the specified window.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "name": "windowId",
             "minimum": -2,
             "optional": true,
             "description": "Defaults to the $(topic:current-window)[current window]."
           },
@@ -313,16 +316,17 @@
           }
         ]
       },
       {
         "name": "getAllInWindow",
         "type": "function",
         "deprecated": "Please use $(ref:tabs.query) <code>{windowId: windowId}</code>.",
         "description": "Gets details about all tabs in the specified window.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "name": "windowId",
             "minimum": -2,
             "optional": true,
             "description": "Defaults to the $(topic:current-window)[current window]."
             },
@@ -334,16 +338,17 @@
             ]
           }
         ]
       },
       {
         "name": "create",
         "type": "function",
         "description": "Creates a new tab.",
+        "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "createProperties",
             "properties": {
               "windowId": {
                 "type": "integer",
                 "minimum": -2,
@@ -400,16 +405,17 @@
             ]
           }
         ]
       },
       {
         "name": "duplicate",
         "type": "function",
         "description": "Duplicates a tab.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "name": "tabId",
             "minimum": 0,
             "description": "The ID of the tab which is to be duplicated."
           },
           {
@@ -426,16 +432,17 @@
             ]
           }
         ]
       },
       {
         "name": "query",
         "type": "function",
         "description": "Gets all tabs that have the specified properties, or all tabs if no properties are specified.",
+        "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "queryInfo",
             "properties": {
               "active": {
                 "type": "boolean",
                 "optional": true,
@@ -522,16 +529,17 @@
             ]
           }
         ]
       },
       {
         "name": "highlight",
         "type": "function",
         "description": "Highlights the given tabs.",
+        "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "highlightInfo",
             "properties": {
                "windowId": {
                  "type": "integer",
                  "optional": true,
@@ -560,16 +568,17 @@
              ]
            }
         ]
       },
       {
         "name": "update",
         "type": "function",
         "description": "Modifies the properties of a tab. Properties that are not specified in <var>updateProperties</var> are not modified.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "name": "tabId",
             "minimum": 0,
             "optional": true,
             "description": "Defaults to the selected tab of the $(topic:current-window)[current window]."
           },
@@ -633,16 +642,17 @@
             ]
           }
         ]
       },
       {
         "name": "move",
         "type": "function",
         "description": "Moves one or more tabs to a new position within its window, or to a new window. Note that tabs can only be moved to and from normal (window.type === \"normal\") windows.",
+        "async": "callback",
         "parameters": [
           {
             "name": "tabIds",
             "description": "The tab or list of tabs to move.",
             "choices": [
               {"type": "integer", "minimum": 0},
               {"type": "array", "items": {"type": "integer", "minimum": 0}}
             ]
@@ -680,53 +690,72 @@
             ]
           }
         ]
       },
       {
         "name": "reload",
         "type": "function",
         "description": "Reload a tab.",
+        "async": "callback",
         "parameters": [
-          {"type": "integer", "name": "tabId", "minimum": 0, "optional": true, "description": "The ID of the tab to reload; defaults to the selected tab of the current window."},
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0,
+            "optional": true,
+            "description": "The ID of the tab to reload; defaults to the selected tab of the current window."
+          },
           {
             "type": "object",
             "name": "reloadProperties",
             "optional": true,
             "properties": {
               "bypassCache": {
                 "type": "boolean",
                 "optional": true,
                 "description": "Whether using any local cache. Default is false."
               }
             }
           },
-          {"type": "function", "name": "callback", "optional": true, "parameters": []}
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
         ]
       },
       {
         "name": "remove",
         "type": "function",
         "description": "Closes one or more tabs.",
+        "async": "callback",
         "parameters": [
           {
             "name": "tabIds",
             "description": "The tab or list of tabs to close.",
             "choices": [
               {"type": "integer", "minimum": 0},
               {"type": "array", "items": {"type": "integer", "minimum": 0}}
             ]
           },
-          {"type": "function", "name": "callback", "optional": true, "parameters": []}
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
         ]
       },
       {
         "name": "detectLanguage",
         "type": "function",
         "description": "Detects the primary language of the content in a tab.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "name": "tabId",
             "minimum": 0,
             "optional": true,
             "description": "Defaults to the active tab of the $(topic:current-window)[current window]."
           },
@@ -757,29 +786,41 @@
             "description": "The target window. Defaults to the $(topic:current-window)[current window]."
           },
           {
             "$ref": "extensionTypes.ImageDetails",
             "name": "options",
             "optional": true
           },
           {
-            "type": "function", "name": "callback", "parameters": [
-              {"type": "string", "name": "dataUrl", "description": "A data URL which encodes an image of the visible area of the captured tab. May be assigned to the 'src' property of an HTML Image element for display."}
+            "type": "function",
+            "name": "callback",
+            "parameters": [
+              {
+                "type": "string",
+                "name": "dataUrl",
+                "description": "A data URL which encodes an image of the visible area of the captured tab. May be assigned to the 'src' property of an HTML Image element for display."
+              }
             ]
           }
         ]
       },
       {
         "name": "executeScript",
         "type": "function",
         "description": "Injects JavaScript code into a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.",
         "async": "callback",
         "parameters": [
-          {"type": "integer", "name": "tabId", "minimum": 0, "optional": true, "description": "The ID of the tab in which to run the script; defaults to the active tab of the current window."},
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0,
+            "optional": true,
+            "description": "The ID of the tab in which to run the script; defaults to the active tab of the current window."
+          },
           {
             "$ref": "extensionTypes.InjectDetails",
             "name": "details",
             "description": "Details of the script to run."
           },
           {
             "type": "function",
             "name": "callback",
@@ -798,17 +839,23 @@
         ]
       },
       {
         "name": "insertCSS",
         "type": "function",
         "description": "Injects CSS into a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.",
         "async": "callback",
         "parameters": [
-          {"type": "integer", "name": "tabId", "minimum": 0, "optional": true, "description": "The ID of the tab in which to insert the CSS; defaults to the active tab of the current window."},
+          {
+            "type": "integer",
+            "name": "tabId",
+            "minimum": 0,
+            "optional": true,
+            "description": "The ID of the tab in which to insert the CSS; defaults to the active tab of the current window."
+          },
           {
             "$ref": "extensionTypes.InjectDetails",
             "name": "details",
             "description": "Details of the CSS text to insert."
           },
           {
             "type": "function",
             "name": "callback",
@@ -817,16 +864,17 @@
             "parameters": []
           }
         ]
       },
       {
         "name": "setZoom",
         "type": "function",
         "description": "Zooms a specified tab.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "name": "tabId",
             "minimum": 0,
             "optional": true,
             "description": "The ID of the tab to zoom; defaults to the active tab of the current window."
           },
@@ -843,16 +891,17 @@
             "parameters": []
           }
         ]
       },
       {
         "name": "getZoom",
         "type": "function",
         "description": "Gets the current zoom factor of a specified tab.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "name": "tabId",
             "minimum": 0,
             "optional": true,
             "description": "The ID of the tab to get the current zoom factor from; defaults to the active tab of the current window."
           },
@@ -869,16 +918,17 @@
             ]
           }
         ]
       },
       {
         "name": "setZoomSettings",
         "type": "function",
         "description": "Sets the zoom settings for a specified tab, which define how zoom changes are handled. These settings are reset to defaults upon navigating the tab.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "name": "tabId",
             "optional": true,
             "minimum": 0,
             "description": "The ID of the tab to change the zoom settings for; defaults to the active tab of the current window."
           },
@@ -895,16 +945,17 @@
             "parameters": []
           }
         ]
       },
       {
         "name": "getZoomSettings",
         "type": "function",
         "description": "Gets the current zoom settings of a specified tab.",
+        "async": "callback",
         "parameters": [
           {
             "type": "integer",
             "name": "tabId",
             "optional": true,
             "minimum": 0,
             "description": "The ID of the tab to get the current zoom settings from; defaults to the active tab of the current window."
           },
--- a/browser/components/extensions/schemas/windows.json
+++ b/browser/components/extensions/schemas/windows.json
@@ -32,36 +32,77 @@
         "type": "string",
         "description": "The state of this browser window. Under some circumstances a Window may not be assigned state property, for example when querying closed windows from the $(ref:sessions) API.",
         "enum": ["normal", "minimized", "maximized", "fullscreen", "docked"]
       },
       {
         "id": "Window",
         "type": "object",
         "properties": {
-          "id": {"type": "integer", "optional": true, "minimum": 0, "description": "The ID of the window. Window IDs are unique within a browser session. Under some circumstances a Window may not be assigned an ID, for example when querying windows using the $(ref:sessions) API, in which case a session ID may be present."},
-          "focused": {"type": "boolean", "description": "Whether the window is currently the focused window."},
-          "top": {"type": "integer", "optional": true, "description": "The offset of the window from the top edge of the screen in pixels. Under some circumstances a Window may not be assigned top property, for example when querying closed windows from the $(ref:sessions) API."},
-          "left": {"type": "integer", "optional": true, "description": "The offset of the window from the left edge of the screen in pixels. Under some circumstances a Window may not be assigned left property, for example when querying closed windows from the $(ref:sessions) API."},
-          "width": {"type": "integer", "optional": true, "description": "The width of the window, including the frame, in pixels. Under some circumstances a Window may not be assigned width property, for example when querying closed windows from the $(ref:sessions) API."},
-          "height": {"type": "integer", "optional": true, "description": "The height of the window, including the frame, in pixels. Under some circumstances a Window may not be assigned height property, for example when querying closed windows from the $(ref:sessions) API."},
-          "tabs": {"type": "array", "items": { "$ref": "tabs.Tab" }, "optional": true, "description": "Array of $(ref:tabs.Tab) objects representing the current tabs in the window."},
-          "incognito": {"type": "boolean", "description": "Whether the window is incognito."},
+          "id": {
+            "type": "integer",
+            "optional": true,
+            "minimum": 0,
+            "description": "The ID of the window. Window IDs are unique within a browser session. Under some circumstances a Window may not be assigned an ID, for example when querying windows using the $(ref:sessions) API, in which case a session ID may be present."
+          },
+          "focused": {
+            "type": "boolean",
+            "description": "Whether the window is currently the focused window."
+          },
+          "top": {
+            "type": "integer",
+            "optional": true,
+            "description": "The offset of the window from the top edge of the screen in pixels. Under some circumstances a Window may not be assigned top property, for example when querying closed windows from the $(ref:sessions) API."
+          },
+          "left": {
+            "type": "integer",
+            "optional": true,
+            "description": "The offset of the window from the left edge of the screen in pixels. Under some circumstances a Window may not be assigned left property, for example when querying closed windows from the $(ref:sessions) API."
+          },
+          "width": {
+            "type": "integer",
+            "optional": true,
+            "description": "The width of the window, including the frame, in pixels. Under some circumstances a Window may not be assigned width property, for example when querying closed windows from the $(ref:sessions) API."
+          },
+          "height": {
+            "type": "integer",
+            "optional": true,
+            "description": "The height of the window, including the frame, in pixels. Under some circumstances a Window may not be assigned height property, for example when querying closed windows from the $(ref:sessions) API."
+          },
+          "tabs": {
+            "type": "array",
+            "items": { "$ref": "tabs.Tab" },
+            "optional": true,
+            "description": "Array of $(ref:tabs.Tab) objects representing the current tabs in the window."
+          },
+          "incognito": {
+            "type": "boolean",
+            "description": "Whether the window is incognito."
+          },
           "type": {
             "$ref": "WindowType",
             "optional": true,
             "description": "The type of browser window this is."
           },
           "state": {
             "$ref": "WindowState",
             "optional": true,
             "description": "The state of this browser window."
           },
-          "alwaysOnTop": {"unsupported": true, "type": "boolean", "description": "Whether the window is set to be always on top."},
-          "sessionId": {"unsupported": true, "type": "string", "optional": true, "description": "The session ID used to uniquely identify a Window obtained from the $(ref:sessions) API."}
+          "alwaysOnTop": {
+            "unsupported": true,
+            "type": "boolean",
+            "description": "Whether the window is set to be always on top."
+          },
+          "sessionId": {
+            "unsupported": true,
+            "type": "string",
+            "optional": true,
+            "description": "The session ID used to uniquely identify a Window obtained from the $(ref:sessions) API."
+          }
         }
       },
       {
         "id": "CreateType",
         "type": "string",
         "description": "Specifies what type of browser window to create. The 'panel' and 'detached_panel' types create a popup unless the '--enable-panels' flag is set.",
         "enum": ["normal", "popup", "panel", "detached_panel"]
       }
@@ -76,141 +117,228 @@
         "description": "The windowId value that represents the $(topic:current-window)[current window]."
       }
     },
     "functions": [
       {
         "name": "get",
         "type": "function",
         "description": "Gets details about a window.",
+        "async": "callback",
         "parameters": [
-          {"type": "integer", "name": "windowId", "minimum": -2},
+          {
+            "type": "integer",
+            "name": "windowId",
+            "minimum": -2
+          },
           {
             "type": "object",
             "name": "getInfo",
             "optional": true,
             "description": "",
             "properties": {
-              "populate": {"type": "boolean", "optional": true, "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." },
-              "windowTypes": {"type": "array", "items": { "$ref": "WindowType" }, "optional": true, "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows." }
+              "populate": {
+                "type": "boolean",
+                "optional": true,
+                "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission."
+              },
+              "windowTypes": {
+                "type": "array",
+                "items": {
+                  "$ref": "WindowType"
+                },
+                "optional": true,
+                "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
+              }
             }
           },
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
-                "name": "window", "$ref": "Window"
+                "name": "window",
+                "$ref": "Window"
               }
             ]
           }
         ]
       },
       {
         "name": "getCurrent",
         "type": "function",
         "description": "Gets the $(topic:current-window)[current window].",
+        "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "getInfo",
             "optional": true,
             "description": "",
             "properties": {
-              "populate": {"type": "boolean", "optional": true, "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." },
-              "windowTypes": {"type": "array", "items": { "$ref": "WindowType" }, "optional": true, "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows." }
+              "populate": {
+                "type": "boolean",
+                "optional": true,
+                "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission."
+              },
+              "windowTypes": {
+                "type": "array",
+                "items": { "$ref": "WindowType" },
+                "optional": true,
+                "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
+              }
             }
           },
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
-                "name": "window", "$ref": "Window"
+                "name": "window",
+                "$ref": "Window"
               }
             ]
           }
         ]
       },
       {
         "name": "getLastFocused",
         "type": "function",
         "description": "Gets the window that was most recently focused &mdash; typically the window 'on top'.",
+        "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "getInfo",
             "optional": true,
             "description": "",
             "properties": {
-              "populate": {"type": "boolean", "optional": true, "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." },
-              "windowTypes": {"type": "array", "items": { "$ref": "WindowType" }, "optional": true, "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows." }
+              "populate": {
+                "type": "boolean",
+                "optional": true,
+                "description": "If true, the $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission."
+              },
+              "windowTypes": {
+                "type": "array",
+                "items": { "$ref": "WindowType" },
+                "optional": true,
+                "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
+              }
             }
           },
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
-                "name": "window", "$ref": "Window"
+                "name": "window",
+                "$ref": "Window"
               }
             ]
           }
         ]
       },
       {
         "name": "getAll",
         "type": "function",
         "description": "Gets all windows.",
+        "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "getInfo",
             "optional": true,
             "description": "",
             "properties": {
-              "populate": {"type": "boolean", "optional": true, "description": "If true, each $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects for that window. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission." },
-              "windowTypes": {"type": "array", "items": { "$ref": "WindowType" }, "optional": true, "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows." }
+              "populate": {
+                "type": "boolean",
+                "optional": true,
+                "description": "If true, each $(ref:windows.Window) object will have a <var>tabs</var> property that contains a list of the $(ref:tabs.Tab) objects for that window. The <code>Tab</code> objects only contain the <code>url</code>, <code>title</code> and <code>favIconUrl</code> properties if the extension's manifest file includes the <code>\"tabs\"</code> permission."
+              },
+              "windowTypes": {
+                "type": "array",
+                "items": { "$ref": "WindowType" },
+                "optional": true,
+                "description": "If set, the $(ref:windows.Window) returned will be filtered based on its type. If unset the default filter is set to <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
+              }
             }
           },
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
-                "name": "windows", "type": "array", "items": { "$ref": "Window" }
+                "name": "windows",
+                "type": "array",
+                "items": { "$ref": "Window" }
               }
             ]
           }
         ]
       },
       {
         "name": "create",
         "type": "function",
         "description": "Creates (opens) a new browser with any optional sizing, position or default URL provided.",
+        "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "createData",
             "properties": {
               "url": {
                 "description": "A URL or array of URLs to open as tabs in the window. Fully-qualified URLs must include a scheme (i.e. 'http://www.google.com', not 'www.google.com'). Relative URLs will be relative to the current page within the extension. Defaults to the New Tab Page.",
                 "optional": true,
                 "choices": [
-                  {"type": "string"},
-                  {"type": "array", "items": {"type": "string"}}
+                  { "type": "string" },
+                  {
+                    "type": "array",
+                    "items": { "type": "string" }
+                  }
                 ]
               },
-              "tabId": {"type": "integer", "minimum": 0, "optional": true, "description": "The id of the tab for which you want to adopt to the new window."},
-              "left": {"type": "integer", "optional": true, "description": "The number of pixels to position the new window from the left edge of the screen. If not specified, the new window is offset naturally from the last focused window. This value is ignored for panels."},
-              "top": {"type": "integer", "optional": true, "description": "The number of pixels to position the new window from the top edge of the screen. If not specified, the new window is offset naturally from the last focused window. This value is ignored for panels."},
-              "width": {"type": "integer", "minimum": 0, "optional": true, "description": "The width in pixels of the new window, including the frame. If not specified defaults to a natural width."},
-              "height": {"type": "integer", "minimum": 0, "optional": true, "description": "The height in pixels of the new window, including the frame. If not specified defaults to a natural height."},
-              "focused": {"unsupported": true, "type": "boolean", "optional": true, "description": "If true, opens an active window. If false, opens an inactive window."},
-              "incognito": {"type": "boolean", "optional": true, "description": "Whether the new window should be an incognito window."},
+              "tabId": {
+                "type": "integer",
+                "minimum": 0,
+                "optional": true,
+                "description": "The id of the tab for which you want to adopt to the new window."
+              },
+              "left": {
+                "type": "integer",
+                "optional": true,
+                "description": "The number of pixels to position the new window from the left edge of the screen. If not specified, the new window is offset naturally from the last focused window. This value is ignored for panels."
+              },
+              "top": {
+                "type": "integer",
+                "optional": true,
+                "description": "The number of pixels to position the new window from the top edge of the screen. If not specified, the new window is offset naturally from the last focused window. This value is ignored for panels."
+              },
+              "width": {
+                "type": "integer",
+                "minimum": 0,
+                "optional": true,
+                "description": "The width in pixels of the new window, including the frame. If not specified defaults to a natural width."
+              },
+              "height": {
+                "type": "integer",
+                "minimum": 0,
+                "optional": true,
+                "description": "The height in pixels of the new window, including the frame. If not specified defaults to a natural height."
+              },
+              "focused": {
+                "unsupported": true,
+                "type": "boolean",
+                "optional": true,
+                "description": "If true, opens an active window. If false, opens an inactive window."
+              },
+              "incognito": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the new window should be an incognito window."
+              },
               "type": {
                 "unsupported": true,
                 "$ref": "CreateType",
                 "optional": true,
                 "description": "Specifies what type of browser window to create. The 'panel' and 'detached_panel' types create a popup unless the '--enable-panels' flag is set."
               },
               "state": {
                 "unsupported": true,
@@ -222,66 +350,115 @@
             "optional": true
           },
           {
             "type": "function",
             "name": "callback",
             "optional": true,
             "parameters": [
               {
-                "name": "window", "$ref": "Window", "description": "Contains details about the created window.",
+                "name": "window",
+                "$ref": "Window",
+                "description": "Contains details about the created window.",
                 "optional": true
               }
             ]
           }
         ]
       },
       {
         "name": "update",
         "type": "function",
         "description": "Updates the properties of a window. Specify only the properties that you want to change; unspecified properties will be left unchanged.",
+        "async": "callback",
         "parameters": [
-          {"type": "integer", "name": "windowId", "minimum": -2},
+          {
+            "type": "integer",
+            "name": "windowId",
+            "minimum": -2
+          },
           {
             "type": "object",
             "name": "updateInfo",
             "properties": {
-              "left": {"unsupported": true, "type": "integer", "optional": true, "description": "The offset from the left edge of the screen to move the window to in pixels. This value is ignored for panels."},
-              "top": {"unsupported": true, "type": "integer", "optional": true, "description": "The offset from the top edge of the screen to move the window to in pixels. This value is ignored for panels."},
-              "width": {"unsupported": true, "type": "integer", "minimum": 0, "optional": true, "description": "The width to resize the window to in pixels. This value is ignored for panels."},
-              "height": {"unsupported": true, "type": "integer", "minimum": 0, "optional": true, "description": "The height to resize the window to in pixels. This value is ignored for panels."},
-              "focused": {"type": "boolean", "optional": true, "description": "If true, brings the window to the front. If false, brings the next window in the z-order to the front."},
-              "drawAttention": {"unsupported": true, "type": "boolean", "optional": true, "description": "If true, causes the window to be displayed in a manner that draws the user's attention to the window, without changing the focused window. The effect lasts until the user changes focus to the window. This option has no effect if the window already has focus. Set to false to cancel a previous draw attention request."},
+              "left": {
+                "unsupported": true,
+                "type": "integer",
+                "optional": true,
+                "description": "The offset from the left edge of the screen to move the window to in pixels. This value is ignored for panels."
+              },
+              "top": {
+                "unsupported": true,
+                "type": "integer",
+                "optional": true,
+                "description": "The offset from the top edge of the screen to move the window to in pixels. This value is ignored for panels."
+              },
+              "width": {
+                "unsupported": true,
+                "type": "integer",
+                "minimum": 0,
+                "optional": true,
+                "description": "The width to resize the window to in pixels. This value is ignored for panels."
+              },
+              "height": {
+                "unsupported": true,
+                "type": "integer",
+                "minimum": 0,
+                "optional": true,
+                "description": "The height to resize the window to in pixels. This value is ignored for panels."
+              },
+              "focused": {
+                "type": "boolean",
+                "optional": true,
+                "description": "If true, brings the window to the front. If false, brings the next window in the z-order to the front."
+              },
+              "drawAttention": {
+                "unsupported": true,
+                "type": "boolean",
+                "optional": true,
+                "description": "If true, causes the window to be displayed in a manner that draws the user's attention to the window, without changing the focused window. The effect lasts until the user changes focus to the window. This option has no effect if the window already has focus. Set to false to cancel a previous draw attention request."
+              },
               "state": {
                 "unsupported": true,
                 "$ref": "WindowState",
                 "optional": true,
                 "description": "The new state of the window. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined with 'left', 'top', 'width' or 'height'."
               }
             }
           },
           {
             "type": "function",
             "name": "callback",
             "optional": true,
             "parameters": [
               {
-                "name": "window", "$ref": "Window"
+                "name": "window",
+                "$ref": "Window"
               }
             ]
           }
         ]
       },
       {
         "name": "remove",
         "type": "function",
         "description": "Removes (closes) a window, and all the tabs inside it.",
+        "async": "callback",
         "parameters": [
-          {"type": "integer", "name": "windowId", "minimum": 0},
-          {"type": "function", "name": "callback", "optional": true, "parameters": []}
+          {
+            "type": "integer",
+            "name": "windowId",
+            "minimum": 0
+          },
+          {
+            "type": "function",
+            "name": "callback",
+            "optional": true,
+            "parameters": []
+          }
         ]
       }
     ],
     "events": [
       {
         "name": "onCreated",
         "type": "function",
         "description": "Fired when a window is created.",
@@ -309,30 +486,40 @@
           {
             "name": "windowTypes",
             "type": "array",
             "items": { "$ref": "WindowType" },
             "description": "Conditions that the window's type being removed must satisfy. By default it will satisfy <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
           }
         ],
         "parameters": [
-          {"type": "integer", "name": "windowId", "minimum": 0, "description": "ID of the removed window."}
+          {
+            "type": "integer",
+            "name": "windowId",
+            "minimum": 0,
+            "description": "ID of the removed window."
+          }
         ]
       },
       {
         "name": "onFocusChanged",
         "type": "function",
         "description": "Fired when the currently focused window changes. Will be $(ref:windows.WINDOW_ID_NONE) if all browser windows have lost focus. Note: On some Linux window managers, WINDOW_ID_NONE will always be sent immediately preceding a switch from one browser window to another.",
         "filters": [
           {
             "name": "windowTypes",
             "type": "array",
             "items": { "$ref": "WindowType" },
             "description": "Conditions that the window's type being removed must satisfy. By default it will satisfy <code>['app', 'normal', 'panel', 'popup']</code>, with <code>'app'</code> and <code>'panel'</code> window types limited to the extension's own windows."
           }
         ],
         "parameters": [
-          {"type": "integer", "name": "windowId", "minimum": -1, "description": "ID of the newly focused window."}
+          {
+            "type": "integer",
+            "name": "windowId",
+            "minimum": -1,
+            "description": "ID of the newly focused window."
+          }
         ]
       }
     ]
   }
 ]
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -3,20 +3,20 @@
 "use strict";
 
 function* runTests(options) {
   function background(getTests) {
     // Gets the current details of the browser action, and returns a
     // promise that resolves to an object containing them.
     function getDetails(tabId) {
       return Promise.all([
-        new Promise(resolve => browser.browserAction.getTitle({tabId}, resolve)),
-        new Promise(resolve => browser.browserAction.getPopup({tabId}, resolve)),
-        new Promise(resolve => browser.browserAction.getBadgeText({tabId}, resolve)),
-        new Promise(resolve => browser.browserAction.getBadgeBackgroundColor({tabId}, resolve))]
+        browser.browserAction.getTitle({tabId}),
+        browser.browserAction.getPopup({tabId}),
+        browser.browserAction.getBadgeText({tabId}),
+        browser.browserAction.getBadgeBackgroundColor({tabId})]
       ).then(details => {
         return Promise.resolve({ title: details[0],
                                  popup: details[1],
                                  badge: details[2],
                                  badgeBackgroundColor: details[3] });
       });
     }
 
--- a/browser/components/extensions/test/browser/browser_ext_getViews.js
+++ b/browser/components/extensions/test/browser/browser_ext_getViews.js
@@ -15,26 +15,35 @@ function genericChecker() {
   browser.test.onMessage.addListener((msg, ...args) => {
     if (msg == kind + "-check-views") {
       let views = browser.extension.getViews();
       let counts = {
         "background": 0,
         "tab": 0,
         "popup": 0,
       };
+      let background;
       for (let i = 0; i < views.length; i++) {
         let view = views[i];
         browser.test.assertTrue(view.kind in counts, "view type is valid");
         counts[view.kind]++;
         if (view.kind == "background") {
           browser.test.assertTrue(view === browser.extension.getBackgroundPage(),
                                   "background page is correct");
+          background = view;
         }
       }
-      browser.test.sendMessage("counts", counts);
+      if (background) {
+        browser.runtime.getBackgroundPage().then(view => {
+          browser.test.assertEq(background, view, "runtime.getBackgroundPage() is correct");
+          browser.test.sendMessage("counts", counts);
+        });
+      } else {
+        browser.test.sendMessage("counts", counts);
+      }
     } else if (msg == kind + "-open-tab") {
       browser.tabs.create({windowId: args[0], url: browser.runtime.getURL("tab.html")});
     } else if (msg == kind + "-close-tab") {
       browser.tabs.query({
         windowId: args[0],
       }, tabs => {
         let tab = tabs.find(tab => tab.url.indexOf("tab.html") != -1);
         browser.tabs.remove(tab.id, () => {
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
@@ -11,18 +11,18 @@ function* runTests(options) {
     // promise that resolves to an object containing them.
     function getDetails() {
       return new Promise(resolve => {
         return browser.tabs.query({ active: true, currentWindow: true }, resolve);
       }).then(([tab]) => {
         let tabId = tab.id;
         browser.test.log(`Get details: tab={id: ${tabId}, url: ${JSON.stringify(tab.url)}}`);
         return Promise.all([
-          new Promise(resolve => browser.pageAction.getTitle({tabId}, resolve)),
-          new Promise(resolve => browser.pageAction.getPopup({tabId}, resolve))]);
+          browser.pageAction.getTitle({tabId}),
+          browser.pageAction.getPopup({tabId})]);
       }).then(details => {
         return Promise.resolve({ title: details[0],
                                  popup: details[1] });
       });
     }
 
 
     // Runs the next test in the `tests` array, checks the results,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_captureVisibleTab.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_captureVisibleTab.js
@@ -145,25 +145,25 @@ add_task(function* testCaptureVisibleTab
 add_task(function* testCaptureVisibleTabPermissions() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background: function(x) {
       browser.tabs.query({ currentWindow: true, active: true }, tab => {
-        try {
-          browser.tabs.captureVisibleTab(tab.windowId, () => {});
-        } catch (e) {
-          if (e.message == "The <all_urls> permission is required to use the captureVisibleTab API") {
+        browser.tabs.captureVisibleTab(tab.windowId).then(
+          () => {
+            browser.test.notifyFail("captureVisibleTabPermissions");
+          },
+          (e) => {
+            browser.test.assertEq("The <all_urls> permission is required to use the captureVisibleTab API",
+                                  e.message, "Expected permissions error message");
             browser.test.notifyPass("captureVisibleTabPermissions");
-            return;
-          }
-        }
-        browser.test.notifyFail("captureVisibleTabPermissions");
+          });
       });
     },
   });
 
   yield extension.startup();
 
   yield extension.awaitFinish("captureVisibleTabPermissions");
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_create.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_create.js
@@ -26,27 +26,16 @@ add_task(function* () {
       "bg/blank.html": `<html><head><meta charset="utf-8"></head></html>`,
 
       "bg/background.html": `<html><head>
         <meta charset="utf-8">
         <script src="background.js"></script>
       </head></html>`,
 
       "bg/background.js": function() {
-        // Wrap API methods in promise-based variants.
-        let promiseTabs = {};
-        Object.keys(browser.tabs).forEach(method => {
-          promiseTabs[method] = (...args) => {
-            return new Promise(resolve => {
-              browser.tabs[method](...args, resolve);
-            });
-          };
-        });
-
-
         let activeTab;
         let activeWindow;
 
         function runTests() {
           const DEFAULTS = {
             index: 2,
             windowId: activeWindow,
             active: true,
@@ -127,17 +116,17 @@ add_task(function* () {
               let onCreated = tab => {
                 browser.test.assertTrue("id" in tab, `Expected tabs.onCreated callback to receive tab object`);
                 resolve();
               };
               browser.tabs.onCreated.addListener(onCreated);
             });
 
             Promise.all([
-              promiseTabs.create(test.create),
+              browser.tabs.create(test.create),
               createdPromise,
             ]).then(([tab]) => {
               tabId = tab.id;
 
               for (let key of Object.keys(expected)) {
                 if (key === "url") {
                   // FIXME: This doesn't get updated until later in the load cycle.
                   continue;
@@ -145,19 +134,19 @@ add_task(function* () {
 
                 browser.test.assertEq(expected[key], tab[key], `Expected value for tab.${key}`);
               }
 
               return updatedPromise;
             }).then(url => {
               browser.test.assertEq(expected.url, url, `Expected value for tab.url`);
 
-              return promiseTabs.remove(tabId);
+              return browser.tabs.remove(tabId);
             }).then(() => {
-              return promiseTabs.update(activeTab, { active: true });
+              return browser.tabs.update(activeTab, { active: true });
             }).then(() => {
               nextTest();
             });
           }
 
           nextTest();
         }
 
--- a/browser/components/syncedtabs/SyncedTabsDeckComponent.js
+++ b/browser/components/syncedtabs/SyncedTabsDeckComponent.js
@@ -77,17 +77,18 @@ SyncedTabsDeckComponent.prototype = {
     this._deckView = new this._DeckView(this._window, this.tabListComponent, {
       onAndroidClick: event => this.openAndroidLink(event),
       oniOSClick: event => this.openiOSLink(event),
       onSyncPrefClick: event => this.openSyncPrefs(event)
     });
 
     this._deckStore.on("change", state => this._deckView.render(state));
     // Trigger the initial rendering of the deck view
-    this._deckStore.setPanels(Object.values(this.PANELS));
+    // Object.values only in nightly
+    this._deckStore.setPanels(Object.keys(this.PANELS).map(k => this.PANELS[k]));
     // Set the initial panel to display
     this.updatePanel();
   },
 
   uninit() {
     Services.obs.removeObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED);
     Services.obs.removeObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION);
     this._deckView.destroy();
--- a/browser/components/syncedtabs/TabListComponent.js
+++ b/browser/components/syncedtabs/TabListComponent.js
@@ -95,17 +95,17 @@ TabListComponent.prototype = {
   },
 
   onToggleBranch(id) {
     this._store.toggleBranch(id);
   },
 
   onBookmarkTab(uri, title) {
     this._window.top.PlacesCommandHook
-      .bookmarkLink(this._window.PlacesUtils.bookmarksMenuFolderId, uri, title)
+      .bookmarkLink(this._window.top.PlacesUtils.bookmarksMenuFolderId, uri, title)
       .catch(Cu.reportError);
   },
 
   onOpenTab(url, event) {
     this._window.openUILink(url, event);
   },
 
   onSyncRefresh() {
--- a/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js
+++ b/browser/components/syncedtabs/test/xpcshell/test_SyncedTabsDeckComponent.js
@@ -47,17 +47,19 @@ add_task(function* testInitUninit() {
   Assert.ok(ViewMock.args[0][2].onSyncPrefClick,
     "view is passed onSyncPrefClick prop");
 
   Assert.equal(component.container, view.container,
     "component returns view's container");
 
   Assert.ok(deckStore.on.calledOnce, "listener is added to store");
   Assert.equal(deckStore.on.args[0][0], "change");
-  Assert.ok(deckStore.setPanels.calledWith(Object.values(component.PANELS)),
+  // Object.values only in nightly
+  let values = Object.keys(component.PANELS).map(k => component.PANELS[k]);
+  Assert.ok(deckStore.setPanels.calledWith(values),
     "panels are set on deck store");
 
   Assert.ok(component.updatePanel.called);
 
   deckStore.emit("change", "mock state");
   Assert.ok(view.render.calledWith("mock state"),
     "view.render is called on state change");
 
--- a/browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js
+++ b/browser/components/syncedtabs/test/xpcshell/test_TabListComponent.js
@@ -66,20 +66,20 @@ add_task(function* testInitUninit() {
 });
 
 add_task(function* testActions() {
   let store = new SyncedTabsListStore();
   let windowMock = {
     top: {
       PlacesCommandHook: {
         bookmarkLink() { return Promise.resolve(); }
-      }
+      },
+      PlacesUtils: { bookmarksMenuFolderId: "id" }
     },
-    openUILink() {},
-    PlacesUtils: { bookmarksMenuFolderId: "id" },
+    openUILink() {}
   };
   let component = new TabListComponent({
     window: windowMock, store, View: null, SyncedTabs});
 
   sinon.stub(store, "getData");
   component.onFilter("query");
   Assert.ok(store.getData.calledWith("query"));
 
--- a/configure.in
+++ b/configure.in
@@ -8535,16 +8535,17 @@ AC_SUBST(MOZ_D3DCOMPILER_XP_CAB)
 AC_SUBST(MOZ_ANDROID_HISTORY)
 AC_SUBST(MOZ_WEBSMS_BACKEND)
 AC_SUBST(MOZ_ANDROID_BEAM)
 AC_SUBST(MOZ_LOCALE_SWITCHER)
 AC_SUBST(MOZ_DISABLE_GECKOVIEW)
 AC_SUBST(MOZ_ANDROID_GCM)
 AC_SUBST(MOZ_ANDROID_GECKOLIBS_AAR)
 AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
+AC_SUBST(MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER)
 AC_SUBST(MOZ_ANDROID_MLS_STUMBLER)
 AC_SUBST(MOZ_ANDROID_DOWNLOADS_INTEGRATION)
 AC_SUBST(MOZ_ANDROID_APPLICATION_CLASS)
 AC_SUBST(MOZ_ANDROID_BROWSER_INTENT_CLASS)
 AC_SUBST(MOZ_ANDROID_SEARCH_INTENT_CLASS)
 AC_SUBST(MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE)
 AC_SUBST(MOZ_EXCLUDE_HYPHENATION_DICTIONARIES)
 AC_SUBST(MOZ_INSTALL_TRACKING)
--- a/devtools/client/aboutdebugging/aboutdebugging.js
+++ b/devtools/client/aboutdebugging/aboutdebugging.js
@@ -1,19 +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/. */
 
 /* eslint-env browser */
 /* global AddonsComponent, DebuggerClient, DebuggerServer, React,
-   RuntimesComponent, WorkersComponent */
+   WorkersComponent */
 
 "use strict";
 
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { classes: Cc, interfaces: Ci } = Components;
 const { loader } = Components.utils.import(
   "resource://devtools/shared/Loader.jsm", {});
 
 loader.lazyRequireGetter(this, "AddonsComponent",
   "devtools/client/aboutdebugging/components/addons", true);
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "DebuggerServer",
@@ -139,17 +139,17 @@ var AboutDebugging = {
     let file = fp.file;
     // AddonManager.installTemporaryAddon accepts either
     // addon directory or final xpi file.
     if (!file.isDirectory() && !file.leafName.endsWith(".xpi")) {
       file = file.parent;
     }
     try {
       AddonManager.installTemporaryAddon(file);
-    } catch(e) {
+    } catch (e) {
       alert("Error while installing the addon:\n" + e.message + "\n");
       throw e;
     }
   },
 
   destroy() {
     let telemetry = this._telemetry;
     telemetry.toolClosed("aboutdebugging");
--- a/devtools/client/aboutdebugging/test/addons/unpacked/bootstrap.js
+++ b/devtools/client/aboutdebugging/test/addons/unpacked/bootstrap.js
@@ -1,7 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env browser */
+/* exported startup, shutdown, install, uninstall */
+
+"use strict";
+
 Components.utils.import("resource://gre/modules/Services.jsm");
 function startup() {
   Services.obs.notifyObservers(null, "test-devtools", null);
 }
 function shutdown() {}
 function install() {}
 function uninstall() {}
--- a/devtools/client/aboutdebugging/test/browser_addons_install.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_install.js
@@ -1,27 +1,30 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
 const ADDON_ID = "test-devtools@mozilla.org";
 const ADDON_NAME = "test-devtools";
 
-add_task(function *() {
+add_task(function* () {
   let { tab, document } = yield openAboutDebugging("addons");
 
   yield installAddon(document, "addons/unpacked/install.rdf", "test-devtools");
 
   // Check that the addon appears in the UI
   let names = [...document.querySelectorAll("#addons .target-name")];
   names = names.map(element => element.textContent);
-  ok(names.includes(ADDON_NAME), "The addon name appears in the list of addons: " + names);
+  ok(names.includes(ADDON_NAME),
+    "The addon name appears in the list of addons: " + names);
 
   // Now uninstall this addon
   yield uninstallAddon(ADDON_ID);
 
   // Ensure that the UI removes the addon from the list
   names = [...document.querySelectorAll("#addons .target-name")];
   names = names.map(element => element.textContent);
-  ok(!names.includes(ADDON_NAME), "After uninstall, the addon name disappears from the list of addons: " + names);
+  ok(!names.includes(ADDON_NAME),
+    "After uninstall, the addon name disappears from the list of addons: "
+    + names);
 
   yield closeAboutDebugging(tab);
 });
--- a/devtools/client/aboutdebugging/test/browser_service_workers.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers.js
@@ -1,52 +1,57 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-disable mozilla/no-cpows-in-tests */
+/* global sendAsyncMessage */
+
 "use strict";
 
 // Service workers can't be loaded from chrome://,
 // but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
 const HTTP_ROOT = CHROME_ROOT.replace("chrome://mochitests/content/",
                                       "http://mochi.test:8888/");
 const SERVICE_WORKER = HTTP_ROOT + "service-workers/empty-sw.js";
 const TAB_URL = HTTP_ROOT + "service-workers/empty-sw.html";
 
-add_task(function *() {
+add_task(function* () {
   yield new Promise(done => {
     let options = {"set": [
-                    ["dom.serviceWorkers.testing.enabled", true],
-                  ]};
+      ["dom.serviceWorkers.testing.enabled", true],
+    ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   let swTab = yield addTab(TAB_URL);
 
   let serviceWorkersElement = document.getElementById("service-workers");
   yield waitForMutation(serviceWorkersElement, { childList: true });
 
   // Check that the service worker appears in the UI
   let names = [...document.querySelectorAll("#service-workers .target-name")];
   names = names.map(element => element.textContent);
-  ok(names.includes(SERVICE_WORKER), "The service worker url appears in the list: " + names);
+  ok(names.includes(SERVICE_WORKER),
+    "The service worker url appears in the list: " + names);
 
   // Finally, unregister the service worker itself
   let aboutDebuggingUpdate = waitForMutation(serviceWorkersElement,
     { childList: true });
 
   // Use message manager to work with e10s
-  let frameScript = function () {
+  let frameScript = function() {
     // Retrieve the `sw` promise created in the html page
     let { sw } = content.wrappedJSObject;
-    sw.then(function (registration) {
-      registration.unregister().then(function (success) {
+    sw.then(function(registration) {
+      registration.unregister().then(function() {
         sendAsyncMessage("sw-unregistered");
       },
-      function (e) {
+      function(e) {
         dump("SW not unregistered; " + e + "\n");
       });
     });
   };
   let mm = swTab.linkedBrowser.messageManager;
   mm.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
 
   yield new Promise(done => {
@@ -57,13 +62,14 @@ add_task(function *() {
   });
   ok(true, "Service worker registration unregistered");
 
   yield aboutDebuggingUpdate;
 
   // Check that the service worker disappeared from the UI
   names = [...document.querySelectorAll("#service-workers .target-name")];
   names = names.map(element => element.textContent);
-  ok(!names.includes(SERVICE_WORKER), "The service worker url is no longer in the list: " + names);
+  ok(!names.includes(SERVICE_WORKER),
+    "The service worker url is no longer in the list: " + names);
 
   yield removeTab(swTab);
   yield closeAboutDebugging(tab);
 });
--- a/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
+++ b/devtools/client/aboutdebugging/test/browser_service_workers_timeout.js
@@ -1,54 +1,59 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-disable mozilla/no-cpows-in-tests */
+/* global sendAsyncMessage */
+
 "use strict";
 
 // Service workers can't be loaded from chrome://,
 // but http:// is ok with dom.serviceWorkers.testing.enabled turned on.
 const HTTP_ROOT = CHROME_ROOT.replace("chrome://mochitests/content/",
                                       "http://mochi.test:8888/");
 const SERVICE_WORKER = HTTP_ROOT + "service-workers/empty-sw.js";
 const TAB_URL = HTTP_ROOT + "service-workers/empty-sw.html";
 
 const SW_TIMEOUT = 1000;
 
 function assertHasWorker(expected, document, type, name) {
   let names = [...document.querySelectorAll("#" + type + " .target-name")];
   names = names.map(element => element.textContent);
-  is(names.includes(name), expected, "The " + type + " url appears in the list: " + names);
+  is(names.includes(name), expected,
+      "The " + type + " url appears in the list: " + names);
 }
 
-add_task(function *() {
+add_task(function* () {
   yield new Promise(done => {
     let options = {"set": [
-                    // Accept workers from mochitest's http
-                    ["dom.serviceWorkers.testing.enabled", true],
-                    // Reduce the timeout to expose issues when service worker
-                    // freezing is broken
-                    ["dom.serviceWorkers.idle_timeout", SW_TIMEOUT],
-                    ["dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT],
-                  ]};
+      // Accept workers from mochitest's http
+      ["dom.serviceWorkers.testing.enabled", true],
+      // Reduce the timeout to expose issues when service worker
+      // freezing is broken
+      ["dom.serviceWorkers.idle_timeout", SW_TIMEOUT],
+      ["dom.serviceWorkers.idle_extended_timeout", SW_TIMEOUT],
+    ]};
     SpecialPowers.pushPrefEnv(options, done);
   });
 
   let { tab, document } = yield openAboutDebugging("workers");
 
   let swTab = yield addTab(TAB_URL);
 
   let serviceWorkersElement = document.getElementById("service-workers");
   yield waitForMutation(serviceWorkersElement, { childList: true });
 
   assertHasWorker(true, document, "service-workers", SERVICE_WORKER);
 
   // Ensure that the registration resolved before trying to connect to the sw
-  let frameScript = function () {
+  let frameScript = function() {
     // Retrieve the `sw` promise created in the html page
     let { sw } = content.wrappedJSObject;
-    sw.then(function (registration) {
+    sw.then(function() {
       sendAsyncMessage("sw-registered");
     });
   };
   let mm = swTab.linkedBrowser.messageManager;
   mm.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
 
   yield new Promise(done => {
     mm.addMessageListener("sw-registered", function listener() {
@@ -62,17 +67,17 @@ add_task(function *() {
   let names = [...document.querySelectorAll("#service-workers .target-name")];
   let name = names.filter(element => element.textContent === SERVICE_WORKER)[0];
   ok(name, "Found the service worker in the list");
   let debugBtn = name.parentNode.parentNode.querySelector("button");
   ok(debugBtn, "Found its debug button");
 
   // Click on it and wait for the toolbox to be ready
   let onToolboxReady = new Promise(done => {
-    gDevTools.once("toolbox-ready", function (e, toolbox) {
+    gDevTools.once("toolbox-ready", function(e, toolbox) {
       done(toolbox);
     });
   });
   debugBtn.click();
 
   let toolbox = yield onToolboxReady;
 
   // Wait for more than the regular timeout,
@@ -91,24 +96,24 @@ add_task(function *() {
   // after we destroy the toolbox.
   // The list should update once it get destroyed.
   yield waitForMutation(serviceWorkersElement, { childList: true });
 
   assertHasWorker(false, document, "service-workers", SERVICE_WORKER);
 
   // Finally, unregister the service worker itself
   // Use message manager to work with e10s
-  frameScript = function () {
+  frameScript = function() {
     // Retrieve the `sw` promise created in the html page
     let { sw } = content.wrappedJSObject;
-    sw.then(function (registration) {
-      registration.unregister().then(function (success) {
+    sw.then(function(registration) {
+      registration.unregister().then(function() {
         sendAsyncMessage("sw-unregistered");
       },
-      function (e) {
+      function(e) {
         dump("SW not unregistered; " + e + "\n");
       });
     });
   };
   mm = swTab.linkedBrowser.messageManager;
   mm.loadFrameScript("data:,(" + encodeURIComponent(frameScript) + ")()", true);
 
   yield new Promise(done => {
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -1,11 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* eslint-env browser */
+/* exported openAboutDebugging, closeAboutDebugging, installAddon,
+   uninstallAddon, waitForMutation */
+
 "use strict";
 
 var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
 
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {AddonManager} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 const Services = require("Services");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
@@ -32,65 +36,66 @@ function openAboutDebugging(page) {
   });
 }
 
 function closeAboutDebugging(tab) {
   info("Closing about:debugging");
   return removeTab(tab);
 }
 
-function addTab(aUrl, aWindow) {
-  info("Adding tab: " + aUrl);
+function addTab(url, win) {
+  info("Adding tab: " + url);
 
   return new Promise(done => {
-    let targetWindow = aWindow || window;
+    let targetWindow = win || window;
     let targetBrowser = targetWindow.gBrowser;
 
     targetWindow.focus();
-    let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
+    let tab = targetBrowser.selectedTab = targetBrowser.addTab(url);
     let linkedBrowser = tab.linkedBrowser;
 
     linkedBrowser.addEventListener("load", function onLoad() {
       linkedBrowser.removeEventListener("load", onLoad, true);
-      info("Tab added and finished loading: " + aUrl);
+      info("Tab added and finished loading: " + url);
       done(tab);
     }, true);
   });
 }
 
-function removeTab(aTab, aWindow) {
+function removeTab(tab, win) {
   info("Removing tab.");
 
   return new Promise(done => {
-    let targetWindow = aWindow || window;
+    let targetWindow = win || window;
     let targetBrowser = targetWindow.gBrowser;
     let tabContainer = targetBrowser.tabContainer;
 
-    tabContainer.addEventListener("TabClose", function onClose(aEvent) {
+    tabContainer.addEventListener("TabClose", function onClose() {
       tabContainer.removeEventListener("TabClose", onClose, false);
       info("Tab removed and finished closing.");
       done();
     }, false);
 
-    targetBrowser.removeTab(aTab);
+    targetBrowser.removeTab(tab);
   });
 }
 
-function get_supports_file(path) {
-  let cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
-  getService(Ci.nsIChromeRegistry);
-  let fileurl = cr.convertChromeURL(Services.io.newURI(CHROME_ROOT + path, null, null));
+function getSupportsFile(path) {
+  let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
+    .getService(Ci.nsIChromeRegistry);
+  let uri = Services.io.newURI(CHROME_ROOT + path, null, null);
+  let fileurl = cr.convertChromeURL(uri);
   return fileurl.QueryInterface(Ci.nsIFileURL);
 }
 
 function installAddon(document, path, evt) {
   // Mock the file picker to select a test addon
   let MockFilePicker = SpecialPowers.MockFilePicker;
   MockFilePicker.init(null);
-  let file = get_supports_file(path);
+  let file = getSupportsFile(path);
   MockFilePicker.returnFiles = [file.file];
 
   // Wait for a message sent by the addon's bootstrap.js file
   let onAddonInstalled = new Promise(done => {
     Services.obs.addObserver(function listener() {
       Services.obs.removeObserver(listener, evt, false);
       ok(true, "Addon installed and running its bootstrap.js file");
       done();
--- a/devtools/client/aboutdebugging/test/service-workers/empty-sw.html
+++ b/devtools/client/aboutdebugging/test/service-workers/empty-sw.html
@@ -1,20 +1,22 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="UTF-8">
   <title>Service worker test</title>
 </head>
 <body>
 <script type="text/javascript">
+"use strict";
+
 var sw = navigator.serviceWorker.register("empty-sw.js");
 sw.then(
-  function (registration) {
+  function() {
     dump("SW registered\n");
   },
-  function (e) {
+  function(e) {
     dump("SW not registered: " + e + "\n");
   }
 );
 </script>
 </body>
 </html>
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -56,16 +56,17 @@ support-files =
 [browser_rules_colorpicker-revert-on-ESC.js]
 [browser_rules_colorpicker-swatch-displayed.js]
 [browser_rules_colorUnit.js]
 [browser_rules_completion-existing-property_01.js]
 [browser_rules_completion-existing-property_02.js]
 [browser_rules_completion-new-property_01.js]
 [browser_rules_completion-new-property_02.js]
 [browser_rules_completion-new-property_03.js]
+[browser_rules_completion-new-property_04.js]
 [browser_rules_computed-lists_01.js]
 [browser_rules_computed-lists_02.js]
 [browser_rules_completion-popup-hidden-after-navigation.js]
 [browser_rules_content_01.js]
 [browser_rules_content_02.js]
 skip-if = e10s # Bug 1039528: "inspect element" contextual-menu doesn't work with e10s
 [browser_rules_context-menu-show-mdn-docs-01.js]
 [browser_rules_context-menu-show-mdn-docs-02.js]
--- a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_01.js
@@ -51,18 +51,18 @@ add_task(function*() {
   yield runAutocompletionTest(toolbox, inspector, view);
 });
 
 function* runAutocompletionTest(toolbox, inspector, view) {
   info("Selecting the test node");
   yield selectNode("h1", inspector);
 
   info("Focusing the css property editable field");
-  let brace = view.styleDocument.querySelector(".ruleview-ruleclose");
-  let editor = yield focusEditableField(view, brace);
+  let ruleEditor = getRuleViewRuleEditor(view, 0);
+  let editor = yield focusNewRuleViewProperty(ruleEditor);
 
   info("Starting to test for css property completion");
   for (let i = 0; i < testData.length; i++) {
     yield testCompletion(testData[i], editor, view);
   }
 }
 
 function* testCompletion([key, completion, index, total], editor, view) {
--- a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
@@ -60,18 +60,18 @@ add_task(function*() {
   yield runAutocompletionTest(toolbox, inspector, view);
 });
 
 function* runAutocompletionTest(toolbox, inspector, view) {
   info("Selecting the test node");
   yield selectNode("h1", inspector);
 
   info("Focusing a new css property editable property");
-  let brace = view.styleDocument.querySelectorAll(".ruleview-ruleclose")[1];
-  let editor = yield focusEditableField(view, brace);
+  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let editor = yield focusNewRuleViewProperty(ruleEditor);
 
   info("Starting to test for css property completion");
   for (let i = 0; i < testData.length; i++) {
     // Re-define the editor at each iteration, because the focus may have moved
     // from property to value and back
     editor = inplaceEditor(view.styleDocument.activeElement);
     yield testCompletion(testData[i], editor, view);
   }
--- a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_03.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_03.js
@@ -16,19 +16,19 @@ add_task(function*() {
   info("Test autocompletion for background-color");
   yield runAutocompletionTest(toolbox, inspector, view);
 });
 
 function* runAutocompletionTest(toolbox, inspector, view) {
   info("Selecting the test node");
   yield selectNode("h1", inspector);
 
-  info("Focusing the css property editable field");
-  let brace = view.styleDocument.querySelector(".ruleview-ruleclose");
-  let editor = yield focusEditableField(view, brace);
+  info("Focusing the new property editable field");
+  let ruleEditor = getRuleViewRuleEditor(view, 0);
+  let editor = yield focusNewRuleViewProperty(ruleEditor);
 
   info("Sending \"background\" to the editable field");
   for (let key of "background") {
     let onSuggest = editor.once("after-suggest");
     EventUtils.synthesizeKey(key, {}, view.styleWindow);
     yield onSuggest;
   }
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_04.js
@@ -0,0 +1,72 @@
+/* vim: set ft=javascript 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 that a new property editor supports the following flow:
+// - type first character of property name
+// - select an autocomplete suggestion !!with a mouse click!!
+// - press RETURN to move to the property value
+// - blur the input to commit
+
+const TEST_URI = "<style>.title {margin: 0;}</style>" +
+  "<h1 class=title style='color: red'>Header</h1>";
+
+add_task(function*() {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let { inspector, view} = yield openRuleView();
+
+  info("Selecting the test node");
+  yield selectNode("h1", inspector);
+
+  info("Focusing the new property editable field");
+  let ruleEditor = getRuleViewRuleEditor(view, 0);
+  let editor = yield focusNewRuleViewProperty(ruleEditor);
+
+  info("Sending \"background\" to the editable field.");
+  for (let key of "background") {
+    let onSuggest = editor.once("after-suggest");
+    EventUtils.synthesizeKey(key, {}, view.styleWindow);
+    yield onSuggest;
+  }
+
+  const itemIndex = 4;
+  let bgcItem = editor.popup.getItemAtIndex(itemIndex);
+  is(bgcItem.label, "background-color",
+    "Check the expected completion element is background-color.");
+  editor.popup.selectedIndex = itemIndex;
+
+  info("Select the background-color suggestion with a mouse click.");
+  let onInputFocus = once(editor.input, "focus", true);
+  let node = editor.popup._list.childNodes[itemIndex];
+  EventUtils.synthesizeMouseAtCenter(node, {}, view.styleWindow);
+  yield onInputFocus;
+  is(editor.input.value, "background-color", "Correct value is autocompleted");
+
+  info("Press RETURN to move the focus to a property value editor.");
+  let onModifications = view.once("ruleview-changed");
+  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+  yield onModifications;
+
+  // Getting the new value editor after focus
+  let elementRuleEditor = getRuleViewRuleEditor(view, 0);
+  editor = inplaceEditor(view.styleDocument.activeElement);
+  let textProp = elementRuleEditor.rule.textProps[1];
+
+  is(elementRuleEditor.rule.textProps.length, 2,
+    "Created a new text property.");
+  is(elementRuleEditor.propertyList.children.length, 2,
+    "Created a property editor.");
+  is(editor, inplaceEditor(textProp.editor.valueSpan),
+    "Editing the value span now.");
+
+  info("Entering a value and blurring the field to expect a rule change");
+  editor.input.value = "#F00";
+
+  onModifications = view.once("ruleview-changed");
+  editor.input.blur();
+  yield onModifications;
+
+  is(textProp.value, "#F00", "Text prop should have been changed.");
+});
--- a/devtools/client/inspector/rules/test/browser_rules_keybindings.js
+++ b/devtools/client/inspector/rules/test/browser_rules_keybindings.js
@@ -10,27 +10,23 @@
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8,<h1>Some header text</h1>");
   let {inspector, view} = yield openRuleView();
   yield selectNode("h1", inspector);
 
   info("Getting the ruleclose brace element");
   let brace = view.styleDocument.querySelector(".ruleview-ruleclose");
 
-  info("Clicking on the brace element to focus the new property field");
-  let onFocus = once(brace.parentNode, "focus", true);
-  brace.click();
-  yield onFocus;
-
-  info("Entering a property name");
-  let editor = getCurrentInplaceEditor(view);
+  info("Focus the new property editable field to create a color property");
+  let ruleEditor = getRuleViewRuleEditor(view, 0);
+  let editor = yield focusNewRuleViewProperty(ruleEditor);
   editor.input.value = "color";
 
   info("Typing ENTER to focus the next field: property value");
-  onFocus = once(brace.parentNode, "focus", true);
+  let onFocus = once(brace.parentNode, "focus", true);
   // The rule view changes twice, once for the first field to loose focus
   // and a second time for the second field to gain focus
   let onRuleViewChanged = view.once("ruleview-changed").then(
     () => view.once("ruleview-changed"));
   EventUtils.sendKey("return");
   yield onFocus;
   yield onRuleViewChanged;
   ok(true, "The value field was focused");
--- a/devtools/client/memory/actions/diffing.js
+++ b/devtools/client/memory/actions/diffing.js
@@ -45,17 +45,17 @@ const selectSnapshotForDiffing = exports
  */
 const takeCensusDiff = exports.takeCensusDiff = function (heapWorker, first, second) {
   return function*(dispatch, getState) {
     assert(snapshotIsDiffable(first),
            `First snapshot must be in a diffable state, found ${first.state}`);
     assert(snapshotIsDiffable(second),
            `Second snapshot must be in a diffable state, found ${second.state}`);
 
-    let report;
+    let report, parentMap;
     let inverted = getState().inverted;
     let breakdown = getState().breakdown;
     let filter = getState().filter;
 
     if (censusIsUpToDate(inverted, filter, breakdown, getState().diffing.census)) {
       return;
     }
 
@@ -82,35 +82,36 @@ const takeCensusDiff = exports.takeCensu
         filter,
         breakdown,
       });
 
       let opts = inverted ? { asInvertedTreeNode: true } : { asTreeNode: true };
       opts.filter = filter || null;
 
       try {
-        report = yield heapWorker.takeCensusDiff(first.path,
-                                                 second.path,
-                                                 { breakdown },
-                                                 opts);
+        ({ delta: report, parentMap } = yield heapWorker.takeCensusDiff(first.path,
+                                                                        second.path,
+                                                                        { breakdown },
+                                                                        opts));
       } catch (error) {
         reportException("actions/diffing/takeCensusDiff", error);
         dispatch({ type: actions.DIFFING_ERROR, error });
         return;
       }
     }
     while (inverted !== getState().inverted
            || filter !== getState().filter
            || !breakdownEquals(breakdown, getState().breakdown));
 
     dispatch({
       type: actions.TAKE_CENSUS_DIFF_END,
       first,
       second,
       report,
+      parentMap,
       inverted,
       filter,
       breakdown,
     });
 
     telemetry.countDiff({ inverted, filter, breakdown });
   };
 };
--- a/devtools/client/memory/actions/snapshot.js
+++ b/devtools/client/memory/actions/snapshot.js
@@ -131,17 +131,17 @@ const readSnapshot = exports.readSnapsho
  * @see `js/src/doc/Debugger/Debugger.Memory.md` for breakdown details
  */
 const takeCensus = exports.takeCensus = function (heapWorker, id) {
   return function *(dispatch, getState) {
     const snapshot = getSnapshot(getState(), id);
     assert([states.READ, states.SAVED_CENSUS].includes(snapshot.state),
       `Can only take census of snapshots in READ or SAVED_CENSUS state, found ${snapshot.state}`);
 
-    let report;
+    let report, parentMap;
     let inverted = getState().inverted;
     let breakdown = getState().breakdown;
     let filter = getState().filter;
 
     // If breakdown, filter and inversion haven't changed, don't do anything.
     if (censusIsUpToDate(inverted, filter, breakdown, snapshot.census)) {
       return;
     }
@@ -161,34 +161,37 @@ const takeCensus = exports.takeCensus = 
         filter,
         breakdown
       });
 
       let opts = inverted ? { asInvertedTreeNode: true } : { asTreeNode: true };
       opts.filter = filter || null;
 
       try {
-        report = yield heapWorker.takeCensus(snapshot.path, { breakdown }, opts);
+        ({ report, parentMap } = yield heapWorker.takeCensus(snapshot.path,
+                                                             { breakdown },
+                                                             opts));
       } catch (error) {
         reportException("takeCensus", error);
         dispatch({ type: actions.SNAPSHOT_ERROR, id, error });
         return;
       }
     }
     while (inverted !== getState().inverted ||
            filter !== getState().filter ||
            !breakdownEquals(breakdown, getState().breakdown));
 
     dispatch({
       type: actions.TAKE_CENSUS_END,
       id,
       breakdown,
       inverted,
       filter,
-      report
+      report,
+      parentMap
     });
 
     telemetry.countCensus({ inverted, filter, breakdown });
   };
 };
 
 /**
  * Refresh the selected snapshot's census data, if need be (for example,
--- a/devtools/client/memory/components/census.js
+++ b/devtools/client/memory/components/census.js
@@ -27,17 +27,17 @@ const Census = module.exports = createCl
       onExpand,
       onCollapse,
       onFocus,
       diffing,
       onViewSourceInDebugger,
     } = this.props;
 
     const report = census.report;
-    let parentMap = createParentMap(report);
+    let parentMap = census.parentMap;
     const { totalBytes, totalCount } = report;
 
     const getPercentBytes = totalBytes === 0
       ? _ => 0
       : bytes => (bytes / totalBytes) * 100;
 
     const getPercentCount = totalCount === 0
       ? _ => 0
--- a/devtools/client/memory/components/dominator-tree.js
+++ b/devtools/client/memory/components/dominator-tree.js
@@ -1,17 +1,18 @@
 /* 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 { DOM: dom, createClass, PropTypes, createFactory } = require("devtools/client/shared/vendor/react");
 const { assert, safeErrorString } = require("devtools/shared/DevToolsUtils");
+const { createParentMap } = require("devtools/shared/heapsnapshot/CensusUtils");
 const Tree = createFactory(require("devtools/client/shared/components/tree"));
 const DominatorTreeItem = createFactory(require("./dominator-tree-item"));
-const { createParentMap, L10N } = require("../utils");
+const { L10N } = require("../utils");
 const { TREE_ROW_HEIGHT, dominatorTreeState } = require("../constants");
 const { dominatorTreeModel } = require("../models");
 const DominatorTreeLazyChildren = require("../dominator-tree-lazy-children");
 
 const DOMINATOR_TREE_AUTO_EXPAND_DEPTH = 3;
 
 /**
  * A throbber that represents a subtree in the dominator tree that is actively
--- a/devtools/client/memory/models.js
+++ b/devtools/client/memory/models.js
@@ -51,16 +51,18 @@ function catchAndIgnore(fn) {
  */
 let breakdownModel = exports.breakdown = PropTypes.shape({
   by: PropTypes.oneOf(["coarseType", "allocationStack", "objectClass", "internalType"]).isRequired,
 });
 
 let censusModel = exports.censusModel = PropTypes.shape({
   // The current census report data.
   report: PropTypes.object,
+  // The parent map for the report.
+  parentMap: PropTypes.object,
   // The breakdown used to generate the current census
   breakdown: breakdownModel,
   // Whether the currently cached report tree is inverted or not.
   inverted: PropTypes.bool,
   // If present, the currently cached report's filter string used for pruning
   // the tree items.
   filter: PropTypes.string,
   // The Set<CensusTreeNode.id> of expanded node ids in the report tree.
--- a/devtools/client/memory/reducers/diffing.js
+++ b/devtools/client/memory/reducers/diffing.js
@@ -76,16 +76,17 @@ handlers[actions.TAKE_CENSUS_DIFF_END] =
          "First snapshot's id should match");
   assert(action.second.id === diffing.secondSnapshotId,
          "Second snapshot's id should match");
 
   return immutableUpdate(diffing, {
     state: diffingState.TOOK_DIFF,
     census: {
       report: action.report,
+      parentMap: action.parentMap,
       expanded: new Set(),
       inverted: action.inverted,
       filter: action.filter,
       breakdown: action.breakdown,
     }
   });
 };
 
--- a/devtools/client/memory/reducers/snapshots.js
+++ b/devtools/client/memory/reducers/snapshots.js
@@ -62,19 +62,25 @@ handlers[actions.TAKE_CENSUS_START] = fu
 
   return snapshots.map(snapshot => {
     return snapshot.id === id
       ? immutableUpdate(snapshot, { state: states.SAVING_CENSUS, census })
       : snapshot;
   });
 };
 
-handlers[actions.TAKE_CENSUS_END] = function (snapshots, { id, report, breakdown, inverted, filter }) {
+handlers[actions.TAKE_CENSUS_END] = function (snapshots, { id,
+                                                           report,
+                                                           parentMap,
+                                                           breakdown,
+                                                           inverted,
+                                                           filter }) {
   const census = {
     report,
+    parentMap,
     expanded: new Set(),
     breakdown,
     inverted,
     filter,
   };
 
   return snapshots.map(snapshot => {
     return snapshot.id === id
--- a/devtools/client/memory/store.js
+++ b/devtools/client/memory/store.js
@@ -11,17 +11,18 @@ const DevToolsUtils = require("devtools/
 module.exports = function () {
   let shouldLog = false;
   let history;
 
   // If testing, store the action history in an array
   // we'll later attach to the store
   if (DevToolsUtils.testing) {
     history = [];
-    shouldLog = true;
+    // Uncomment this for TONS of logging in tests.
+    // shouldLog = true;
   }
 
   let store = createStore({
     log: shouldLog,
     history
   })(combineReducers(reducers), {});
 
   if (history) {
--- a/devtools/client/memory/test/browser/browser.ini
+++ b/devtools/client/memory/test/browser/browser.ini
@@ -2,16 +2,17 @@
 tags = devtools devtools-memory
 subsuite = devtools
 support-files =
   head.js
   doc_big_tree.html
   doc_steady_allocation.html
 
 [browser_memory_allocationStackBreakdown_01.js]
+    skip-if = debug # bug 1219554
 [browser_memory_breakdowns_01.js]
 [browser_memory_clear_snapshots.js]
 [browser_memory_diff_01.js]
 [browser_memory_dominator_trees_01.js]
 [browser_memory_dominator_trees_02.js]
 [browser_memory_filter_01.js]
 [browser_memory_no_allocation_stacks.js]
 [browser_memory_no_auto_expand.js]
--- a/devtools/client/memory/utils.js
+++ b/devtools/client/memory/utils.js
@@ -556,30 +556,8 @@ exports.formatNumber = function(number, 
  *
  * @param {Number} percent
  * @param {Boolean} showSign (defaults to false)
  */
 exports.formatPercent = function(percent, showSign = false) {
   return exports.L10N.getFormatStr("tree-item.percent",
                            exports.formatNumber(percent, showSign));
 };
-
-/**
- * Creates a hash map mapping node IDs to its parent node.
- *
- * @param {CensusTreeNode} node
- * @param {Object<number, TreeNode>} aggregator
- *
- * @return {Object<number, TreeNode>}
- */
-const createParentMap = exports.createParentMap = function (node,
-                                                            getId = node => node.id,
-                                                            aggregator = Object.create(null)) {
-  if (node.children) {
-    for (let i = 0, length = node.children.length; i < length; i++) {
-      const child = node.children[i];
-      aggregator[getId(child)] = node;
-      createParentMap(child, getId, aggregator);
-    }
-  }
-
-  return aggregator;
-};
--- a/devtools/client/shared/inplace-editor.js
+++ b/devtools/client/shared/inplace-editor.js
@@ -815,17 +815,17 @@ InplaceEditor.prototype = {
     }
 
     return null;
   },
 
   /**
    * Handle loss of focus by calling done if it hasn't been called yet.
    */
-  _onBlur: function(event, doNotClear) {
+  _onBlur: function(event) {
     if (event && this.popup && this.popup.isOpen &&
         this.popup.selectedIndex >= 0) {
       let label, preLabel;
 
       if (this._selectedIndex === undefined) {
         ({label, preLabel} =
           this.popup.getItemAtIndex(this.popup.selectedIndex));
       } else {
@@ -861,29 +861,21 @@ InplaceEditor.prototype = {
         this.popup._panel.removeEventListener("popuphidden", onPopupHidden);
         this.doc.defaultView.setTimeout(()=> {
           input.focus();
           this.emit("after-suggest");
         }, 0);
       };
       this.popup._panel.addEventListener("popuphidden", onPopupHidden);
       this.popup.hidePopup();
-      // Content type other than CSS_MIXED is used in rule-view where the values
-      // are live previewed. So we apply the value before returning.
-      if (this.contentType != CONTENT_TYPES.CSS_MIXED) {
-        this._apply();
-      }
       return;
     }
 
     this._apply();
-
-    if (!doNotClear) {
-      this._clear();
-    }
+    this._clear();
   },
 
   /**
    * Handle the input field's keypress event.
    */
   _onKeyPress: function(event) {
     let prevent = false;
 
--- a/devtools/shared/heapsnapshot/CensusUtils.js
+++ b/devtools/shared/heapsnapshot/CensusUtils.js
@@ -355,8 +355,30 @@ DiffVisitor.prototype.results = function
  *            - {Number} basisTotalCount: the total count in the start census.
  */
 function diff(breakdown, startCensus, endCensus) {
   const visitor = new DiffVisitor(endCensus);
   walk(breakdown, startCensus, visitor);
   return visitor.results();
 };
 exports.diff = diff
+
+/**
+ * Creates a hash map mapping node IDs to its parent node.
+ *
+ * @param {CensusTreeNode} node
+ * @param {Object<number, TreeNode>} aggregator
+ *
+ * @return {Object<number, TreeNode>}
+ */
+const createParentMap = exports.createParentMap = function (node,
+                                                            getId = node => node.id,
+                                                            aggregator = Object.create(null)) {
+  if (node.children) {
+    for (let i = 0, length = node.children.length; i < length; i++) {
+      const child = node.children[i];
+      aggregator[getId(child)] = getId(node);
+      createParentMap(child, getId, aggregator);
+    }
+  }
+
+  return aggregator;
+};
--- a/devtools/shared/heapsnapshot/HeapAnalysesClient.js
+++ b/devtools/shared/heapsnapshot/HeapAnalysesClient.js
@@ -16,17 +16,17 @@ var workerCounter = 0;
  * interacting with a HeapAnalysesWorker. This enables users to be ignorant of
  * the message passing protocol used to communicate with the worker. The
  * HeapAnalysesClient owns the worker, and terminating the worker is done by
  * terminating the client (see the `destroy` method).
  */
 const HeapAnalysesClient = module.exports = function () {
   this._worker = new DevToolsWorker(WORKER_URL, {
     name: `HeapAnalyses-${workerCounter++}`,
-    verbose: DevToolsUtils.dumpn.wantLogging
+    verbose: DevToolsUtils.dumpv.wantLogging
   });
 };
 
 /**
  * Destroy the worker, causing it to release its resources (such as heap
  * snapshots it has deserialized and read into memory). The client is no longer
  * usable after calling this method.
  */
@@ -99,20 +99,25 @@ HeapAnalysesClient.prototype.getCreation
  *          @see `devtools/shared/heapsnapshot/census-tree-node.js`
  *        - {Boolean} asInvertedTreeNode
  *          Whether or not the census is returned as an inverted
  *          CensusTreeNode. Defaults to false.
  *        - {String} filter
  *          A filter string to prune the resulting tree with. Only applies if
  *          either asTreeNode or asInvertedTreeNode is true.
  *
- * @returns Promise<census report|CensusTreeNode>
- *          The report generated by the given census breakdown, or
- *          a CensusTreeNode generated by the given census breakdown
- *          if `asTreeNode` is true.
+ * @returns Promise<Object>
+ *          An object with the following properties:
+ *          - report:
+ *            The report generated by the given census breakdown, or a
+ *            CensusTreeNode generated by the given census breakdown if
+ *            `asTreeNode` is true.
+ *          - parentMap:
+ *            The result of calling CensusUtils.createParentMap on the generated
+ *            report. Only exists if asTreeNode or asInvertedTreeNode are set.
  */
 HeapAnalysesClient.prototype.takeCensus = function (snapshotFilePath,
                                                     censusOptions,
                                                     requestOptions={}) {
   return this._worker.performTask("takeCensus", {
     snapshotFilePath,
     censusOptions,
     requestOptions,
@@ -142,20 +147,24 @@ HeapAnalysesClient.prototype.takeCensus 
  *          tree node before returned. Defaults to false.
  *        - {Boolean} asInvertedTreeNode
  *          Whether or not the census is returned as an inverted
  *          CensusTreeNode. Defaults to false.
  *        - {String} filter
  *          A filter string to prune the resulting tree with. Only applies if
  *          either asTreeNode or asInvertedTreeNode is true.
  *
- * @returns Promise<delta report|CensusTreeNode>
- *          The delta report generated by diffing the two census reports, or a
- *          CensusTreeNode generated from the delta report if
- *          `requestOptions.asTreeNode` was true.
+ * @returns Promise<Object>
+ *          - delta:
+ *            The delta report generated by diffing the two census reports, or a
+ *            CensusTreeNode generated from the delta report if
+ *            `requestOptions.asTreeNode` was true.
+ *          - parentMap:
+ *            The result of calling CensusUtils.createParentMap on the generated
+ *            delta. Only exists if asTreeNode or asInvertedTreeNode are set.
  */
 HeapAnalysesClient.prototype.takeCensusDiff = function (firstSnapshotFilePath,
                                                         secondSnapshotFilePath,
                                                         censusOptions,
                                                         requestOptions = {}) {
   return this._worker.performTask("takeCensusDiff", {
     firstSnapshotFilePath,
     secondSnapshotFilePath,
--- a/devtools/shared/heapsnapshot/HeapAnalysesWorker.js
+++ b/devtools/shared/heapsnapshot/HeapAnalysesWorker.js
@@ -53,27 +53,29 @@ workerHelper.createTask(self, "deleteHea
 /**
  * @see HeapAnalysesClient.prototype.takeCensus
  */
 workerHelper.createTask(self, "takeCensus", ({ snapshotFilePath, censusOptions, requestOptions }) => {
   if (!snapshots[snapshotFilePath]) {
     throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
   }
 
-  const report = snapshots[snapshotFilePath].takeCensus(censusOptions);
+  let report = snapshots[snapshotFilePath].takeCensus(censusOptions);
+  let parentMap;
 
   if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) {
     const opts = { filter: requestOptions.filter || null };
     if (requestOptions.asInvertedTreeNode) {
       opts.invert = true;
     }
-    return censusReportToCensusTreeNode(censusOptions.breakdown, report, opts);
+    report = censusReportToCensusTreeNode(censusOptions.breakdown, report, opts);
+    parentMap = CensusUtils.createParentMap(report);
   }
 
-  return report;
+  return { report, parentMap };
 });
 
 /**
  * @see HeapAnalysesClient.prototype.takeCensusDiff
  */
 workerHelper.createTask(self, "takeCensusDiff", request => {
   const {
     firstSnapshotFilePath,
@@ -87,27 +89,29 @@ workerHelper.createTask(self, "takeCensu
   }
 
   if (!snapshots[secondSnapshotFilePath]) {
     throw new Error(`No known heap snapshot for '${secondSnapshotFilePath}'`);
   }
 
   const first = snapshots[firstSnapshotFilePath].takeCensus(censusOptions);
   const second = snapshots[secondSnapshotFilePath].takeCensus(censusOptions);
-  const delta = CensusUtils.diff(censusOptions.breakdown, first, second);
+  let delta = CensusUtils.diff(censusOptions.breakdown, first, second);
+  let parentMap;
 
   if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) {
     const opts = { filter: requestOptions.filter || null };
     if (requestOptions.asInvertedTreeNode) {
       opts.invert = true;
     }
-    return censusReportToCensusTreeNode(censusOptions.breakdown, delta, opts);
+    delta = censusReportToCensusTreeNode(censusOptions.breakdown, delta, opts);
+    parentMap = CensusUtils.createParentMap(delta);
   }
 
-  return delta;
+  return { delta, parentMap };
 });
 
 /**
  * @see HeapAnalysesClient.prototype.getCreationTime
  */
 workerHelper.createTask(self, "getCreationTime", snapshotFilePath => {
   if (!snapshots[snapshotFilePath]) {
     throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_01.js
@@ -25,27 +25,27 @@ add_task(function* () {
   markers.push(allocationMarker());
 
   const secondSnapshotFilePath = saveNewHeapSnapshot();
 
   yield client.readHeapSnapshot(firstSnapshotFilePath);
   yield client.readHeapSnapshot(secondSnapshotFilePath);
   ok(true, "Should have read both heap snapshot files");
 
-  const delta = yield client.takeCensusDiff(firstSnapshotFilePath,
-                                            secondSnapshotFilePath,
-                                            { breakdown: BREAKDOWN });
+  const { delta } = yield client.takeCensusDiff(firstSnapshotFilePath,
+                                                secondSnapshotFilePath,
+                                                { breakdown: BREAKDOWN });
 
   equal(delta.AllocationMarker.count, 1,
     "There exists one new AllocationMarker in the second heap snapshot");
 
-  const deltaTreeNode = yield client.takeCensusDiff(firstSnapshotFilePath,
-                                                    secondSnapshotFilePath,
-                                                    { breakdown: BREAKDOWN },
-                                                    { asTreeNode: true });
+  const { delta: deltaTreeNode } = yield client.takeCensusDiff(firstSnapshotFilePath,
+                                                               secondSnapshotFilePath,
+                                                               { breakdown: BREAKDOWN },
+                                                               { asTreeNode: true });
 
   // Have to manually set these because symbol properties aren't structured
   // cloned.
   delta[CensusUtils.basisTotalBytes] = deltaTreeNode.totalBytes;
   delta[CensusUtils.basisTotalCount] = deltaTreeNode.totalCount;
 
   compareCensusViewData(BREAKDOWN, delta, deltaTreeNode,
     "Returning delta-census as a tree node represents same data as the report");
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_02.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_02.js
@@ -34,24 +34,24 @@ add_task(function* () {
   const secondSnapshotFilePath = saveNewHeapSnapshot();
 
   const client = new HeapAnalysesClient();
   yield client.readHeapSnapshot(firstSnapshotFilePath);
   yield client.readHeapSnapshot(secondSnapshotFilePath);
 
   ok(true, "Should have read both heap snapshot files");
 
-  const delta = yield client.takeCensusDiff(firstSnapshotFilePath,
-                                            secondSnapshotFilePath,
-                                            { breakdown: BREAKDOWN });
+  const { delta } = yield client.takeCensusDiff(firstSnapshotFilePath,
+                                                secondSnapshotFilePath,
+                                                { breakdown: BREAKDOWN });
 
-  const deltaTreeNode = yield client.takeCensusDiff(firstSnapshotFilePath,
-                                                    secondSnapshotFilePath,
-                                                    { breakdown: BREAKDOWN },
-                                                    { asInvertedTreeNode: true });
+  const { delta: deltaTreeNode } = yield client.takeCensusDiff(firstSnapshotFilePath,
+                                                               secondSnapshotFilePath,
+                                                               { breakdown: BREAKDOWN },
+                                                               { asInvertedTreeNode: true });
 
   // Have to manually set these because symbol properties aren't structured
   // cloned.
   delta[CensusUtils.basisTotalBytes] = deltaTreeNode.totalBytes;
   delta[CensusUtils.basisTotalCount] = deltaTreeNode.totalCount;
 
   compareCensusViewData(BREAKDOWN, delta, deltaTreeNode, { invert: true });
 
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_01.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_01.js
@@ -9,17 +9,17 @@ function run_test() {
 
 add_task(function* () {
   const client = new HeapAnalysesClient();
 
   const snapshotFilePath = saveNewHeapSnapshot();
   yield client.readHeapSnapshot(snapshotFilePath);
   ok(true, "Should have read the heap snapshot");
 
-  const report = yield client.takeCensus(snapshotFilePath);
+  const { report } = yield client.takeCensus(snapshotFilePath);
   ok(report, "Should get a report");
   equal(typeof report, "object", "report should be an object");
 
   ok(report.objects);
   ok(report.scripts);
   ok(report.strings);
   ok(report.other);
 
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_02.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_02.js
@@ -10,17 +10,17 @@ function run_test() {
 
 add_task(function* () {
   const client = new HeapAnalysesClient();
 
   const snapshotFilePath = saveNewHeapSnapshot();
   yield client.readHeapSnapshot(snapshotFilePath);
   ok(true, "Should have read the heap snapshot");
 
-  const report = yield client.takeCensus(snapshotFilePath, {
+  const { report } = yield client.takeCensus(snapshotFilePath, {
     breakdown: { by: "count", count: true, bytes: true }
   });
 
   ok(report, "Should get a report");
   equal(typeof report, "object", "report should be an object");
 
   ok(report.count);
   ok(report.bytes);
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_04.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_04.js
@@ -44,17 +44,17 @@ add_task(function* test() {
 
   const snapshotFilePath = saveNewHeapSnapshot({ debugger: dbg });
   yield client.readHeapSnapshot(snapshotFilePath);
   ok(true, "Should have read the heap snapshot");
 
   // Run a census broken down by class name -> allocation stack so we can grab
   // only the AllocationMarker objects we have complete control over.
 
-  const report = yield client.takeCensus(snapshotFilePath, {
+  const { report } = yield client.takeCensus(snapshotFilePath, {
     breakdown: { by: 'objectClass',
                  then: { by: 'allocationStack',
                          then: { by: 'count',
                                  bytes: true,
                                  count: true
                                },
                          noStack: { by: 'count',
                                     bytes: true,
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_05.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_05.js
@@ -15,21 +15,21 @@ const BREAKDOWN = {
 
 add_task(function* () {
   const client = new HeapAnalysesClient();
 
   const snapshotFilePath = saveNewHeapSnapshot();
   yield client.readHeapSnapshot(snapshotFilePath);
   ok(true, "Should have read the heap snapshot");
 
-  const report = yield client.takeCensus(snapshotFilePath, {
+  const { report } = yield client.takeCensus(snapshotFilePath, {
     breakdown: BREAKDOWN
   });
 
-  const treeNode = yield client.takeCensus(snapshotFilePath, {
+  const { report: treeNode } = yield client.takeCensus(snapshotFilePath, {
     breakdown: BREAKDOWN
   }, {
     asTreeNode: true
   });
 
   ok(treeNode.children.length > 0, "treeNode has children");
   ok(treeNode.children.every(type => {
     return "name" in type &&
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_06.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_06.js
@@ -54,21 +54,21 @@ add_task(function* () {
          `);
 
   const snapshotFilePath = saveNewHeapSnapshot({ debugger: dbg });
 
   const client = new HeapAnalysesClient();
   yield client.readHeapSnapshot(snapshotFilePath);
   ok(true, "Should have read the heap snapshot");
 
-  const report = yield client.takeCensus(snapshotFilePath, {
+  const { report } = yield client.takeCensus(snapshotFilePath, {
     breakdown: BREAKDOWN
   });
 
-  const treeNode = yield client.takeCensus(snapshotFilePath, {
+  const { report: treeNode } = yield client.takeCensus(snapshotFilePath, {
     breakdown: BREAKDOWN
   }, {
     asTreeNode: true
   });
 
   const markers = treeNode.children.find(c => c.name === "AllocationMarker");
   ok(markers);
 
--- a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_07.js
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_07.js
@@ -31,21 +31,21 @@ const BREAKDOWN = {
 
 add_task(function* () {
   const client = new HeapAnalysesClient();
 
   const snapshotFilePath = saveNewHeapSnapshot();
   yield client.readHeapSnapshot(snapshotFilePath);
   ok(true, "Should have read the heap snapshot");
 
-  const report = yield client.takeCensus(snapshotFilePath, {
+  const { report } = yield client.takeCensus(snapshotFilePath, {
     breakdown: BREAKDOWN
   });
 
-  const treeNode = yield client.takeCensus(snapshotFilePath, {
+  const { report: treeNode } = yield client.takeCensus(snapshotFilePath, {
     breakdown: BREAKDOWN
   }, {
     asInvertedTreeNode: true
   });
 
   compareCensusViewData(BREAKDOWN, report, treeNode, { invert: true });
 
   client.destroy();
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -61,17 +61,19 @@ android {
         automation {
         }
     }
 
     sourceSets {
         main {
             manifest.srcFile "${topobjdir}/mobile/android/base/AndroidManifest.xml"
             assets {
-                if (mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY) {
+                if (mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY && !mozconfig.substs.MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER) {
+                    // If we are packaging the bouncer, it will have the distribution, so don't put
+                    // it in the main APK as well.
                     srcDir "${mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY}/assets"
                 }
             }
         }
 
         androidTest {
             java {
                 srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/src"
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoProfile.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoProfile.java
@@ -611,24 +611,26 @@ public final class GeckoProfile {
      * [1]: https://mxr.mozilla.org/mozilla-central/source/toolkit/modules/ClientID.jsm
      */
     @WorkerThread
     public String getClientId() throws IOException {
         final String clientIdFileContents;
         try {
             clientIdFileContents = readFile(CLIENT_ID_FILE_PATH);
         } catch (final IOException e) {
-            throw new IOException("Could not read client ID file to retrieve client ID", e);
+            // Don't log exception to avoid leaking profile path.
+            throw new IOException("Could not read client ID file to retrieve client ID");
         }
 
         try {
             final org.json.JSONObject json = new org.json.JSONObject(clientIdFileContents);
             return json.getString(CLIENT_ID_JSON_ATTR);
         } catch (final JSONException e) {
-            throw new IOException("Could not parse JSON to retrieve client ID", e);
+            // Don't log exception to avoid leaking profile path.
+            throw new IOException("Could not parse JSON to retrieve client ID");
         }
     }
 
     /**
      * Moves the session file to the backup session file.
      *
      * sessionstore.js should hold the current session, and sessionstore.bak
      * should hold the previous session (where it is used to read the "tabs
--- a/mobile/android/base/java/org/mozilla/gecko/distribution/Distribution.java
+++ b/mobile/android/base/java/org/mozilla/gecko/distribution/Distribution.java
@@ -458,21 +458,23 @@ public class Distribution {
         // We've done the work once; don't do it again.
         if (this.state == STATE_SET) {
             // Note that we don't compute the distribution directory.
             // Call `ensureDistributionDir` if you need it.
             runReadyQueue();
             return true;
         }
 
-        // We try the install intent, then the APK, then the system directory.
+        // We try to find the install intent, then the APK, then the system directory, and finally
+        // an already copied distribution.  Already copied might originate from the bouncer APK.
         final boolean distributionSet =
                 checkIntentDistribution(referrer) ||
                 copyAndCheckAPKDistribution() ||
-                checkSystemDistribution();
+                checkSystemDistribution() ||
+                checkDataDistribution();
 
         // If this is our first run -- and thus we weren't already in STATE_NONE or STATE_SET above --
         // and we didn't find a distribution already, then we should hold on to callbacks in case we
         // get a late distribution.
         this.shouldDelayLateCallbacks = !distributionSet;
         this.state = distributionSet ? STATE_SET : STATE_NONE;
         settings.edit().putInt(keyName, this.state).apply();
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -881,19 +881,23 @@ ANDROID_GENERATED_RESFILES += [
     'res/values/strings.xml',
 ]
 
 ANDROID_ASSETS_DIRS += [
     '/mobile/android/app/assets',
 ]
 
 if CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY']:
-    ANDROID_ASSETS_DIRS += [
-        '%' + CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY'] + '/assets',
-    ]
+    # If you change this, also change its equivalent in mobile/android/bouncer.
+    if not CONFIG['MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER']:
+        # If we are packaging the bouncer, it will have the distribution, so don't put
+        # it in the main APK as well.
+        ANDROID_ASSETS_DIRS += [
+            '%' + CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY'] + '/assets',
+        ]
 
 # We do not expose MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN here because that
 # would leak the value to build logs.  Instead we expose the token quietly where
 # appropriate in Makefile.in.
 for var in ('MOZ_ANDROID_ANR_REPORTER', 'MOZ_LINKER_EXTRACT', 'MOZ_DEBUG',
             'MOZ_ANDROID_SEARCH_ACTIVITY', 'MOZ_NATIVE_DEVICES', 'MOZ_ANDROID_MLS_STUMBLER',
             'MOZ_ANDROID_DOWNLOADS_INTEGRATION', 'MOZ_INSTALL_TRACKING',
             'MOZ_ANDROID_GCM'):
new file mode 100644
--- /dev/null
+++ b/mobile/android/bouncer/AndroidManifest.xml.in
@@ -0,0 +1,96 @@
+#filter substitution
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="@ANDROID_PACKAGE_NAME@"
+      android:installLocation="auto"
+      android:versionCode="@ANDROID_VERSION_CODE@"
+      android:versionName="@MOZ_APP_VERSION@"
+#ifdef MOZ_ANDROID_SHARED_ID
+      android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
+#endif
+      >
+    <uses-sdk android:minSdkVersion="@MOZ_ANDROID_MIN_SDK_VERSION@"
+#ifdef MOZ_ANDROID_MAX_SDK_VERSION
+              android:maxSdkVersion="@MOZ_ANDROID_MAX_SDK_VERSION@"
+#endif
+              android:targetSdkVersion="23"/>
+
+<!-- The bouncer APK and the main APK should define the same set of
+     <permission>, <uses-permission>, and <uses-feature> elements.  This reduces
+     the likelihood of permission-related surprises when installing the main APK
+     on top of a pre-installed bouncer APK.  Add such shared elements in the
+     fileincluded here, so that they can be referenced by both APKs. -->
+#include ../base/FennecManifest_permissions.xml.in
+
+    <application android:label="@MOZ_APP_DISPLAYNAME@"
+                 android:icon="@drawable/icon"
+                 android:logo="@drawable/logo"
+                 android:hardwareAccelerated="true"
+                 android:allowBackup="false"
+# The preprocessor does not yet support arbitrary parentheses, so this cannot
+# be parenthesized thus to clarify that the logical AND operator has precedence:
+#   !defined(MOZILLA_OFFICIAL) || (defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG))
+#if !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) && defined(MOZ_DEBUG)
+                 android:debuggable="true">
+#else
+                 android:debuggable="false">
+#endif
+
+        <activity
+            android:name="@MOZ_ANDROID_BROWSER_INTENT_CLASS@"
+            android:label="@MOZ_APP_DISPLAYNAME@"
+            android:theme="@android:style/Theme.Translucent">
+
+            <!-- Aping org.mozilla.gecko.BrowserApp. -->
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+                <category android:name="android.intent.category.MULTIWINDOW_LAUNCHER"/>
+                <category android:name="android.intent.category.APP_BROWSER" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+
+            <meta-data android:name="com.sec.minimode.icon.portrait.normal"
+                       android:resource="@drawable/icon"/>
+
+            <meta-data android:name="com.sec.minimode.icon.landscape.normal"
+                       android:resource="@drawable/icon" />
+
+            <intent-filter>
+                <action android:name="android.intent.action.WEB_SEARCH" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+            </intent-filter>
+
+            <!-- Aping org.mozilla.gecko.tabqueue.TabQueueDispatcher. -->
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:scheme="about" />
+                <data android:scheme="javascript" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="file" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:mimeType="text/html"/>
+                <data android:mimeType="text/plain"/>
+                <data android:mimeType="application/xhtml+xml"/>
+            </intent-filter>
+        </activity>
+
+        <service
+            android:name="org.mozilla.bouncer.BouncerService"
+            android:exported="false" />
+
+    </application>
+</manifest>
copy from mobile/android/javaaddons/Makefile.in
copy to mobile/android/bouncer/Makefile.in
--- a/mobile/android/javaaddons/Makefile.in
+++ b/mobile/android/bouncer/Makefile.in
@@ -1,9 +1,30 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-include $(topsrcdir)/config/rules.mk
+include $(topsrcdir)/config/config.mk
+
+JAVAFILES := \
+	java/org/mozilla/bouncer/BouncerService.java \
+	java/org/mozilla/gecko/BrowserApp.java \
+  $(NULL)
+
+ANDROID_EXTRA_JARS := \
+  $(NULL)
 
-include $(topsrcdir)/config/android-common.mk
+PP_TARGETS += manifest
+manifest := $(srcdir)/AndroidManifest.xml.in
+manifest_TARGET := export
+# Special 'cuz they are set in mobile/android/defs.mk.
+manifest_FLAGS += \
+  -DMOZ_ANDROID_SHARED_ID="$(MOZ_ANDROID_SHARED_ID)" \
+  -DMOZ_ANDROID_SHARED_ACCOUNT_TYPE="$(MOZ_ANDROID_SHARED_ACCOUNT_TYPE)" \
+  -DMOZ_ANDROID_SHARED_FXACCOUNT_TYPE="$(MOZ_ANDROID_SHARED_FXACCOUNT_TYPE)" \
+  $(NULL)
 
-libs:: javaaddons-1.0.jar
+# Targets built very early during a Gradle build.
+gradle-targets: $(abspath AndroidManifest.xml)
+
+.PHONY: gradle-targets
+
+libs:: $(ANDROID_APK_NAME).apk
copy from mobile/android/app/assets/example_asset.txt
copy to mobile/android/bouncer/assets/example_asset.txt
new file mode 100644
--- /dev/null
+++ b/mobile/android/bouncer/build.gradle
@@ -0,0 +1,76 @@
+buildDir "${topobjdir}/gradle/build/mobile/android/bouncer"
+
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "23.0.1"
+
+    defaultConfig {
+        targetSdkVersion 23
+        minSdkVersion 15 
+        applicationId mozconfig.substs.ANDROID_PACKAGE_NAME
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+ 
+    dexOptions {
+        javaMaxHeapSize "2g"
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+        }
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile "${topobjdir}/mobile/android/bouncer/AndroidManifest.xml"
+            assets {
+                if (mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY) {
+                    srcDir "${mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY}/assets"
+                }
+            }
+            java {
+                srcDir 'java'
+            }
+            res {
+                srcDir "${topsrcdir}/${mozconfig.substs.MOZ_BRANDING_DIRECTORY}/res" // For the icon.
+                srcDir 'res'
+            }
+        }
+    }
+}
+
+task generateCodeAndResources(type:Exec) {
+    workingDir "${topobjdir}"
+
+    commandLine mozconfig.substs.GMAKE
+    args '-C'
+    args "${topobjdir}/mobile/android/bouncer"
+    args 'gradle-targets'
+
+    // Only show the output if something went wrong.
+    ignoreExitValue = true
+    standardOutput = new ByteArrayOutputStream()
+    errorOutput = standardOutput
+    doLast {
+        if (execResult.exitValue != 0) {
+            throw new GradleException("Process '${commandLine}' finished with non-zero exit value ${execResult.exitValue}:\n\n${standardOutput.toString()}")
+        }
+    }
+}
+
+afterEvaluate {
+    android.applicationVariants.all {
+        preBuild.dependsOn generateCodeAndResources
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/bouncer/java/org/mozilla/bouncer/BouncerService.java
@@ -0,0 +1,129 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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.bouncer;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BouncerService extends IntentService {
+
+    private static final String LOGTAG = "GeckoBouncerService";
+
+    public BouncerService() {
+        super("BouncerService");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        final byte[] buffer = new byte[8192];
+
+        Log.d(LOGTAG, "Preparing to copy distribution files");
+
+        final List<String> files;
+        try {
+            files = getFiles("distribution");
+        } catch (IOException e) {
+            Log.e(LOGTAG, "Error getting distribution files from assets/distribution/**", e);
+            return;
+        }
+
+        InputStream in = null;
+        for (String path : files) {
+            try {
+                Log.d(LOGTAG, "Copying distribution file: " + path);
+
+                in = getAssets().open(path);
+
+                final File outFile = getDataFile(path);
+                writeStream(in, outFile, buffer);
+            } catch (IOException e) {
+                Log.e(LOGTAG, "Error opening distribution input stream from assets", e);
+            } finally {
+                if (in != null) {
+                    try {
+                        in.close();
+                    } catch (IOException e) {
+                        Log.e(LOGTAG, "Error closing distribution input stream", e);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Recursively traverse a directory to list paths to all files.
+     *
+     * @param path Directory to traverse.
+     * @return List of all files in given directory.
+     * @throws IOException
+     */
+    private List<String> getFiles(String path) throws IOException {
+        List<String> paths = new ArrayList<>();
+        getFiles(path, paths);
+        return paths;
+    }
+
+    /**
+     * Recursively traverse a directory to list paths to all files.
+     *
+     * @param path Directory to traverse.
+     * @param acc Accumulator of paths seen.
+     * @throws IOException
+     */
+    private void getFiles(String path, List<String> acc) throws IOException {
+        final String[] list = getAssets().list(path);
+        if (list.length > 0) {
+            // We're a directory -- recurse.
+            for (final String file : list) {
+                getFiles(path + "/" + file, acc);
+            }
+        } else {
+            // We're a file -- accumulate.
+            acc.add(path);
+        }
+    }
+
+    private String getDataDir() {
+        return getApplicationInfo().dataDir;
+    }
+
+    private File getDataFile(final String path) {
+        File outFile = new File(getDataDir(), path);
+        File dir = outFile.getParentFile();
+
+        if (dir != null && !dir.exists()) {
+            Log.d(LOGTAG, "Creating " + dir.getAbsolutePath());
+            if (!dir.mkdirs()) {
+                Log.e(LOGTAG, "Unable to create directories: " + dir.getAbsolutePath());
+                return null;
+            }
+        }
+
+        return outFile;
+    }
+
+    private void writeStream(InputStream fileStream, File outFile, byte[] buffer)
+            throws IOException {
+        final OutputStream outStream = new FileOutputStream(outFile);
+        try {
+            int count;
+            while ((count = fileStream.read(buffer)) > 0) {
+                outStream.write(buffer, 0, count);
+            }
+        } finally {
+            outStream.close();
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/bouncer/java/org/mozilla/gecko/BrowserApp.java
@@ -0,0 +1,46 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import org.mozilla.bouncer.BouncerService;
+
+/**
+ * Bouncer activity version of BrowserApp.
+ *
+ * This class has the same name as org.mozilla.gecko.BrowserApp to preserve
+ * shortcuts created by the bouncer app.
+ */
+public class BrowserApp extends Activity {
+    private static final String LOGTAG = "GeckoBouncerActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // This races distribution installation against the Play Store killing our process to
+        // install the update.  We'll live with it.  To do better, consider using an Intent to
+        // notify when the service has completed.
+        startService(new Intent(this, BouncerService.class));
+
+        final String appPackageName = Uri.encode(getPackageName());
+        final Uri uri = Uri.parse("market://details?id=" + appPackageName);
+        Log.i(LOGTAG, "Lanching activity with URL: " + uri.toString());
+
+        // It might be more correct to catch failure in case the Play Store isn't installed.  The
+        // fallback action is to open the Play Store website... but doing so may offer Firefox as
+        // browser (since even the bouncer offers to view URLs), which will be very confusing.
+        // Therefore, we don't try to be fancy here, and we just fail (silently).
+        startActivity(new Intent(Intent.ACTION_VIEW, uri));
+
+        finish();
+    }
+}
copy from mobile/android/javaaddons/moz.build
copy to mobile/android/bouncer/moz.build
--- a/mobile/android/javaaddons/moz.build
+++ b/mobile/android/bouncer/moz.build
@@ -1,11 +1,32 @@
 # -*- 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/.
 
-jar = add_java_jar('javaaddons-1.0')
-jar.sources = [
-    'java/org/mozilla/javaaddons/JavaAddonInterfaceV1.java',
+DEFINES['ANDROID_VERSION_CODE'] = '1'
+
+for var in ('ANDROID_PACKAGE_NAME',
+            'MOZ_ANDROID_BROWSER_INTENT_CLASS',
+            'MOZ_APP_DISPLAYNAME',
+            'MOZ_APP_VERSION'):
+    DEFINES[var] = CONFIG[var]
+
+ANDROID_APK_NAME = 'bouncer'
+ANDROID_APK_PACKAGE = CONFIG['ANDROID_PACKAGE_NAME']
+
+# Putting branding earlier allows branders to override default resources.
+ANDROID_RES_DIRS += [
+    '/' + CONFIG['MOZ_BRANDING_DIRECTORY'] + '/res', # For the icon.
+    'res',
 ]
-jar.javac_flags += ['-Xlint:all']
+
+ANDROID_ASSETS_DIRS += [
+    'assets',
+]
+
+if CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY']:
+    # If you change this, also change its equivalent in mobile/android/base.
+    ANDROID_ASSETS_DIRS += [
+        '%' + CONFIG['MOZ_ANDROID_DISTRIBUTION_DIRECTORY'] + '/assets',
+    ]
copy from mobile/android/base/resources/drawable-v21/logo.xml
copy to mobile/android/bouncer/res/drawable-v21/logo.xml
copy from mobile/android/base/resources/drawable/logo.xml
copy to mobile/android/bouncer/res/drawable/logo.xml
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -88,16 +88,19 @@ MOZ_WEBGL_CONFORMANT=1
 MOZ_ANDROID_SEARCH_ACTIVITY=1
 
 # Enable the Mozilla Location Service stumbler.
 MOZ_ANDROID_MLS_STUMBLER=1
 
 # Enable adding to the system downloads list.
 MOZ_ANDROID_DOWNLOADS_INTEGRATION=1
 
+# Build and package the install bouncer APK by default.
+MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER=1
+
 # Use the low-memory GC tuning.
 export JS_GC_SMALL_CHUNK_SIZE=1
 
 # Enable GCM registration on Nightly builds only.
 if test "$NIGHTLY_BUILD"; then
   MOZ_ANDROID_GCM=1
 fi
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/docs/bouncer.rst
@@ -0,0 +1,38 @@
+.. -*- Mode: rst; fill-column: 100; -*-
+
+=========================================
+ The Firefox for Android install bouncer
+=========================================
+
+`Bug 1234629 <https://bugzilla.mozilla.org/show_bug.cgi?id=1234629>`_ and `Bug 1163082
+<https://bugzilla.mozilla.org/show_bug.cgi?id=1163082>`_ combine to allow building a very small
+Fennec-like "bouncer" APK that redirects (bounces) a potential Fennec user to the marketplace of
+their choice -- usually the Google Play Store -- to install the real Firefox for Android application
+APK.
+
+The real APK should install seamlessly over top of the bouncer APK.  Care is taken to keep the
+bouncer and application APK <permission> manifest definitions identical, and to have the bouncer APK
+<activity> manifest definitions look similar to the application APK <activity> manifest definitions.
+
+In addition, the bouncer APK can carry a Fennec distribution, which it copies onto the device before
+redirecting to the marketplace.  The application APK recognizes the installed distribution and
+customizes itself accordingly on first run.
+
+The motivation is to allow partners to pre-install the very small bouncer APK on shipping devices
+and to have a smooth path to upgrade to the full application APK, with a partner-specific
+distribution in place.
+
+Technical details
+=================
+
+To build the bouncer APK, define ``MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER``.  To pack a distribution
+into the bouncer APK (and *not* into the application APK), add a line like::
+
+  ac_add_options --with-android-distribution-directory=/path/to/fennec-distribution-sample
+
+to your ``mozconfig`` file.  See the `general distribution documentation on the wiki
+<https://wiki.mozilla.org/Mobile/Distribution_Files>`_ for more information.
+
+The ``distribution`` directory should end up in the ``assets/distribution`` directory of the bouncer
+APK.  It will be copied into ``/data/data/$ANDROID_PACKAGE_NAME/distribution`` when the bouncer
+executes.
--- a/mobile/android/docs/defaultdomains.rst
+++ b/mobile/android/docs/defaultdomains.rst
@@ -67,16 +67,17 @@ list of domains:
 * Remove any URL shorteners and redirecters.
 * Remove any content/CDN domains. Some sites use separate domains to store images and other static
   content.
 
 Guidelines for Adult Content
 ============================
 
 Generally the Adult category includes sites whose dominant theme is either:
+
 * To appeal to the prurient interest in sex without any serious literary, artistic, political, or
   scientific value
 * The depiction or description of nudity, including sexual or excretory activities or organs in a
   lascivious way
 * The depiction or description of sexually explicit conduct in a lascivious way (e.g. for
   entertainment purposes)
 
 For a more complete definition and guidelines of adult content, use the full DMOZ guidelines at
deleted file mode 100644
--- a/mobile/android/docs/gradle.rst
+++ /dev/null
@@ -1,97 +0,0 @@
-.. -*- Mode: rst; fill-column: 80; -*-
-
-======================
- Building with Gradle
-======================
-
-Instructions
-============
-
-.. code-block:: shell
-
-  ./mach build && ./mach package
-  ./mach gradle build
-
-The debug APK will be at
-``$OBJDIR/mobile/android/gradle/app/build/outputs/apk/app-debug.apk``.
-
-The ``$OBJDIR/mobile/android/gradle`` directory can be imported into IntelliJ as
-follows:
-
-- File > Import Project...
-- [select ``$OBJDIR/mobile/android/gradle``]
-- Import project from external model > Gradle
-- [select Use customizable Gradle wrapper]
-
-When prompted, do not add any files to git.  You may need to re-open the
-project, or restart IntelliJ, to pick up a compiler language-level change.
-
-Technical overview
-==================
-
-Caveats
--------
-
-* The Gradle build will "succeed" but crash on start up if the object directory
-  has not been properly packaged.
-* Changes to preprocessed source code and resources (namely, ``strings.xml.in``
-  and the accompanying DTD files) may not be recognized.
-* There's minimal support for editing JavaScript.
-* There's no support for editing C/C++.
-
-How the Gradle project is laid out
-----------------------------------
-
-To the greatest extent possible, the Gradle configuration lives in the object
-directory.
-
-At the time of writing, there are three main sub-modules: *app*, *base*, and
-*thirdparty*, and several smaller sub-modules.
-
-*app* is the Fennec wrapper; it generates the **org.mozilla.fennec.R** resource
-package.  *base* is the Gecko code; it generates the **org.mozilla.gecko.R**
-resource package.  Together, *app* and *base* address the "two package
-namespaces" that has plagued Fennec from day one.
-
-Due to limitations in the Android Gradle plugin, all test code is shoved into
-the *app* module.  (The issue is that, at the time of writing, there is no
-support for test-only APKs.)  For no particular reason, the compiled C/C++
-libraries are included in the *app* module; they could be included in the *base*
-module.  I expect *base* to rebuilt slightly more frequently than *app*, so I'm
-hoping this choice will allow for faster incremental builds.
-
-*thirdparty* is the external code we use in Fennec; it's built as an Android
-library but uses no resources.  It's separate simply to allow the build system
-to cache the compiled and pre-dexed artifacts, hopefully allowing for faster
-incremental builds.
-
-Recursive make backend details
-------------------------------
-
-The ``mobile/android/gradle`` directory writes the following into
-``$OBJDIR/mobile/android/gradle``:
-
-1) the Gradle wrapper;
-2) ``gradle.properties``;
-3) symlinks to certain source and resource directories.
-
-The Gradle wrapper is written to make it easy to build with Gradle from the
-object directory.  The wrapper is `intended to be checked into version
-control`_.
-
-``gradle.properties`` is the single source of per-object directory Gradle
-configuration, and provides the Gradle configuration access to
-configure/moz.build variables.
-
-The symlinks are not necessary for the Gradle build itself, but they prevent
-nested directory errors and incorrect Java package scoping when the Gradle
-project is imported into IntelliJ.  Because IntelliJ treats the Gradle project
-as authoritative, it's not sufficient to fix these manually in IntelliJ after
-the initial import -- IntelliJ reverts to the Gradle configuration after every
-build.  Since there aren't many symlinks, I've done them in the Makefile rather
-than at a higher level of abstraction (like a moz.build definition, or a custom
-build backend).  In future, I expect to be able to remove all such symlinks by
-making our in-tree directory structures agree with what Gradle and IntelliJ
-expect.
-
-.. _intended to be checked into version control: http://www.gradle.org/docs/current/userguide/gradle_wrapper.html
--- a/mobile/android/docs/index.rst
+++ b/mobile/android/docs/index.rst
@@ -1,24 +1,25 @@
 .. Firefox for Android documentation master file, created by
    sphinx-quickstart on Fri Dec  4 22:51:57 2015.
    You can adapt this file completely to your liking, but it should at least
    contain the root `toctree` directive.
 
-Welcome to Firefox for Android's documentation!
-===============================================
+Firefox for Android
+===================
 
 Contents:
 
 .. toctree::
    :maxdepth: 2
 
    localeswitching
    uitelemetry
    adjust
    defaultdomains
+   bouncer
 
 Indices and tables
 ==================
 
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
--- a/mobile/android/moz.build
+++ b/mobile/android/moz.build
@@ -21,15 +21,18 @@ DIRS += [
     'components',
     'modules',
     'themes/core',
     'app',
     'fonts',
     'geckoview_library',
 ]
 
+if CONFIG['MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER']:
+    DIRS += ['bouncer'] # No ordering implied with respect to base.
+
 DIRS += ['../../xulrunner/tools/redit']
 
 TEST_DIRS += [
     'tests',
 ]
 
 SPHINX_TREES['fennec'] = 'docs'
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testDistribution.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testDistribution.java
@@ -132,16 +132,18 @@ public class testDistribution extends Co
         String mockPackagePath = getMockPackagePath();
 
         // Wait for any startup-related background distribution shenanigans to
         // finish. This reduces the chance of us racing with startup pref writes.
         waitForBackgroundHappiness();
 
         // Pre-clear distribution pref, run basic preferences and en-US localized preferences Tests
         clearDistributionPref();
+        clearDistributionFromDataData();
+
         setTestLocale("en-US");
         try {
             initDistribution(mockPackagePath);
         } catch(NoSuchElementException e) {
             // TODO: determine why this exception is intermittently thrown
             Log.w(LOGTAG, "NoSuchElementException on first initDistribution -- will retry");
             mSolo.sleep(4000);
             initDistribution(mockPackagePath);
@@ -149,26 +151,29 @@ public class testDistribution extends Co
         checkPreferences();
         checkAndroidPreferences();
         checkLocalizedPreferences("en-US");
         checkSearchPlugin();
         checkAddon();
 
         // Pre-clear distribution pref, and run es-MX localized preferences Test
         clearDistributionPref();
+        clearDistributionFromDataData();
         setTestLocale("es-MX");
         initDistribution(mockPackagePath);
         checkLocalizedPreferences("es-MX");
 
         // Test the (stubbed) download interaction.
         setTestLocale("en-US");
         clearDistributionPref();
+        clearDistributionFromDataData();
         doTestValidReferrerIntent();
 
         clearDistributionPref();
+        clearDistributionFromDataData();
         doTestInvalidReferrerIntent();
     }
 
     private void setOSLocale(Locale locale) {
         Locale.setDefault(locale);
         BrowserLocaleManager.storeAndNotifyOSLocale(GeckoSharedPrefs.forProfile(mActivity), locale);
     }
 
@@ -498,16 +503,32 @@ public class testDistribution extends Co
     private void clearDistributionPref() {
         mAsserter.dumpLog("Clearing distribution pref.");
         SharedPreferences settings = mActivity.getSharedPreferences("GeckoApp", Activity.MODE_PRIVATE);
         String keyName = mActivity.getPackageName() + ".distribution_state";
         settings.edit().remove(keyName).commit();
         TestableDistribution.clearReferrerDescriptorForTesting();
     }
 
+    /**
+     * Clears any distribution found in /data/data.
+     */
+    private void clearDistributionFromDataData() throws Exception {
+        File dataDir = new File(mActivity.getApplicationInfo().dataDir);
+
+        // Recursively delete distribution files that Distribution.init copied to data directory.
+        File distDir = new File(dataDir, "distribution");
+        if (distDir.exists()) {
+            mAsserter.dumpLog("Clearing distribution from " + distDir.getAbsolutePath());
+            delete(distDir);
+        } else {
+            mAsserter.dumpLog("No distribution to clear from " + distDir.getAbsolutePath());
+        }
+    }
+
     @Override
     public void setUp() throws Exception {
         // TODO: Set up the content provider after setting the distribution.
         super.setUp(sBrowserProviderCallable, BrowserContract.AUTHORITY, "browser.db");
     }
 
     private void delete(File file) throws Exception {
       if (file.isDirectory()) {
@@ -522,17 +543,14 @@ public class testDistribution extends Co
     @Override
     public void tearDown() throws Exception {
         File dataDir = new File(mActivity.getApplicationInfo().dataDir);
 
         // Delete mock package from data directory.
         File mockPackage = new File(dataDir, MOCK_PACKAGE);
         mAsserter.ok(mockPackage.delete(), "clean up mock package", "deleted " + mockPackage.getPath());
 
-        // Recursively delete distribution files that Distribution.init copied to data directory.
-        File distDir = new File(dataDir, "distribution");
-        delete(distDir);
-
+        clearDistributionFromDataData();
         clearDistributionPref();
 
         super.tearDown();
     }
 }
--- a/python/mozboot/mozboot/fedora.py
+++ b/python/mozboot/mozboot/fedora.py
@@ -16,16 +16,17 @@ class FedoraBootstrapper(BaseBootstrappe
 
         self.group_packages = [
             'C Development Tools and Libraries',
         ]
 
         self.packages = [
             'autoconf213',
             'mercurial',
+            'python2-devel',
         ]
 
         self.browser_group_packages = [
             'GNOME Software Development',
         ]
 
         self.browser_packages = [
             'alsa-lib-devel',
--- a/python/mozboot/mozboot/gentoo.py
+++ b/python/mozboot/mozboot/gentoo.py
@@ -8,17 +8,17 @@ from mozboot.base import BaseBootstrappe
 class GentooBootstrapper(BaseBootstrapper):
     def __init__(self, version, dist_id, **kwargs):
         BaseBootstrapper.__init__(self, **kwargs)
 
         self.version = version
         self.dist_id = dist_id
 
     def install_system_packages(self):
-        self.run_as_root(['emerge', '--quiet', 'git', 'mercurial'])
+        self.run_as_root(['emerge', '--quiet', 'dev-vcs/git', 'mercurial'])
 
     def install_browser_packages(self):
         self.run_as_root(['emerge', '--onlydeps', '--quiet', 'firefox'])
         self.run_as_root(['emerge', '--quiet', 'gtk+'])
 
     def _update_package_manager(self):
         self.run_as_root(['emerge', '--sync'])
 
--- a/settings.gradle
+++ b/settings.gradle
@@ -33,16 +33,21 @@ include ':base'
 include ':omnijar'
 include ':thirdparty'
 
 project(':app').projectDir = new File("${json.topsrcdir}/mobile/android/app")
 project(':base').projectDir = new File("${json.topsrcdir}/mobile/android/app/base")
 project(':omnijar').projectDir = new File("${json.topsrcdir}/mobile/android/app/omnijar")
 project(':thirdparty').projectDir = new File("${json.topsrcdir}/mobile/android/thirdparty")
 
+if (json.substs.MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER) {
+    include ':bouncer'
+    project(':bouncer').projectDir = new File("${json.topsrcdir}/mobile/android/bouncer")
+}
+
 // The Gradle instance is shared between settings.gradle and all the
 // other build.gradle files (see
 // http://forums.gradle.org/gradle/topics/define_extension_properties_from_settings_xml).
 // We use this ext property to pass the per-object-directory mozconfig
 // between scripts.  This lets us execute set-up code before we gradle
 // tries to configure the project even once, and as a side benefit
 // saves invoking |mach environment| multiple times.
 gradle.ext.mozconfig = json
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -207,40 +207,51 @@ class BaseContext {
    * callback funciton must check `browser.runtime.lastError` or
    * `extension.runtime.lastError` in order to prevent it being reported
    * to the console.
    *
    * @param {Promise} promise The promise with which to wrap the
    *     callback. May resolve to a `SpreadArgs` instance, in which case
    *     each element will be used as a separate argument.
    *
+   *     Unless the promise object belongs to the cloneScope global, its
+   *     resolution value is cloned into cloneScope prior to calling the
+   *     `callback` function or resolving the wrapped promise.
+   *
    * @param {function} [callback] The callback function to wrap
    *
    * @returns {Promise|undefined} If callback is null, a promise object
    *     belonging to the target scope. Otherwise, undefined.
    */
   wrapPromise(promise, callback = null) {
+    // Note: `promise instanceof this.cloneScope.Promise` returns true
+    // here even for promises that do not belong to the content scope.
+    let runSafe = runSafeSync.bind(null, this);
+    if (promise.constructor === this.cloneScope.Promise) {
+      runSafe = runSafeSyncWithoutClone;
+    }
+
     if (callback) {
       promise.then(
         args => {
           if (args instanceof SpreadArgs) {
-            runSafeSync(this, callback, ...args);
+            runSafe(callback, ...args);
           } else {
-            runSafeSync(this, callback, args);
+            runSafe(callback, args);
           }
         },
         error => {
           this.withLastError(error, () => {
-            runSafeSync(this, callback);
+            runSafeSyncWithoutClone(callback);
           });
         });
     } else {
       return new this.cloneScope.Promise((resolve, reject) => {
         promise.then(
-          value => { runSafeSync(this, resolve, value); },
+          value => { runSafe(resolve, value); },
           value => {
             if (!(value instanceof this.cloneScope.Error)) {
               value = new this.cloneScope.Error(value.message);
             }
             runSafeSyncWithoutClone(reject, value);
           });
       });
     }
--- a/toolkit/components/extensions/ext-alarms.js
+++ b/toolkit/components/extensions/ext-alarms.js
@@ -1,16 +1,15 @@
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
-  runSafe,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> Set[Alarm]]
 var alarmsMap = new WeakMap();
 
 // WeakMap[Extension -> Set[callback]]
 var alarmCallbacksMap = new WeakMap();
 
@@ -109,28 +108,32 @@ extensions.registerPrivilegedAPI("alarms
       get: function(args) {
         let name = "", callback;
         if (args.length == 1) {
           callback = args[0];
         } else {
           [name, callback] = args;
         }
 
-        for (let alarm of alarmsMap.get(extension)) {
-          if (alarm.name == name) {
-            runSafe(context, callback, alarm.data);
-            break;
+        let promise = new Promise((resolve, reject) => {
+          for (let alarm of alarmsMap.get(extension)) {
+            if (alarm.name == name) {
+              return resolve(alarm.data);
+            }
           }
-        }
+          reject("No matching alarm");
+        });
+
+        return context.wrapPromise(promise, callback);
       },
 
       getAll: function(callback) {
         let alarms = alarmsMap.get(extension);
         let result = alarms.map(alarm => alarm.data);
-        runSafe(context, callback, result);
+        return context.wrapPromise(Promise.resolve(result), callback);
       },
 
       clear: function(...args) {
         let name = "", callback;
         if (args.length == 1) {
           callback = args[0];
         } else {
           [name, callback] = args;
@@ -141,31 +144,27 @@ extensions.registerPrivilegedAPI("alarms
         for (let alarm of alarms) {
           if (alarm.name == name) {
             alarm.clear();
             cleared = true;
             break;
           }
         }
 
-        if (callback) {
-          runSafe(context, callback, cleared);
-        }
+        return context.wrapPromise(Promise.resolve(cleared), callback);
       },
 
       clearAll: function(callback) {
         let alarms = alarmsMap.get(extension);
         let cleared = false;
         for (let alarm of alarms) {
           alarm.clear();
           cleared = true;
         }
-        if (callback) {
-          runSafe(context, callback, cleared);
-        }
+        return context.wrapPromise(Promise.resolve(cleared), callback);
       },
 
       onAlarm: new EventManager(context, "alarms.onAlarm", fire => {
         let callback = alarm => {
           fire(alarm.data);
         };
 
         alarmCallbacksMap.get(extension).add(callback);
--- a/toolkit/components/extensions/ext-backgroundPage.js
+++ b/toolkit/components/extensions/ext-backgroundPage.js
@@ -128,14 +128,14 @@ extensions.registerSchemaAPI("extension"
   return {
     extension: {
       getBackgroundPage: function() {
         return backgroundPagesMap.get(extension).contentWindow;
       },
     },
 
     runtime: {
-      getBackgroundPage: function(callback) {
-        runSafe(context, callback, backgroundPagesMap.get(extension).contentWindow);
+      getBackgroundPage() {
+        return context.cloneScope.Promise.resolve(backgroundPagesMap.get(extension).contentWindow);
       },
     },
   };
 });
--- a/toolkit/components/extensions/ext-cookies.js
+++ b/toolkit/components/extensions/ext-cookies.js
@@ -2,17 +2,16 @@
 
 const { interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 
 var {
   EventManager,
-  runSafe,
 } = ExtensionUtils;
 
 // Cookies from private tabs currently can't be enumerated.
 var DEFAULT_STORE = "firefox-default";
 
 function convert(cookie) {
   let result = {
     name: cookie.name,
@@ -237,35 +236,34 @@ function* query(detailsIn, props, extens
       yield cookie;
     }
   }
 }
 
 extensions.registerSchemaAPI("cookies", "cookies", (extension, context) => {
   let self = {
     cookies: {
-      get: function(details, callback) {
+      get: function(details) {
         // FIXME: We don't sort by length of path and creation time.
         for (let cookie of query(details, ["url", "name", "storeId"], extension)) {
-          runSafe(context, callback, convert(cookie));
-          return;
+          return Promise.resolve(convert(cookie));
         }
 
         // Found no match.
-        runSafe(context, callback, null);
+        return Promise.resolve(null);
       },
 
-      getAll: function(details, callback) {
+      getAll: function(details) {
         let allowed = ["url", "name", "domain", "path", "secure", "session", "storeId"];
         let result = Array.from(query(details, allowed, extension), convert);
 
-        runSafe(context, callback, result);
+        return Promise.resolve(result);
       },
 
-      set: function(details, callback) {
+      set: function(details) {
         let uri = NetUtil.newURI(details.url).QueryInterface(Ci.nsIURL);
 
         let path;
         if (details.path !== null) {
           path = details.path;
         } else {
           // This interface essentially emulates the behavior of the
           // Set-Cookie header. In the case of an omitted path, the cookie
@@ -278,52 +276,45 @@ extensions.registerSchemaAPI("cookies", 
         let value = details.value !== null ? details.value : "";
         let secure = details.secure !== null ? details.secure : false;
         let httpOnly = details.httpOnly !== null ? details.httpOnly : false;
         let isSession = details.expirationDate === null;
         let expiry = isSession ? 0 : details.expirationDate;
         // Ignore storeID.
 
         let cookieAttrs = { host: details.domain, path: path, isSecure: secure };
-        if (checkSetCookiePermissions(extension, uri, cookieAttrs)) {
-          // TODO: Set |lastError| when false.
-          //
-          // The permission check may have modified the domain, so use
-          // the new value instead.
-          Services.cookies.add(cookieAttrs.host, path, name, value,
-                               secure, httpOnly, isSession, expiry);
+        if (!checkSetCookiePermissions(extension, uri, cookieAttrs)) {
+          return Promise.reject({message: `Permission denied to set cookie ${JSON.stringify(details)}`});
         }
 
-        if (callback) {
-          self.cookies.get(details, callback);
-        }
+        // The permission check may have modified the domain, so use
+        // the new value instead.
+        Services.cookies.add(cookieAttrs.host, path, name, value,
+                             secure, httpOnly, isSession, expiry);
+
+        return self.cookies.get(details);
       },
 
-      remove: function(details, callback) {
+      remove: function(details) {
         for (let cookie of query(details, ["url", "name", "storeId"], extension)) {
           Services.cookies.remove(cookie.host, cookie.name, cookie.path, false);
-          if (callback) {
-            runSafe(context, callback, {
-              url: details.url,
-              name: details.name,
-              storeId: DEFAULT_STORE,
-            });
-          }
           // Todo: could there be multiple per subdomain?
-          return;
+          return Promise.resolve({
+            url: details.url,
+            name: details.name,
+            storeId: DEFAULT_STORE,
+          });
         }
 
-        if (callback) {
-          runSafe(context, callback, null);
-        }
+        return Promise.resolve(null);
       },
 
-      getAllCookieStores: function(callback) {
+      getAllCookieStores: function() {
         // Todo: list all the tabIds for non-private tabs
-        runSafe(context, callback, [{id: DEFAULT_STORE, tabIds: []}]);
+        return Promise.resolve([{id: DEFAULT_STORE, tabIds: []}]);
       },
 
       onChanged: new EventManager(context, "cookies.onChanged", fire => {
         let observer = (subject, topic, data) => {
           let notify = (removed, cookie, cause) => {
             cookie.QueryInterface(Ci.nsICookie2);
 
             if (extension.whiteListedHosts.matchesCookie(cookie)) {
--- a/toolkit/components/extensions/ext-idle.js
+++ b/toolkit/components/extensions/ext-idle.js
@@ -1,11 +1,11 @@
 "use strict";
 
 extensions.registerSchemaAPI("idle", "idle", (extension, context) => {
   return {
     idle: {
-      queryState: function(detectionIntervalInSeconds, callback) {
-        runSafe(context, callback, "active");
+      queryState: function(detectionIntervalInSeconds) {
+        return Promise.resolve("active");
       },
     },
   };
 });
--- a/toolkit/components/extensions/ext-notifications.js
+++ b/toolkit/components/extensions/ext-notifications.js
@@ -1,17 +1,16 @@
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
   ignoreEvent,
-  runSafe,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> Set[Notification]]
 var notificationsMap = new WeakMap();
 
 // WeakMap[Extension -> Set[callback]]
 var notificationCallbacksMap = new WeakMap();
 
@@ -51,17 +50,17 @@ Notification.prototype = {
     notificationsMap.get(this.extension).delete(this);
   },
 
   observe(subject, topic, data) {
     if (topic != "alertfinished") {
       return;
     }
 
-    for (let callback in notificationCallbacksMap.get(this.extension)) {
+    for (let callback of notificationCallbacksMap.get(this.extension)) {
       callback(this);
     }
 
     notificationsMap.get(this.extension).delete(this);
   },
 };
 
 /* eslint-disable mozilla/balanced-listeners */
@@ -96,41 +95,37 @@ extensions.registerPrivilegedAPI("notifi
           notificationId = nextId++;
         }
 
         // FIXME: Lots of options still aren't supported, especially
         // buttons.
         let notification = new Notification(extension, notificationId, options);
         notificationsMap.get(extension).add(notification);
 
-        if (callback) {
-          runSafe(context, callback, notificationId);
-        }
+        return context.wrapPromise(Promise.resolve(notificationId), callback);
       },
 
       clear: function(notificationId, callback) {
         let notifications = notificationsMap.get(extension);
         let cleared = false;
         for (let notification of notifications) {
           if (notification.id == notificationId) {
             notification.clear();
             cleared = true;
             break;
           }
         }
 
-        if (callback) {
-          runSafe(context, callback, cleared);
-        }
+        return context.wrapPromise(Promise.resolve(cleared), callback);
       },
 
       getAll: function(callback) {
         let notifications = notificationsMap.get(extension);
         notifications = Array.from(notifications, notification => notification.id);
-        runSafe(context, callback, notifications);
+        return context.wrapPromise(Promise.resolve(notifications), callback);
       },
 
       onClosed: new EventManager(context, "notifications.onClosed", fire => {
         let listener = notification => {
           // FIXME: Support the byUser argument.
           fire(notification.id, true);
         };
 
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -5,17 +5,16 @@ var { classes: Cc, interfaces: Ci, utils
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
   ignoreEvent,
-  runSafe,
 } = ExtensionUtils;
 
 extensions.registerSchemaAPI("runtime", null, (extension, context) => {
   return {
     runtime: {
       onStartup: new EventManager(context, "runtime.onStartup", fire => {
         extension.onStartup = fire;
         return () => {
@@ -64,28 +63,28 @@ extensions.registerSchemaAPI("runtime", 
       },
 
       id: extension.id,
 
       getURL: function(url) {
         return extension.baseURI.resolve(url);
       },
 
-      getPlatformInfo: function(callback) {
+      getPlatformInfo: function() {
         let os = AppConstants.platform;
         if (os == "macosx") {
           os = "mac";
         }
 
         let abi = Services.appinfo.XPCOMABI;
         let [arch] = abi.split("-");
         if (arch == "x86") {
           arch = "x86-32";
         } else if (arch == "x86_64") {
           arch = "x86-64";
         }
 
         let info = {os, arch};
-        runSafe(context, callback, info);
+        return Promise.resolve(info);
       },
     },
   };
 });
--- a/toolkit/components/extensions/ext-storage.js
+++ b/toolkit/components/extensions/ext-storage.js
@@ -3,48 +3,37 @@
 var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
                                   "resource://gre/modules/ExtensionStorage.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
-  runSafe,
 } = ExtensionUtils;
 
 extensions.registerPrivilegedAPI("storage", (extension, context) => {
   return {
     storage: {
       local: {
         get: function(keys, callback) {
-          ExtensionStorage.get(extension.id, keys).then(result => {
-            runSafe(context, callback, result);
-          });
+          return context.wrapPromise(
+            ExtensionStorage.get(extension.id, keys), callback);
         },
         set: function(items, callback) {
-          ExtensionStorage.set(extension.id, items).then(() => {
-            if (callback) {
-              runSafe(context, callback);
-            }
-          });
+          return context.wrapPromise(
+            ExtensionStorage.set(extension.id, items), callback);
         },
         remove: function(items, callback) {
-          ExtensionStorage.remove(extension.id, items).then(() => {
-            if (callback) {
-              runSafe(context, callback);
-            }
-          });
+          return context.wrapPromise(
+            ExtensionStorage.remove(extension.id, items), callback);
         },
         clear: function(callback) {
-          ExtensionStorage.clear(extension.id).then(() => {
-            if (callback) {
-              runSafe(context, callback);
-            }
-          });
+          return context.wrapPromise(
+            ExtensionStorage.clear(extension.id), callback);
         },
       },
 
       onChanged: new EventManager(context, "storage.local.onChanged", fire => {
         let listener = changes => {
           fire(changes, "local");
         };
 
--- a/toolkit/components/extensions/schemas/cookies.json
+++ b/toolkit/components/extensions/schemas/cookies.json
@@ -54,16 +54,17 @@
         "description": "The underlying reason behind the cookie's change. If a cookie was inserted, or removed via an explicit call to $(ref:cookies.remove), \"cause\" will be \"explicit\". If a cookie was automatically removed due to expiry, \"cause\" will be \"expired\". If a cookie was removed due to being overwritten with an already-expired expiration date, \"cause\" will be set to \"expired_overwrite\".  If a cookie was automatically removed due to garbage collection, \"cause\" will be \"evicted\".  If a cookie was automatically removed due to a \"set\" call that overwrote it, \"cause\" will be \"overwrite\". Plan your response accordingly."
       }
     ],
     "functions": [
       {
         "name": "get",
         "type": "function",
         "description": "Retrieves information about a single cookie. If more than one cookie of the same name exists for the given URL, the one with the longest path will be returned. For cookies with the same path length, the cookie with the earliest creation time will be returned.",
+        "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "details",
             "description": "Details to identify the cookie being retrieved.",
             "properties": {
               "url": {"type": "string", "description": "The URL with which the cookie to retrieve is associated. This argument may be a full URL, in which case any data following the URL path (e.g. the query string) is simply ignored. If host permissions for this URL are not specified in the manifest file, the API call will fail."},
               "name": {"type": "string", "description": "The name of the cookie to retrieve."},
@@ -80,16 +81,17 @@
             ]
           }
         ]
       },
       {
         "name": "getAll",
         "type": "function",
         "description": "Retrieves all cookies from a single cookie store that match the given information.  The cookies returned will be sorted, with those with the longest path first.  If multiple cookies have the same path length, those with the earliest creation time will be first.",
+        "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "details",
             "description": "Information to filter the cookies being retrieved.",
             "properties": {
               "url": {"type": "string", "optional": true, "description": "Restricts the retrieved cookies to those that would match the given URL."},
               "name": {"type": "string", "optional": true, "description": "Filters the cookies by name."},
@@ -110,16 +112,17 @@
             ]
           }
         ]
       },
       {
         "name": "set",
         "type": "function",
         "description": "Sets a cookie with the given cookie data; may overwrite equivalent cookies if they exist.",
+        "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "details",
             "description": "Details about the cookie being set.",
             "properties": {
               "url": {"type": "string", "description": "The request-URI to associate with the setting of the cookie. This value can affect the default domain and path values of the created cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail."},
               "name": {"type": "string", "optional": true, "description": "The name of the cookie. Empty by default if omitted."},
@@ -143,16 +146,17 @@
             ]
           }
         ]
       },
       {
         "name": "remove",
         "type": "function",
         "description": "Deletes a cookie by name.",
+        "async": "callback",
         "parameters": [
           {
             "type": "object",
             "name": "details",
             "description": "Information to identify the cookie to remove.",
             "properties": {
               "url": {"type": "string", "description": "The URL associated with the cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail."},
               "name": {"type": "string", "description": "The name of the cookie to remove."},
@@ -178,16 +182,17 @@
             ]
           }
         ]
       },
       {
         "name": "getAllCookieStores",
         "type": "function",
         "description": "Lists all existing cookie stores.",
+        "async": "callback",
         "parameters": [
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
                 "name": "cookieStores", "type": "array", "items": {"$ref": "CookieStore"}, "description": "All the existing cookie stores."
               }
--- a/toolkit/components/extensions/schemas/idle.json
+++ b/toolkit/components/extensions/schemas/idle.json
@@ -13,16 +13,17 @@
         "enum": ["active", "idle", "locked"]
       }
     ],
     "functions": [
       {
         "name": "queryState",
         "type": "function",
         "description": "Returns \"locked\" if the system is locked, \"idle\" if the user has not generated any input for a specified number of seconds, or \"active\" otherwise.",
+        "async": "callback",
         "parameters": [
           {
             "name": "detectionIntervalInSeconds",
             "type": "integer",
             "minimum": 15,
             "description": "The system is considered idle if detectionIntervalInSeconds seconds have elapsed since the last user input detected."
           },
           {
--- a/toolkit/components/extensions/schemas/manifest.json
+++ b/toolkit/components/extensions/schemas/manifest.json
@@ -94,25 +94,34 @@
           "content_scripts": {
             "type": "array",
             "optional": true,
             "items": { "$ref": "ContentScript" }
           },
 
           "permissions": {
             "type": "array",
-            "items": { "$ref": "Permission" },
+            "items": {
+              "choices": [
+                { "$ref": "Permission" },
+                { "type": "string" }
+              ]
+            },
             "optional": true
           },
 
           "web_accessible_resources": {
             "type": "array",
             "items": { "type": "string" },
             "optional": true
           }
+        },
+
+        "additionalProperties": {
+          "type": "any"
         }
       },
       {
         "id": "Permission",
         "choices": [
           {
             "type": "string",
             "enum": [
--- a/toolkit/components/extensions/schemas/runtime.json
+++ b/toolkit/components/extensions/schemas/runtime.json
@@ -109,16 +109,17 @@
         "description": "The ID of the extension/app."
       }
     },
     "functions": [
       {
         "name": "getBackgroundPage",
         "type": "function",
         "description": "Retrieves the JavaScript 'window' object for the background page running inside the current extension/app. If the background page is an event page, the system will ensure it is loaded before calling the callback. If there is no background page, an error is set.",
+        "async": "callback",
         "parameters": [
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
                 "name": "backgroundPage",
                 "optional": true,
@@ -131,16 +132,17 @@
           }
         ]
       },
       {
         "name": "openOptionsPage",
         "unsupported": true,
         "type": "function",
         "description": "<p>Open your Extension's options page, if possible.</p><p>The precise behavior may depend on your manifest's <code>$(topic:optionsV2)[options_ui]</code> or <code>$(topic:options)[options_page]</code> key, or what the browser happens to support at the time.</p><p>If your Extension does not declare an options page, or the browser failed to create one for some other reason, the callback will set $(ref:lastError).</p>",
+        "async": "callback",
         "parameters": [{
           "type": "function",
           "name": "callback",
           "parameters": [],
           "optional": true
         }]
       },
       {
@@ -171,16 +173,17 @@
           "description": "The fully-qualified URL to the resource."
         }
       },
       {
         "name": "setUninstallURL",
         "unsupported": true,
         "type": "function",
         "description": "Sets the URL to be visited upon uninstallation. This may be used to clean up server-side data, do analytics, and implement surveys. Maximum 255 characters.",
+        "async": "callback",
         "parameters": [
           {
             "type": "string",
             "name": "url",
             "maxLength": 255,
             "description": "URL to be opened after the extension is uninstalled. This URL must have an http: or https: scheme. Set an empty string to not open a new tab upon uninstallation."
           },
           {
@@ -199,16 +202,17 @@
         "type": "function",
         "parameters": []
       },
       {
         "name": "requestUpdateCheck",
         "unsupported": true,
         "type": "function",
         "description": "Requests an update check for this app/extension.",
+        "async": "callback",
         "parameters": [
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
                 "name": "status",
                 "$ref": "RequestUpdateCheckStatus",
@@ -337,16 +341,17 @@
             ]
           }
         ]
       },
       {
         "name": "getPlatformInfo",
         "type": "function",
         "description": "Returns information about the current platform.",
+        "async": "callback",
         "parameters": [
           {
             "type": "function",
             "name": "callback",
             "description": "Called with results",
             "parameters": [
               {
                 "name": "platformInfo",
@@ -356,16 +361,17 @@
           }
         ]
       },
       {
         "name": "getPackageDirectoryEntry",
         "unsupported": true,
         "type": "function",
         "description": "Returns a DirectoryEntry for the package directory.",
+        "async": "callback",
         "parameters": [
           {
             "type": "function",
             "name": "callback",
             "parameters": [
               {
                 "name": "directoryEntry",
                 "type": "object",
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest.ini
@@ -28,16 +28,17 @@ support-files =
 [test_ext_geturl.html]
 [test_ext_contentscript.html]
 skip-if = buildapp == 'b2g' # runat != document_idle is not supported.
 [test_ext_contentscript_create_iframe.html]
 [test_ext_contentscript_api_injection.html]
 [test_ext_downloads.html]
 [test_ext_i18n_css.html]
 [test_ext_generate.html]
+[test_ext_idle.html]
 [test_ext_localStorage.html]
 [test_ext_onmessage_removelistener.html]
 [test_ext_notifications.html]
 [test_ext_permission_xhr.html]
 skip-if = buildapp == 'b2g' # JavaScript error: jar:remoteopenfile:///data/local/tmp/generated-extension.xpi!/content.js, line 46: NS_ERROR_ILLEGAL_VALUE:
 [test_ext_runtime_connect.html]
 skip-if = buildapp == 'b2g' # port.sender.tab is undefined on b2g.
 [test_ext_runtime_connect2.html]
--- a/toolkit/components/extensions/test/mochitest/test_ext_bookmarks.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_bookmarks.html
@@ -9,130 +9,92 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 function backgroundScript() {
-  function get(idOrIdList) {
-    return new Promise(resolve => {
-      browser.bookmarks.get(idOrIdList, resolve);
-    });
-  }
-
-  function create(bookmark) {
-    return new Promise(resolve => {
-      browser.bookmarks.create(bookmark, resolve);
-    });
-  }
-
-  function getChildren(id) {
-    return new Promise(resolve => {
-      browser.bookmarks.getChildren(id, resolve);
-    });
-  }
-
-  function update(id, changes) {
-    return new Promise(resolve => {
-      browser.bookmarks.update(id, changes, resolve);
-    });
-  }
-
-  function getTree(id) {
-    return new Promise(resolve => {
-      browser.bookmarks.getTree(resolve);
-    });
-  }
-
-  function remove(idOrIdList) {
-    return new Promise(resolve => {
-      browser.bookmarks.remove(idOrIdList, resolve);
-    });
-  }
-
   let unsortedId, ourId;
 
   function checkOurBookmark(bookmark) {
-    browser.test.assertEq(bookmark.id, ourId);
+    browser.test.assertEq(ourId, bookmark.id);
     browser.test.assertTrue("parentId" in bookmark);
-    browser.test.assertEq(bookmark.index, 0); // We assume there are no other bookmarks.
-    browser.test.assertEq(bookmark.url, "http://example.org/");
-    browser.test.assertEq(bookmark.title, "test bookmark");
+    browser.test.assertEq(0, bookmark.index); // We assume there are no other bookmarks.
+    browser.test.assertEq("http://example.org/", bookmark.url);
+    browser.test.assertEq("test bookmark", bookmark.title);
     browser.test.assertTrue("dateAdded" in bookmark);
     browser.test.assertFalse("dateGroupModified" in bookmark);
     browser.test.assertFalse("unmodifiable" in bookmark);
   }
 
-  get(["not-a-bookmark-guid"]).then(result => {
-    // TODO: check lastError
-    browser.test.assertEq(result.length, 0, "invalid bookmark guid returned nothing");
-    return get(["000000000000"]);
+  let failures = 0;
+  let tallyFailure = error => {
+    browser.test.succeed(`Got expected error: ${error}`);
+    failures++;
+  };
+
+  browser.bookmarks.get(["not-a-bookmark-guid"]).catch(tallyFailure).then(result => {
+    return browser.bookmarks.get(["000000000000"]).catch(tallyFailure);
   }).then(results => {
-    // TODO: check lastError
-    browser.test.assertEq(results.length, 0, "correctly did not find bookmark");
-    return create({title: "test bookmark", url: "http://example.org"});
+    return browser.bookmarks.create({title: "test bookmark", url: "http://example.org"});
   }).then(result => {
     ourId = result.id;
     checkOurBookmark(result);
 
-    return get(ourId);
+    return browser.bookmarks.get(ourId);
   }).then(results => {
     browser.test.assertEq(results.length, 1);
     checkOurBookmark(results[0]);
 
     unsortedId = results[0].parentId;
-    return get(unsortedId);
+    return browser.bookmarks.get(unsortedId);
   }).then(results => {
     let folder = results[0];
     browser.test.assertEq(results.length, 1);
 
-    browser.test.assertEq(folder.id, unsortedId);
+    browser.test.assertEq(unsortedId, folder.id);
     browser.test.assertTrue("parentId" in folder);
     browser.test.assertTrue("index" in folder);
     browser.test.assertFalse("url" in folder);
-    browser.test.assertEq(folder.title, "Unsorted Bookmarks");
+    browser.test.assertEq("Unsorted Bookmarks", folder.title);
     browser.test.assertTrue("dateAdded" in folder);
     browser.test.assertTrue("dateGroupModified" in folder);
     browser.test.assertFalse("unmodifiable" in folder); // TODO: Do we want to enable this?
 
-    return getChildren(unsortedId);
+    return browser.bookmarks.getChildren(unsortedId);
   }).then(results => {
-    browser.test.assertEq(results.length, 1);
+    browser.test.assertEq(1, results.length);
     checkOurBookmark(results[0]);
 
-    return update(ourId, {title: "new test title"});
-  }).then(result => {
-    browser.test.assertEq(result.title, "new test title");
-    browser.test.assertEq(result.id, ourId);
-
-    return getTree();
-  }).then(results => {
-    browser.test.assertEq(results.length, 1);
-    let bookmark = results[0].children.find(bookmark => bookmark.id == unsortedId);
-    browser.test.assertEq(bookmark.title, "Unsorted Bookmarks");
-
-    return create({parentId: "invalid"});
+    return browser.bookmarks.update(ourId, {title: "new test title"});
   }).then(result => {
-    // TODO: Check lastError
-    browser.test.assertEq(result, null);
+    browser.test.assertEq("new test title", result.title);
+    browser.test.assertEq(ourId, result.id);
 
-    return remove(ourId);
-  }).then(() => {
-    return get(ourId);
+    return browser.bookmarks.getTree();
   }).then(results => {
-    // TODO: Check lastError
-    browser.test.assertEq(results.length, 0);
+    browser.test.assertEq(1, results.length);
+    let bookmark = results[0].children.find(bookmark => bookmark.id == unsortedId);
+    browser.test.assertEq("Unsorted Bookmarks", bookmark.title);
 
-    return remove("000000000000");
+    return browser.bookmarks.create({parentId: "invalid"}).catch(tallyFailure);
+  }).then(result => {
+    return browser.bookmarks.remove(ourId);
   }).then(() => {
-    // TODO: Check lastError
+    return browser.bookmarks.get(ourId).catch(tallyFailure);
+  }).then(results => {
+    return browser.bookmarks.remove("000000000000").catch(tallyFailure);
   }).then(() => {
+    browser.test.assertEq(5, failures, "Expected failures");
+
     browser.test.notifyPass("bookmarks");
+  }).catch(error => {
+    browser.test.fail(`Error: ${String(error)} :: ${error.stack}`);
   });
 }
 
 let extensionData = {
   background: "(" + backgroundScript.toString() + ")()",
   manifest: {
     permissions: ["bookmarks"],
   },
--- a/toolkit/components/extensions/test/mochitest/test_ext_cookies.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies.html
@@ -9,46 +9,16 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 function backgroundScript() {
-  function get(details) {
-    return new Promise(resolve => {
-      browser.cookies.get(details, resolve);
-    });
-  }
-
-  function getAll(details) {
-    return new Promise(resolve => {
-      browser.cookies.getAll(details, resolve);
-    });
-  }
-
-  function set(details) {
-    return new Promise(resolve => {
-      browser.cookies.set(details, resolve);
-    });
-  }
-
-  function remove(details) {
-    return new Promise(resolve => {
-      browser.cookies.remove(details, resolve);
-    });
-  }
-
-  function getAllCookieStores() {
-    return new Promise(resolve => {
-      browser.cookies.getAllCookieStores(resolve);
-    });
-  }
-
   function assertExpected(cookie, expected) {
     for (let key of Object.keys(cookie)) {
       browser.test.assertTrue(key in expected, "found property " + key);
       browser.test.assertEq(cookie[key], expected[key], "property value for " + key + " is wrong");
     }
     browser.test.assertEq(Object.keys(cookie).length, Object.keys(expected).length, "all expected properties found");
   }
 
@@ -63,40 +33,40 @@ function backgroundScript() {
     path: "/",
     secure: false,
     httpOnly: false,
     session: false,
     expirationDate: THE_FUTURE,
     storeId: "firefox-default",
   };
 
-  set({url: TEST_URL, name: "name1", value: "value1", expirationDate: THE_FUTURE}).then(cookie => {
+  browser.cookies.set({url: TEST_URL, name: "name1", value: "value1", expirationDate: THE_FUTURE}).then(cookie => {
     assertExpected(cookie, expected);
-    return get({url: TEST_URL, name: "name1"});
+    return browser.cookies.get({url: TEST_URL, name: "name1"});
   }).then(cookie => {
     assertExpected(cookie, expected);
-    return getAll({domain: "example.org"});
+    return browser.cookies.getAll({domain: "example.org"});
   }).then(cookies => {
     browser.test.assertEq(cookies.length, 1, "only found one cookie for example.org");
     assertExpected(cookies[0], expected);
-    return remove({url: TEST_URL, name: "name1"});
+    return browser.cookies.remove({url: TEST_URL, name: "name1"});
   }).then(details => {
     assertExpected(details, {url: TEST_URL, name: "name1", storeId: "firefox-default"});
-    return get({url: TEST_URL, name: "name1"});
+    return browser.cookies.get({url: TEST_URL, name: "name1"});
   }).then(cookie => {
     browser.test.assertEq(cookie, null);
-    return getAllCookieStores();
+    return browser.cookies.getAllCookieStores();
   }).then(stores => {
     browser.test.assertEq(stores.length, 1);
     browser.test.assertEq(stores[0].id, "firefox-default");
     browser.test.assertEq(stores[0].tabIds.length, 0); // Todo: Implement this.
-    return set({url: TEST_URL, name: "name2", domain: ".example.org", expirationDate: THE_FUTURE});
+    return browser.cookies.set({url: TEST_URL, name: "name2", domain: ".example.org", expirationDate: THE_FUTURE});
   }).then(cookie => {
     browser.test.assertEq(cookie.hostOnly, false, "not a hostOnly cookie");
-    return remove({url: TEST_URL, name: "name2"});
+    return browser.cookies.remove({url: TEST_URL, name: "name2"});
   }).then(details => {
     assertExpected(details, {url: TEST_URL, name: "name2", storeId: "firefox-default"});
   }).then(() => {
     browser.test.notifyPass("cookies");
   });
 }
 
 let extensionData = {
--- a/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies_permissions.html
@@ -14,40 +14,16 @@
 "use strict";
 
 function* testCookies(options) {
   // Changing the options object is a bit of a hack, but it allows us to easily
   // pass an expiration date to the background script.
   options.expiry = Date.now() / 1000 + 3600;
 
   function background(options) {
-    function get(details) {
-      return new Promise(resolve => {
-        browser.cookies.get(details, resolve);
-      });
-    }
-
-    function getAll(details) {
-      return new Promise(resolve => {
-        browser.cookies.getAll(details, resolve);
-      });
-    }
-
-    function set(details) {
-      return new Promise(resolve => {
-        browser.cookies.set(details, resolve);
-      });
-    }
-
-    function remove(details) {
-      return new Promise(resolve => {
-        browser.cookies.remove(details, resolve);
-      });
-    }
-
     // Ask the parent scope to change some cookies we may or may not have
     // permission for.
     let awaitChanges = new Promise(resolve => {
       browser.test.onMessage.addListener(msg => {
         browser.test.assertEq("cookies-changed", msg, "browser.test.onMessage");
         resolve();
       });
     });
@@ -57,33 +33,38 @@ function* testCookies(options) {
       changed.push(`${event.cookie.name}:${event.cause}`);
     });
     browser.test.sendMessage("change-cookies");
 
 
     // Try to access some cookies in various ways.
     let { url, domain, secure } = options;
 
+    let failures = 0;
+    let tallyFailure = error => {
+      failures++;
+    };
+
     awaitChanges.then(() => {
-      return get({ url, name: "foo" });
+      return browser.cookies.get({ url, name: "foo" });
     }).then(cookie => {
       browser.test.assertEq(options.shouldPass, cookie != null, "should pass == get cookie");
 
-      return getAll({ domain });
+      return browser.cookies.getAll({ domain });
     }).then(cookies => {
       if (options.shouldPass) {
         browser.test.assertEq(2, cookies.length, "expected number of cookies");
       } else {
         browser.test.assertEq(0, cookies.length, "expected number of cookies");
       }
 
       return Promise.all([
-        set({ url, domain, secure, name: "foo", "value": "baz", expirationDate: options.expiry }),
-        set({ url, domain, secure, name: "bar", "value": "quux", expirationDate: options.expiry }),
-        remove({ url, name: "deleted" }),
+        browser.cookies.set({ url, domain, secure, name: "foo", "value": "baz", expirationDate: options.expiry }).catch(tallyFailure),
+        browser.cookies.set({ url, domain, secure, name: "bar", "value": "quux", expirationDate: options.expiry }).catch(tallyFailure),
+        browser.cookies.remove({ url, name: "deleted" }),
       ]);
     }).then(() => {
       if (options.shouldPass) {
         // The order of eviction events isn't guaranteed, so just check that
         // it's there somewhere.
         let evicted = changed.indexOf("evicted:evicted");
         if (evicted < 0) {
           browser.test.fail("got no eviction event");
@@ -94,16 +75,24 @@ function* testCookies(options) {
 
         browser.test.assertEq("x:explicit,x:overwrite,x:explicit,foo:overwrite,bar:explicit,deleted:explicit",
                               changed.join(","), "expected changes");
       } else {
         browser.test.assertEq("", changed.join(","), "expected no changes");
       }
 
       browser.test.notifyPass("cookie-permissions");
+    }).then(() => {
+      if (!(options.shouldPass || options.shouldWrite)) {
+        browser.test.assertEq(2, failures, "Expected failures");
+      } else {
+        browser.test.assertEq(0, failures, "Expected no failures");
+      }
+    }).catch(error => {
+      browser.test.fail(`Error: ${error} :: ${error.stack}`);
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": options.permissions,
     },
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_idle.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>WebExtension idle API test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+add_task(function* testIdle() {
+  function background() {
+    browser.idle.queryState(15).then(status => {
+      browser.test.assertEq("active", status, "Expected status");
+      browser.test.notifyPass("idle");
+    },
+    e => {
+      browser.test.fail(`Error: ${e} :: ${e.stack}`);
+      browser.test.notifyFail("idle");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background: `(${background})()`,
+
+    manifest: {
+      permissions: ["idle"],
+    },
+  });
+
+  yield extension.startup();
+
+  yield extension.awaitFinish("idle");
+
+  yield extension.unload();
+});
+</script>
+</body>
deleted file mode 100644
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/selectAddons.dtd
+++ /dev/null
@@ -1,49 +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/. -->
-
-<!ENTITY upgrade.style               "width: 93ch; height: 448px;">
-
-<!ENTITY checking.heading            "Checking Your Add-ons">
-<!ENTITY checking.progress.label     "Checking your add-ons for compatibility with this version of &brandShortName;.">
-
-<!ENTITY select.heading              "Select Your Add-ons">
-<!-- LOCALIZATION NOTE (select.description): The term used for "third parties"
-     here should match the string source.other in selectAddons.properties. -->
-<!ENTITY select.description          "Make &brandShortName; even faster by disabling add-ons you no longer use. Add-ons already installed by third parties will be disabled automatically unless you select them below.">
-<!ENTITY select.keep                 "Keep">
-<!-- LOCALIZATION NOTE (select.keep.style): Should be a width wide enough for
-     the string in select.keep above. -->
-<!ENTITY select.keep.style           "width: 6ch;">
-<!ENTITY select.action               "Action">
-<!-- LOCALIZATION NOTE (select.action.style): Should be a width wide enough for
-     the action strings in selectAddons.properties or brandShortName. -->
-<!ENTITY select.action.style         "width: 35ch;">
-<!ENTITY select.source               "Installed By">
-<!ENTITY select.name                 "Name">
-<!-- LOCALIZATION NOTE (select.name.style): Should be a width small enough so
-     the source column still has enough room for the source strings in
-     selectAddons.properties. -->
-<!ENTITY select.name.style           "width: 33ch;">
-
-<!ENTITY confirm.heading             "Select Your Add-ons">
-<!-- LOCALIZATION NOTE (confirm.description): The term used for "third parties"
-     here should match the string source.other in selectAddons.properties. -->
-<!ENTITY confirm.description         "Make &brandShortName; even faster by disabling add-ons you no longer use. Add-ons already installed by third parties will be disabled automatically unless you select them below.">
-
-<!ENTITY action.disable.heading      "The following add-ons will be disabled:">
-<!ENTITY action.incompatible.heading "The following add-ons are disabled, but will be enabled as soon as they are compatible:">
-<!ENTITY action.update.heading       "The following add-ons will be updated:">
-<!ENTITY action.enable.heading       "The following add-ons will be enabled:">
-
-<!ENTITY update.heading              "Updating Your Add-ons">
-<!ENTITY update.progress.label       "Downloading and installing updates for your selected add-ons.">
-
-<!ENTITY errors.heading              "&brandShortName; could not update some of your add-ons.">
-<!ENTITY errors.description          "Installing updates for some of your add-ons failed. &brandShortName; will automatically try to update them again later.">
-
-<!ENTITY footer.label                "You can always change your add-ons by going to the Add-ons Manager.">
-<!ENTITY cancel.label                "Cancel">
-<!ENTITY back.label                  "Back">
-<!ENTITY next.label                  "Next">
-<!ENTITY done.label                  "Done">
deleted file mode 100644
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/selectAddons.properties
+++ /dev/null
@@ -1,21 +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/.
-
-#LOCALIZATION NOTE (source.profile) add-ons installed by the user, this may be
-# translated as "You" or "User" depending on the locale
-source.profile=You
-#LOCALIZATION NOTE (source.bundled) add-ons shipped with the application, and thus
-# treated as installed by the user. This may be
-# translated as "You" or "User" depending on the locale
-source.bundled=You (Bundled)
-#LOCALIZATION NOTE (source.other) add-ons installed by other applications
-# installed on the computer
-source.other=Third Party
-
-action.enabled=Will be enabled
-action.disabled=Will be disabled
-action.autoupdate=Will be updated to be compatible
-action.incompatible=Will be enabled when compatible
-action.neededupdate=Update to make compatible
-action.unneededupdate=Optional update
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -100,18 +100,16 @@
   locale/@AB_CD@/mozapps/downloads/settingsChange.dtd             (%chrome/mozapps/downloads/settingsChange.dtd)
   locale/@AB_CD@/mozapps/downloads/downloads.dtd                  (%chrome/mozapps/downloads/downloads.dtd)
   locale/@AB_CD@/mozapps/downloads/downloads.properties           (%chrome/mozapps/downloads/downloads.properties)
 #ifndef MOZ_FENNEC
   locale/@AB_CD@/mozapps/extensions/extensions.dtd                (%chrome/mozapps/extensions/extensions.dtd)
   locale/@AB_CD@/mozapps/extensions/extensions.properties         (%chrome/mozapps/extensions/extensions.properties)
   locale/@AB_CD@/mozapps/extensions/blocklist.dtd                 (%chrome/mozapps/extensions/blocklist.dtd)
   locale/@AB_CD@/mozapps/extensions/about.dtd                     (%chrome/mozapps/extensions/about.dtd)
-  locale/@AB_CD@/mozapps/extensions/selectAddons.dtd              (%chrome/mozapps/extensions/selectAddons.dtd)
-  locale/@AB_CD@/mozapps/extensions/selectAddons.properties       (%chrome/mozapps/extensions/selectAddons.properties)
   locale/@AB_CD@/mozapps/extensions/update.dtd                    (%chrome/mozapps/extensions/update.dtd)
   locale/@AB_CD@/mozapps/extensions/update.properties             (%chrome/mozapps/extensions/update.properties)
   locale/@AB_CD@/mozapps/extensions/newaddon.dtd                  (%chrome/mozapps/extensions/newaddon.dtd)
   locale/@AB_CD@/mozapps/extensions/newaddon.properties           (%chrome/mozapps/extensions/newaddon.properties)
 #endif
   locale/@AB_CD@/mozapps/handling/handling.dtd                    (%chrome/mozapps/handling/handling.dtd)
   locale/@AB_CD@/mozapps/handling/handling.properties             (%chrome/mozapps/handling/handling.properties)
   locale/@AB_CD@/mozapps/preferences/changemp.dtd                 (%chrome/mozapps/preferences/changemp.dtd)
deleted file mode 100644
--- a/toolkit/mozapps/extensions/content/selectAddons.css
+++ /dev/null
@@ -1,22 +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/. */
-
-#select .addon {
-  -moz-binding: url("chrome://mozapps/content/extensions/selectAddons.xml#addon-select");
-}
-
-#confirm .addon {
-  -moz-binding: url("chrome://mozapps/content/extensions/selectAddons.xml#addon-confirm");
-}
-
-#select-scrollbox,
-#confirm-scrollbox {
-  overflow-y: auto;
-  -moz-box-orient: vertical;
-}
-
-.addon:not([optionalupdate]) .addon-action-update,
-.addon[optionalupdate] .addon-action-message {
-  display: none;
-}
deleted file mode 100644
--- a/toolkit/mozapps/extensions/content/selectAddons.js
+++ /dev/null
@@ -1,346 +0,0 @@
-// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-Components.utils.import("resource://gre/modules/AddonManager.jsm");
-Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-
-var gView = null;
-
-function showView(aView) {
-  gView = aView;
-
-  gView.show();
-
-  // If the view's show method immediately showed a different view then don't
-  // do anything else
-  if (gView != aView)
-    return;
-
-  let viewNode = document.getElementById(gView.nodeID);
-  viewNode.parentNode.selectedPanel = viewNode;
-
-  // For testing dispatch an event when the view changes
-  var event = document.createEvent("Events");
-  event.initEvent("ViewChanged", true, true);
-  viewNode.dispatchEvent(event);
-}
-
-function showButtons(aCancel, aBack, aNext, aDone) {
-  document.getElementById("cancel").hidden = !aCancel;
-  document.getElementById("back").hidden = !aBack;
-  document.getElementById("next").hidden = !aNext;
-  document.getElementById("done").hidden = !aDone;
-}
-
-function isAddonDistroInstalled(aID) {
-  let branch = Services.prefs.getBranch("extensions.installedDistroAddon.");
-  if (!branch.prefHasUserValue(aID))
-    return false;
-
-  return branch.getBoolPref(aID);
-}
-
-function orderForScope(aScope) {
-  return aScope == AddonManager.SCOPE_PROFILE ? 1 : 0;
-}
-
-var gAddons = {};
-
-var gChecking = {
-  nodeID: "checking",
-
-  _progress: null,
-  _addonCount: 0,
-  _completeCount: 0,
-
-  show: function() {
-    showButtons(true, false, false, false);
-    this._progress = document.getElementById("checking-progress");
-
-    AddonManager.getAllAddons(aAddons => {
-      if (aAddons.length == 0) {
-        window.close();
-        return;
-      }
-
-      aAddons = aAddons.filter(function(aAddon) {
-        if (aAddon.id == AddonManager.hotfixID) {
-          return false;
-        }
-        if (aAddon.type == "plugin" || aAddon.type == "service")
-          return false;
-
-        if (aAddon.type == "theme") {
-          // Don't show application shipped themes
-          if (aAddon.scope == AddonManager.SCOPE_APPLICATION)
-            return false;
-          // Don't show already disabled themes
-          if (aAddon.userDisabled)
-            return false;
-        }
-
-        return true;
-      });
-
-      this._addonCount = aAddons.length;
-      this._progress.value = 0;
-      this._progress.max = aAddons.length;
-      this._progress.mode = "determined";
-
-      AddonRepository.repopulateCache().then(() => {
-        for (let addonItem of aAddons) {
-          // Ignore disabled themes
-          if (addonItem.type != "theme" || !addonItem.userDisabled) {
-            gAddons[addonItem.id] = {
-              addon: addonItem,
-              install: null,
-              wasActive: addonItem.isActive
-            }
-          }
-
-          addonItem.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
-        }
-      });
-    });
-  },
-
-  onUpdateAvailable: function(aAddon, aInstall) {
-    // If the add-on can be upgraded then remember the new version
-    if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE)
-      gAddons[aAddon.id].install = aInstall;
-  },
-
-  onUpdateFinished: function(aAddon, aError) {
-    this._completeCount++;
-    this._progress.value = this._completeCount;
-
-    if (this._completeCount < this._addonCount)
-      return;
-
-    let addons = Object.keys(gAddons).map(k => gAddons[k]);
-
-    addons.sort(function(a, b) {
-      let orderA = orderForScope(a.addon.scope);
-      let orderB = orderForScope(b.addon.scope);
-
-      if (orderA != orderB)
-        return orderA - orderB;
-
-      return String.localeCompare(a.addon.name, b.addon.name);
-    });
-
-    let rows = document.getElementById("select-rows");
-    let lastAddon = null;
-    for (let entry of addons) {
-      if (lastAddon &&
-          orderForScope(entry.addon.scope) != orderForScope(lastAddon.scope)) {
-        let separator = document.createElement("separator");
-        rows.appendChild(separator);
-      }
-
-      let row = document.createElement("row");
-      row.setAttribute("id", entry.addon.id);
-      row.setAttribute("class", "addon");
-      rows.appendChild(row);
-      row.setAddon(entry.addon, entry.install, entry.wasActive,
-                   isAddonDistroInstalled(entry.addon.id));
-
-      if (entry.install)
-        entry.install.addListener(gUpdate);
-
-      lastAddon = entry.addon;
-    }
-
-    showView(gSelect);
-  }
-};
-
-var gSelect = {
-  nodeID: "select",
-
-  show: function() {
-    this.updateButtons();
-  },
-
-  updateButtons: function() {
-    for (let row = document.getElementById("select-rows").firstChild;
-         row; row = row.nextSibling) {
-      if (row.localName == "separator")
-        continue;
-
-      if (row.action) {
-        showButtons(false, false, true, false);
-        return;
-      }
-    }
-
-    showButtons(false, false, false, true);
-  },
-
-  next: function() {
-    showView(gConfirm);
-  },
-
-  done: function() {
-    window.close();
-  }
-};
-
-var gConfirm = {
-  nodeID: "confirm",
-
-  show: function() {
-    showButtons(false, true, false, true);
-
-    let box = document.getElementById("confirm-scrollbox").firstChild;
-    while (box) {
-      box.hidden = true;
-      while (box.lastChild != box.firstChild)
-        box.removeChild(box.lastChild);
-      box = box.nextSibling;
-    }
-
-    for (let row = document.getElementById("select-rows").firstChild;
-         row; row = row.nextSibling) {
-      if (row.localName == "separator")
-        continue;
-
-      let action = row.action;
-      if (!action)
-        continue;
-
-      let list = document.getElementById(action + "-list");
-
-      list.hidden = false;
-      let item = document.createElement("hbox");
-      item.setAttribute("id", row._addon.id);
-      item.setAttribute("class", "addon");
-      item.setAttribute("type", row._addon.type);
-      item.setAttribute("name", row._addon.name);
-      if (action == "update" || action == "enable")
-        item.setAttribute("active", "true");
-      list.appendChild(item);
-
-      if (action == "update")
-        showButtons(false, true, true, false);
-    }
-  },
-
-  back: function() {
-    showView(gSelect);
-  },
-
-  next: function() {
-    showView(gUpdate);
-  },
-
-  done: function() {
-    for (let row = document.getElementById("select-rows").firstChild;
-         row; row = row.nextSibling) {
-      if (row.localName != "separator")
-        row.apply();
-    }
-
-    window.close();
-  }
-}
-
-var gUpdate = {
-  nodeID: "update",
-
-  _progress: null,
-  _waitingCount: 0,
-  _completeCount: 0,
-  _errorCount: 0,
-
-  show: function() {
-    showButtons(true, false, false, false);
-
-    this._progress = document.getElementById("update-progress");
-
-    for (let row = document.getElementById("select-rows").firstChild;
-         row; row = row.nextSibling) {
-      if (row.localName != "separator")
-        row.apply();
-    }
-
-    this._progress.mode = "determined";
-    this._progress.max = this._waitingCount;
-    this._progress.value = this._completeCount;
-  },
-
-  checkComplete: function() {
-    this._progress.value = this._completeCount;
-    if (this._completeCount < this._waitingCount)
-      return;
-
-    if (this._errorCount > 0) {
-      showView(gErrors);
-      return;
-    }
-
-    window.close();
-  },
-
-  onDownloadStarted: function(aInstall) {
-    this._waitingCount++;
-  },
-
-  onDownloadFailed: function(aInstall) {
-    this._errorCount++;
-    this._completeCount++;
-    this.checkComplete();
-  },
-
-  onInstallFailed: function(aInstall) {
-    this._errorCount++;
-    this._completeCount++;
-    this.checkComplete();
-  },
-
-  onInstallEnded: function(aInstall) {
-    this._completeCount++;
-    this.checkComplete();
-  }
-};
-
-var gErrors = {
-  nodeID: "errors",
-
-  show: function() {
-    showButtons(false, false, false, true);
-  },
-
-  done: function() {
-    window.close();
-  }
-};
-
-window.addEventListener("load", function() {
-                                         showView(gChecking); }, false);
-
-// When closing the window cancel any pending or in-progress installs
-window.addEventListener("unload", function() {
-  for (let id in gAddons) {
-    let entry = gAddons[id];
-    if (!entry.install)
-      return;
-
-    aEntry.install.removeListener(gUpdate);
-
-    if (entry.install.state != AddonManager.STATE_INSTALLED &&
-        entry.install.state != AddonManager.STATE_DOWNLOAD_FAILED &&
-        entry.install.state != AddonManager.STATE_INSTALL_FAILED) {
-      entry.install.cancel();
-    }
-  }
-}, false);
deleted file mode 100644
--- a/toolkit/mozapps/extensions/content/selectAddons.xml
+++ /dev/null
@@ -1,235 +0,0 @@
-<?xml version="1.0"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!DOCTYPE window [
-<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/selectAddons.dtd">
-<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
-%updateDTD;
-%brandDTD;
-]>
-
-<bindings xmlns="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-          xmlns:xbl="http://www.mozilla.org/xbl">
-
-  <binding id="addon-select">
-    <content>
-      <xul:hbox class="select-keep select-cell">
-        <xul:checkbox class="addon-keep-checkbox" anonid="keep"
-                      xbl:inherits="tooltiptext=name"
-                      oncommand="document.getBindingParent(this).keepChanged();"/>
-      </xul:hbox>
-      <xul:hbox class="select-icon select-cell">
-        <xul:image class="addon-icon" xbl:inherits="type"/>
-      </xul:hbox>
-      <xul:hbox class="select-name select-cell">
-        <xul:label class="addon-name" crop="end" style="&select.name.style;"
-                   xbl:inherits="xbl:text=name"/>
-      </xul:hbox>
-      <xul:hbox class="select-action select-cell">
-        <xul:label class="addon-action-message" style="&select.action.style;"
-                   xbl:inherits="xbl:text=action"/>
-        <xul:checkbox anonid="update" checked="true" class="addon-action-update"
-                      oncommand="document.getBindingParent(this).updateChanged();"
-                      style="&select.action.style;" xbl:inherits="label=action"/>
-      </xul:hbox>
-      <xul:hbox class="select-source select-cell">
-        <xul:label class="addon-source" xbl:inherits="xbl:text=source"/>
-      </xul:hbox>
-    </content>
-
-    <implementation>
-      <field name="_addon"/>
-      <field name="_disabled"/>
-      <field name="_install"/>
-      <field name="_wasActive"/>
-      <field name="_keep">document.getAnonymousElementByAttribute(this, "anonid", "keep");</field>
-      <field name="_update">document.getAnonymousElementByAttribute(this, "anonid", "update");</field>
-      <field name="_strings">document.getElementById("strings");</field>
-
-      <property name="action" readonly="true">
-        <getter><![CDATA[
-          if (!this._keep.checked) {
-            if (this._wasActive)
-              return "disable";
-            else
-              return null;
-          }
-
-          if (this._addon.appDisabled && !this._install)
-            return "incompatible";
-
-          if (this._install && (AddonManager.shouldAutoUpdate(this._addon) || this._update.checked))
-            return "update";
-
-          if (this._addon.permissions & AddonManager.PERM_CAN_ENABLE)
-            return "enable";
-
-          return null;
-        ]]></getter>
-      </property>
-
-      <method name="setAddon">
-        <parameter name="aAddon"/>
-        <parameter name="aInstall"/>
-        <parameter name="aWasActive"/>
-        <parameter name="aDistroInstalled"/>
-        <body><![CDATA[
-          this._addon = aAddon;
-          this._install = aInstall;
-          this._wasActive = aWasActive;
-
-          this.setAttribute("name", aAddon.name);
-          this.setAttribute("type", aAddon.type);
-
-          // User and bundled add-ons default to staying enabled,
-          // others default to disabled.
-          switch (aAddon.scope) {
-            case AddonManager.SCOPE_PROFILE:
-              this._keep.checked = !aAddon.userDisabled;
-              if (aDistroInstalled)
-                this.setAttribute("source", this._strings.getString("source.bundled"));
-              else
-                this.setAttribute("source", this._strings.getString("source.profile"));
-              break;
-            default:
-              this._keep.checked = false;
-              this.setAttribute("source", this._strings.getString("source.other"));
-          }
-
-          this.updateAction();
-        ]]></body>
-      </method>
-
-      <method name="setActionMessage">
-        <body><![CDATA[
-          if (!this._keep.checked) {
-            // If the user no longer wants this add-on then it is either being
-            // disabled or nothing is changing. Don't complicate matters by
-            // talking about updates for this case
-
-            if (this._wasActive)
-              this.setAttribute("action", this._strings.getString("action.disabled"));
-            else
-              this.setAttribute("action", "");
-
-            this.removeAttribute("optionalupdate");
-            return;
-          }
-
-          if (this._addon.appDisabled && !this._install) {
-            // If the add-on is incompatible and there is no update available
-            // then it will remain disabled
-
-            this.setAttribute("action", this._strings.getString("action.incompatible"));
-
-            this.removeAttribute("optionalupdate");
-            return;
-          }
-
-          if (this._install) {
-            if (!AddonManager.shouldAutoUpdate(this._addon)) {
-              this.setAttribute("optionalupdate", "true");
-
-              // If manual updating for the add-on then display the right
-              // text depending on whether the update is required to make
-              // the add-on work or not
-              if (this._addon.appDisabled)
-                this.setAttribute("action", this._strings.getString("action.neededupdate"));
-              else
-                this.setAttribute("action", this._strings.getString("action.unneededupdate"));
-              return;
-            }
-
-            this.removeAttribute("optionalupdate");
-
-            // If the update is needed to make the add-on compatible then
-            // say so otherwise just say nothing about it
-            if (this._addon.appDisabled) {
-              this.setAttribute("action", this._strings.getString("action.autoupdate"));
-              return;
-            }
-          }
-          else {
-            this.removeAttribute("optionalupdate");
-          }
-
-          // If the add-on didn't used to be active and it now is (via a
-          // compatibility update) or it can be enabled then the action is to
-          // enable the add-on
-          if (!this._wasActive && (this._addon.isActive || this._addon.permissions & AddonManager.PERM_CAN_ENABLE)) {
-            this.setAttribute("action", this._strings.getString("action.enabled"));
-            return;
-          }
-
-          // In all other cases the add-on is simply remaining enabled
-          this.setAttribute("action", "");
-        ]]></body>
-      </method>
-
-      <method name="updateAction">
-        <body><![CDATA[
-          this.setActionMessage();
-          let installingUpdate = this._install &&
-                                 (AddonManager.shouldAutoUpdate(this._addon) ||
-                                  this._update.checked);
-
-          if (this._keep.checked && (!this._addon.appDisabled || installingUpdate))
-            this.setAttribute("active", "true");
-          else
-            this.removeAttribute("active");
-
-          gSelect.updateButtons();
-        ]]></body>
-      </method>
-
-      <method name="updateChanged">
-        <body><![CDATA[
-          this.updateAction();
-        ]]></body>
-      </method>
-
-      <method name="keepChanged">
-        <body><![CDATA[
-          this.updateAction();
-        ]]></body>
-      </method>
-
-      <method name="keep">
-        <body><![CDATA[
-          this._keep.checked = true;
-          this.keepChanged();
-        ]]></body>
-      </method>
-
-      <method name="disable">
-        <body><![CDATA[
-          this._keep.checked = false;
-          this.keepChanged();
-        ]]></body>
-      </method>
-
-      <method name="apply">
-        <body><![CDATA[
-          this._addon.userDisabled = !this._keep.checked;
-
-          if (!this._install || !this._keep.checked)
-            return;
-
-          if (AddonManager.shouldAutoUpdate(this._addon) || this._update.checked)
-            this._install.install();
-        ]]></body>
-      </method>
-    </implementation>
-  </binding>
-
-  <binding id="addon-confirm">
-    <content>
-      <xul:image class="addon-icon" xbl:inherits="type"/>
-      <xul:label class="addon-name" xbl:inherits="xbl:text=name"/>
-    </content>
-  </binding>
-</bindings>
deleted file mode 100644
--- a/toolkit/mozapps/extensions/content/selectAddons.xul
+++ /dev/null
@@ -1,124 +0,0 @@
-<?xml version="1.0"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> 
-<?xml-stylesheet href="chrome://mozapps/content/extensions/selectAddons.css" type="text/css"?> 
-<?xml-stylesheet href="chrome://mozapps/skin/extensions/selectAddons.css" type="text/css"?> 
-
-<!DOCTYPE window [
-<!ENTITY % updateDTD SYSTEM "chrome://mozapps/locale/extensions/selectAddons.dtd">
-<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
-%updateDTD;
-%brandDTD;
-]>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        style="&upgrade.style;" id="select-window">
-
-  <script type="application/javascript" src="chrome://mozapps/content/extensions/selectAddons.js"/>
-  <stringbundle id="strings" src="chrome://mozapps/locale/extensions/selectAddons.properties"/>
-
-  <deck id="view-deck" flex="1" align="stretch" pack="stretch">
-    <vbox id="checking" align="stretch">
-      <vbox flex="1">
-        <label id="checking-heading" class="heading">&checking.heading;</label>
-      </vbox>
-      <progressmeter id="checking-progress" class="progress" mode="undetermined"/>
-      <vbox flex="1">
-        <label id="checking-progress-label" class="progress-label">&checking.progress.label;</label>
-      </vbox>
-    </vbox>
-
-    <vbox id="select" align="stretch">
-      <label id="select-heading" class="heading">&select.heading;</label>
-
-      <description id="select-description">&select.description;</description>
-
-      <vbox id="select-list" align="stretch" flex="1">
-        <hbox id="select-header">
-          <hbox class="select-keep select-cell" style="&select.keep.style;">
-            <label value="&select.keep;"/>
-          </hbox>
-          <hbox class="select-icon select-cell"/>
-          <hbox id="heading-name" class="select-name select-cell" style="&select.name.style;">
-            <label value="&select.name;"/>
-          </hbox>
-          <hbox id="heading-action" class="select-action select-cell" style="&select.action.style;">
-            <label value="&select.action;"/>
-          </hbox>
-          <hbox class="select-source select-cell" flex="1">
-            <label value="&select.source;"/>
-          </hbox>
-        </hbox>
-
-        <scrollbox id="select-scrollbox" flex="1">
-          <grid id="select-grid" flex="1">
-            <columns>
-              <column style="&select.keep.style;"/>
-              <column/>
-              <column id="column-name"/>
-              <column id="column-action" class="select-action"/>
-              <column class="select-source" flex="1"/>
-            </columns>
-
-            <rows id="select-rows"/>
-          </grid>
-        </scrollbox>
-      </vbox>
-    </vbox>
-
-    <vbox id="confirm" align="stretch">
-      <label id="confirm-heading" class="heading">&confirm.heading;</label>
-
-      <description id="confirm-description">&confirm.description;</description>
-
-      <scrollbox id="confirm-scrollbox" flex="1">
-        <vbox id="disable-list" class="action-list" hidden="true">
-          <label class="action-header">&action.disable.heading;</label>
-        </vbox>
-
-        <vbox id="incompatible-list" class="action-list" hidden="true">
-          <label class="action-header">&action.incompatible.heading;</label>
-        </vbox>
-
-        <vbox id="update-list" class="action-list" hidden="true">
-          <label class="action-header">&action.update.heading;</label>
-        </vbox>
-
-        <vbox id="enable-list" class="action-list" hidden="true">
-          <label class="action-header">&action.enable.heading;</label>
-        </vbox>
-      </scrollbox>
-    </vbox>
-
-    <vbox id="update" align="stretch">
-      <vbox flex="1">
-        <label id="update-heading" class="heading">&update.heading;</label>
-      </vbox>
-      <progressmeter id="update-progress" class="progress" mode="undetermined"/>
-      <vbox flex="1">
-        <label id="update-progress-label" class="progress-label">&update.progress.label;</label>
-      </vbox>
-    </vbox>
-
-    <vbox id="errors">
-      <vbox flex="1">
-        <label id="errors-heading" class="heading">&errors.heading;</label>
-      </vbox>
-      <description id="errors-description" value="&errors.description;"/>
-      <spacer flex="1"/>
-    </vbox>
-  </deck>
-
-  <hbox id="footer" align="center">
-    <label id="footer-label" flex="1">&footer.label;</label>
-    <button id="cancel" label="&cancel.label;" oncommand="window.close()"/>
-    <button id="back" label="&back.label;" oncommand="gView.back()" hidden="true"/>
-    <button id="next" label="&next.label;" oncommand="gView.next()" hidden="true"/>
-    <button id="done" label="&done.label;" oncommand="gView.done()" hidden="true"/>
-  </hbox>
-
-</window>
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -103,32 +103,30 @@ const PREF_XPI_FILE_WHITELISTED       = 
 const PREF_XPI_SIGNATURES_REQUIRED    = "xpinstall.signatures.required";
 const PREF_XPI_SIGNATURES_DEV_ROOT    = "xpinstall.signatures.dev-root";
 const PREF_XPI_PERMISSIONS_BRANCH     = "xpinstall.";
 const PREF_XPI_UNPACK                 = "extensions.alwaysUnpack";
 const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
 const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
 const PREF_INSTALL_DISTRO_ADDONS      = "extensions.installDistroAddons";
 const PREF_BRANCH_INSTALLED_ADDON     = "extensions.installedDistroAddon.";
-const PREF_SHOWN_SELECTION_UI         = "extensions.shownSelectionUI";
 const PREF_INTERPOSITION_ENABLED      = "extensions.interposition.enabled";
 const PREF_SYSTEM_ADDON_SET           = "extensions.systemAddonSet";
 const PREF_SYSTEM_ADDON_UPDATE_URL    = "extensions.systemAddon.update.url";
 const PREF_E10S_BLOCK_ENABLE          = "extensions.e10sBlocksEnabling";
 
 const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
 const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
 
 const PREF_CHECKCOMAT_THEMEOVERRIDE   = "extensions.checkCompatibility.temporaryThemeOverride_minAppVersion";
 
 const PREF_EM_HOTFIX_ID               = "extensions.hotfix.id";
 const PREF_EM_CERT_CHECKATTRIBUTES    = "extensions.hotfix.cert.checkAttributes";
 const PREF_EM_HOTFIX_CERTS            = "extensions.hotfix.certs.";
 
-const URI_EXTENSION_SELECT_DIALOG     = "chrome://mozapps/content/extensions/selectAddons.xul";
 const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
 const URI_EXTENSION_STRINGS           = "chrome://mozapps/locale/extensions/extensions.properties";
 
 const STRING_TYPE_NAME                = "type.%ID%.name";
 
 const DIR_EXTENSIONS                  = "extensions";
 const DIR_SYSTEM_ADDONS               = "features";
 const DIR_STAGE                       = "staged";
@@ -2601,39 +2599,22 @@ this.XPIProvider = {
       let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion,
                                              aOldPlatformVersion);
 
       // Changes to installed extensions may have changed which theme is selected
       this.applyThemeChange();
 
       AddonManagerPrivate.markProviderSafe(this);
 
-      if (aAppChanged === undefined) {
-        // For new profiles we will never need to show the add-on selection UI
-        Services.prefs.setBoolPref(PREF_SHOWN_SELECTION_UI, true);
-      }
-      else if (aAppChanged && !this.allAppGlobal &&
-               Preferences.get(PREF_EM_SHOW_MISMATCH_UI, true)) {
-        if (!Preferences.get(PREF_SHOWN_SELECTION_UI, false)) {
-          // Flip a flag to indicate that we interrupted startup with an interactive prompt
-          Services.startup.interrupted = true;
-          // This *must* be modal as it has to block startup.
-          var features = "chrome,centerscreen,dialog,titlebar,modal";
-          Services.ww.openWindow(null, URI_EXTENSION_SELECT_DIALOG, "", features, null);
-          Services.prefs.setBoolPref(PREF_SHOWN_SELECTION_UI, true);
-          // Ensure any changes to the add-ons list are flushed to disk
-          Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
-                                     !XPIDatabase.writeAddonsList());
-        }
-        else {
-          let addonsToUpdate = this.shouldForceUpdateCheck(aAppChanged);
-          if (addonsToUpdate) {
-            this.showUpgradeUI(addonsToUpdate);
-            flushCaches = true;
-          }
+      if (aAppChanged && !this.allAppGlobal &&
+          Preferences.get(PREF_EM_SHOW_MISMATCH_UI, true)) {
+        let addonsToUpdate = this.shouldForceUpdateCheck(aAppChanged);
+        if (addonsToUpdate) {
+          this.showUpgradeUI(addonsToUpdate);
+          flushCaches = true;
         }
       }
 
       if (flushCaches) {
         flushStartupCache();
         // UI displayed early in startup (like the compatibility UI) may have
         // caused us to cache parts of the skin or locale in memory. These must
         // be flushed to allow extension provided skins and locales to take full
--- a/toolkit/mozapps/extensions/jar.mn
+++ b/toolkit/mozapps/extensions/jar.mn
@@ -13,20 +13,16 @@ toolkit.jar:
   content/mozapps/extensions/about.xul                          (content/about.xul)
   content/mozapps/extensions/about.js                           (content/about.js)
   content/mozapps/extensions/list.xul                           (content/list.xul)
   content/mozapps/extensions/list.js                            (content/list.js)
   content/mozapps/extensions/blocklist.xul                      (content/blocklist.xul)
   content/mozapps/extensions/blocklist.js                       (content/blocklist.js)
   content/mozapps/extensions/blocklist.css                      (content/blocklist.css)
   content/mozapps/extensions/blocklist.xml                      (content/blocklist.xml)
-  content/mozapps/extensions/selectAddons.xul                   (content/selectAddons.xul)
-  content/mozapps/extensions/selectAddons.xml                   (content/selectAddons.xml)
-  content/mozapps/extensions/selectAddons.js                    (content/selectAddons.js)
-  content/mozapps/extensions/selectAddons.css                   (content/selectAddons.css)
 * content/mozapps/extensions/update.xul                         (content/update.xul)
   content/mozapps/extensions/update.js                          (content/update.js)
   content/mozapps/extensions/eula.xul                           (content/eula.xul)
   content/mozapps/extensions/eula.js                            (content/eula.js)
   content/mozapps/extensions/newaddon.xul                       (content/newaddon.xul)
   content/mozapps/extensions/newaddon.js                        (content/newaddon.js)
   content/mozapps/extensions/setting.xml                        (content/setting.xml)
   content/mozapps/extensions/pluginPrefs.xul                    (content/pluginPrefs.xul)
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/browser/addons/browser_select_compatoverrides_1/install.rdf
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0"?>
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>addon1@tests.mozilla.org</em:id>
-    <em:version>1.0</em:version>
-
-    <em:targetApplication>
-      <Description>
-        <em:id>toolkit@mozilla.org</em:id>
-        <em:minVersion>0</em:minVersion>
-        <em:maxVersion>0.1</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-    <!-- Front End MetaData -->
-    <em:name>Addon1</em:name>
-    <em:bootstrap>true</em:bootstrap>
-
-  </Description>
-</RDF>
--- a/toolkit/mozapps/extensions/test/browser/browser.ini
+++ b/toolkit/mozapps/extensions/test/browser/browser.ini
@@ -22,17 +22,16 @@ support-files =
   browser_bug557956.rdf
   browser_bug557956_8_2.xpi
   browser_bug557956_9_2.xpi
   browser_bug557956.xml
   browser_bug591465.xml
   browser_bug593535.xml
   browser_searching.xml
   browser_searching_empty.xml
-  browser_select_compatoverrides.xml
   browser_updatessl.rdf
   browser_updatessl.rdf^headers^
   browser_install.rdf
   browser_install.rdf^headers^
   browser_install.xml
   browser_install1_3.xpi
   browser_eula.xml
   browser_purchase.xml
@@ -43,17 +42,13 @@ skip-if = e10s
 [browser_bug616841.js]
 [browser_cancelCompatCheck.js]
 [browser_checkAddonCompatibility.js]
 [browser_gmpProvider.js]
 [browser_hotfix.js]
 [browser_installssl.js]
 [browser_newaddon.js]
 skip-if = e10s
-[browser_select_compatoverrides.js]
-[browser_select_confirm.js]
-[browser_select_selection.js]
-[browser_select_update.js]
 [browser_updatessl.js]
 [browser_task_next_test.js]
 [browser_discovery_install.js]
 
 [include:browser-common.ini]
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_select_compatoverrides.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Tests that compatibility overrides are refreshed when showing the addon
-// selection UI.
-
-const PREF_GETADDONS_BYIDS            = "extensions.getAddons.get.url";
-const PREF_MIN_PLATFORM_COMPAT        = "extensions.minCompatiblePlatformVersion";
-
-var gTestAddon = null;
-var gWin;
-
-function waitForView(aView, aCallback) {
-  var view = gWin.document.getElementById(aView);
-  if (view.parentNode.selectedPanel == view) {
-    aCallback();
-    return;
-  }
-
-  view.addEventListener("ViewChanged", function() {
-    view.removeEventListener("ViewChanged", arguments.callee, false);
-    aCallback();
-  }, false);
-}
-
-function install_test_addon(aCallback) {
-  AddonManager.getInstallForURL(TESTROOT + "addons/browser_select_compatoverrides_1.xpi", function(aInstall) {
-    var listener = {
-      onInstallEnded: function() {
-        AddonManager.getAddonByID("addon1@tests.mozilla.org", function(addon) {
-          gTestAddon = addon;
-          executeSoon(aCallback);
-        });
-      }
-    };
-    aInstall.addListener(listener);
-    aInstall.install();
-  }, "application/x-xpinstall");
-}
-
-registerCleanupFunction(function() {
-  if (gWin)
-    gWin.close();
-  if (gTestAddon)
-    gTestAddon.uninstall();
-
-  Services.prefs.clearUserPref(PREF_MIN_PLATFORM_COMPAT);
-});
-
-function end_test() {
-  finish();
-}
-
-
-function test() {
-  waitForExplicitFinish();
-  Services.prefs.setCharPref(PREF_UPDATEURL, TESTROOT + "missing.rdf");
-  Services.prefs.setBoolPref(PREF_STRICT_COMPAT, false);
-  Services.prefs.setCharPref(PREF_MIN_PLATFORM_COMPAT, "0");
-
-  install_test_addon(run_next_test);
-}
-
-add_test(function() {
-  gWin = Services.ww.openWindow(null,
-                              "chrome://mozapps/content/extensions/selectAddons.xul",
-                              "",
-                              "chrome,centerscreen,dialog,titlebar",
-                              null);
-  waitForFocus(function() {
-    waitForView("select", run_next_test);
-  }, gWin);
-});
-
-add_test(function() {
-  for (var row = gWin.document.getElementById("select-rows").firstChild; row; row = row.nextSibling) {
-    if (row.localName == "separator")
-      continue;
-    if (row.id.substr(-18) != "@tests.mozilla.org")
-      continue;
-
-    is(row.id, "addon1@tests.mozilla.org", "Should get expected addon");
-    isnot(row.action, "incompatible", "Addon should not be incompatible");
-
-    gWin.close();
-    gWin = null;
-    run_next_test();
-  }
-});
-
-add_test(function() {
-  Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, TESTROOT + "browser_select_compatoverrides.xml");
-  Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
-
-  gWin = Services.ww.openWindow(null,
-                              "chrome://mozapps/content/extensions/selectAddons.xul",
-                              "",
-                              "chrome,centerscreen,dialog,titlebar",
-                              null);
-  waitForFocus(function() {
-    waitForView("select", run_next_test);
-  }, gWin);
-});
-
-add_test(function() {
-  for (var row = gWin.document.getElementById("select-rows").firstChild; row; row = row.nextSibling) {
-    if (row.localName == "separator")
-      continue;
-    if (row.id.substr(-18) != "@tests.mozilla.org")
-      continue;
-    is(row.id, "addon1@tests.mozilla.org", "Should get expected addon");
-    is(row.action, "incompatible", "Addon should be incompatible");
-    run_next_test();
-  }
-});
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_select_compatoverrides.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<searchresults total_results="1">
-  <addon_compatibility hosted="false">
-    <guid>addon1@tests.mozilla.org</guid>
-    <name>Addon1</name>
-    <version_ranges>
-      <version_range type="incompatible">
-        <min_version>1.0</min_version>
-        <max_version>2.0</max_version>
-        <compatible_applications>
-          <application>
-            <min_version>0.1</min_version>
-            <max_version>999.0</max_version>
-            <appID>toolkit@mozilla.org</appID>
-          </application>
-        </compatible_applications>
-      </version_range>
-    </version_ranges>
-  </addon_compatibility>
-</searchresults>
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_select_confirm.js
+++ /dev/null
@@ -1,181 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Tests the confirmation part of the post-app-update dialog
-
-var gProvider;
-var gWin;
-
-function waitForView(aView, aCallback) {
-  var view = gWin.document.getElementById(aView);
-  if (view.parentNode.selectedPanel == view) {
-    aCallback();
-    return;
-  }
-
-  view.addEventListener("ViewChanged", function() {
-    view.removeEventListener("ViewChanged", arguments.callee, false);
-    try {
-      aCallback();
-    }
-    catch (e) {
-      ok(false, e);
-    }
-  }, false);
-}
-
-/**
- * Creates 4 test add-ons. Two are disabled and two enabled.
- *
- * @param  aAppDisabled
- *         The appDisabled property for the test add-ons
- * @param  aUpdateAvailable
- *         True if the test add-ons should claim to have an update available
- */
-function setupUI(aAppDisabled, aUpdateAvailable, aCallback) {
-  if (gProvider)
-    gProvider.unregister();
-
-  gProvider = new MockProvider();
-
-  for (var i = 1; i < 5; i++) {
-    var addon = new MockAddon("test" + i + "@tests.mozilla.org",
-                              "Test Add-on " + i, "extension");
-    addon.version = "1.0";
-    addon.userDisabled = (i > 2);
-    addon.appDisabled = aAppDisabled;
-    addon.isActive = !addon.userDisabled && !addon.appDisabled;
-
-    addon.findUpdates = function(aListener, aReason, aAppVersion, aPlatformVersion) {
-      if (aUpdateAvailable) {
-        var newAddon = new MockAddon(this.id, this.name, "extension");
-        newAddon.version = "2.0";
-        var install = new MockInstall(this.name, this.type, newAddon);
-        install.existingAddon = this;
-        aListener.onUpdateAvailable(this, install);
-      }
-
-      aListener.onUpdateFinished(this, AddonManager.UPDATE_STATUS_NO_ERROR);
-    };
-
-    gProvider.addAddon(addon);
-  }
-
-  gWin = Services.ww.openWindow(null,
-                                "chrome://mozapps/content/extensions/selectAddons.xul",
-                                "",
-                                "chrome,centerscreen,dialog,titlebar",
-                                null);
-  waitForFocus(function() {
-    waitForView("select", function() {
-      var row = gWin.document.getElementById("select-rows").firstChild.nextSibling;
-      while (row) {
-        if (!row.id || row.id.indexOf("@tests.mozilla.org") < 0) {
-          // not a test add-on
-          row = row.nextSibling;
-          continue;
-        }
-
-        if (row.id == "test2@tests.mozilla.org" ||
-            row.id == "test4@tests.mozilla.org") {
-          row.disable();
-        }
-        else {
-          row.keep();
-        }
-        row = row.nextSibling;
-      }
-
-      waitForView("confirm", aCallback);
-      EventUtils.synthesizeMouseAtCenter(gWin.document.getElementById("next"), {}, gWin);
-    });
-  }, gWin);
-}
-
-function test() {
-  waitForExplicitFinish();
-
-  run_next_test();
-}
-
-function end_test() {
-  finish();
-}
-
-// Test for disabling
-add_test(function disabling_test() {
-  setupUI(false, false, function() {
-    ok(gWin.document.getElementById("incompatible-list").hidden, "Incompatible list should be hidden");
-    ok(gWin.document.getElementById("update-list").hidden, "Update list should be hidden");
-
-    var list = gWin.document.getElementById("disable-list");
-    ok(!list.hidden, "Disable list should be visible");
-    is(list.childNodes.length, 2, "Should be one add-on getting disabled (plus the header)");
-    is(list.childNodes[1].id, "test2@tests.mozilla.org", "Should be the right add-on ID");
-    is(list.childNodes[1].getAttribute("name"), "Test Add-on 2", "Should be the right add-on name");
-
-    list = gWin.document.getElementById("enable-list");
-    ok(!list.hidden, "Enable list should be visible");
-    is(list.childNodes.length, 2, "Should be one add-on getting disabled (plus the header)");
-    is(list.childNodes[1].id, "test3@tests.mozilla.org", "Should be the right add-on ID");
-    is(list.childNodes[1].getAttribute("name"), "Test Add-on 3", "Should be the right add-on name");
-
-    ok(gWin.document.getElementById("next").hidden, "Next button should be hidden");
-    ok(!gWin.document.getElementById("done").hidden, "Done button should be visible");
-    gWin.close();
-
-    run_next_test();
-  });
-});
-
-// Test for incompatible
-add_test(function incompatible_test() {
-  setupUI(true, false, function() {
-    ok(gWin.document.getElementById("update-list").hidden, "Update list should be hidden");
-    ok(gWin.document.getElementById("disable-list").hidden, "Disable list should be hidden");
-    ok(gWin.document.getElementById("enable-list").hidden, "Enable list should be hidden");
-
-    var list = gWin.document.getElementById("incompatible-list");
-    ok(!list.hidden, "Incompatible list should be visible");
-    is(list.childNodes.length, 3, "Should be two add-ons waiting to be compatible (plus the header)");
-    is(list.childNodes[1].id, "test1@tests.mozilla.org", "Should be the right add-on ID");
-    is(list.childNodes[1].getAttribute("name"), "Test Add-on 1", "Should be the right add-on name");
-    is(list.childNodes[2].id, "test3@tests.mozilla.org", "Should be the right add-on ID");
-    is(list.childNodes[2].getAttribute("name"), "Test Add-on 3", "Should be the right add-on name");
-
-    ok(gWin.document.getElementById("next").hidden, "Next button should be hidden");
-    ok(!gWin.document.getElementById("done").hidden, "Done button should be visible");
-    gWin.close();
-
-    run_next_test();
-  });
-});
-
-// Test for updates
-add_test(function update_test() {
-  setupUI(false, true, function() {
-    ok(gWin.document.getElementById("incompatible-list").hidden, "Incompatible list should be hidden");
-    ok(gWin.document.getElementById("enable-list").hidden, "Enable list should be hidden");
-
-    var list = gWin.document.getElementById("update-list");
-    ok(!list.hidden, "Update list should be visible");
-    is(list.childNodes.length, 3, "Should be two add-ons waiting to be updated (plus the header)");
-    is(list.childNodes[1].id, "test1@tests.mozilla.org", "Should be the right add-on ID");
-    is(list.childNodes[1].getAttribute("name"), "Test Add-on 1", "Should be the right add-on name");
-    is(list.childNodes[2].id, "test3@tests.mozilla.org", "Should be the right add-on ID");
-    is(list.childNodes[2].getAttribute("name"), "Test Add-on 3", "Should be the right add-on name");
-
-    list = gWin.document.getElementById("disable-list");
-    ok(!list.hidden, "Disable list should be visible");
-    is(list.childNodes.length, 2, "Should be one add-on getting disabled (plus the header)");
-    is(list.childNodes[1].id, "test2@tests.mozilla.org", "Should be the right add-on ID");
-    is(list.childNodes[1].getAttribute("name"), "Test Add-on 2", "Should be the right add-on name");
-
-    ok(!gWin.document.getElementById("next").hidden, "Next button should be visible");
-    ok(gWin.document.getElementById("done").hidden, "Done button should be hidden");
-    gWin.close();
-
-    run_next_test();
-  });
-});
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_select_selection.js
+++ /dev/null
@@ -1,268 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Tests the selection part of the post-app-update dialog
-
-var gProvider;
-var gWin;
-
-const PROFILE = AddonManager.SCOPE_PROFILE;
-const USER = AddonManager.SCOPE_USER;
-const APP = AddonManager.SCOPE_APPLICATION;
-const SYSTEM = AddonManager.SCOPE_SYSTEM;
-const DIST = -1;
-
-// The matrix of testcases for the selection part of the UI
-// Note that the isActive flag has the value it had when the previous version
-// of the application ran with this add-on.
-var ADDONS = [
-  //userDisabled   wasAppDisabled isAppDisabled  isActive  hasUpdate  autoUpdate  scope    defaultKeep  position  keepString           disableString
-  [false,          true,          false,         false,    false,     true,       PROFILE, true,        42,       "enabled",           ""],               // 0
-  [false,          true,          false,         false,    true,      true,       PROFILE, true,        43,       "enabled",           ""],               // 1
-  [false,          true,          false,         false,    true,      false,      PROFILE, true,        52,       "unneededupdate",    ""],               // 2
-  [false,          false,         false,         true,     false,     true,       PROFILE, true,        53,       "",                  "disabled"],       // 3
-  [false,          false,         false,         true,     true,      true,       PROFILE, true,        54,       "",                  "disabled"],       // 4
-  [false,          false,         false,         true,     true,      false,      PROFILE, true,        55,       "unneededupdate",    "disabled"],       // 5
-  [false,          true,          true,          false,    false,     true,       PROFILE, true,        56,       "incompatible",      ""],               // 6
-  [false,          true,          true,          false,    true,      true,       PROFILE, true,        57,       "autoupdate",        ""],               // 7
-  [false,          true,          true,          false,    true,      false,      PROFILE, true,        58,       "neededupdate",      ""],               // 8
-  [false,          false,         true,          true,     false,     true,       PROFILE, true,        59,       "incompatible",      "disabled"],       // 9
-  [false,          true,          true,          true,     true,      true,       PROFILE, true,        44,       "autoupdate",        "disabled"],       // 10
-  [false,          true,          true,          true,     true,      false,      PROFILE, true,        45,       "neededupdate",      "disabled"],       // 11
-  [true,           false,         false,         false,    false,     true,       PROFILE, false,       46,       "enabled",           ""],               // 12
-  [true,           false,         false,         false,    true,      true,       PROFILE, false,       47,       "enabled",           ""],               // 13
-  [true,           false,         false,         false,    true,      false,      PROFILE, false,       48,       "unneededupdate",    ""],               // 14
-
-  // userDisabled and isActive cannot be true on startup
-
-  [true,           true,          true,          false,    false,     true,       PROFILE, false,       49,       "incompatible",      ""],               // 15
-  [true,           true,          true,          false,    true,      true,       PROFILE, false,       50,       "autoupdate",        ""],               // 16
-  [true,           true,          true,          false,    true,      false,      PROFILE, false,       51,       "neededupdate",      ""],               // 17
-
-  // userDisabled and isActive cannot be true on startup
-
-  // Being in a different scope should make little difference except no updates are possible so don't exhaustively test each
-  [false,          false,         false,         true,     true,      false,      USER,    false,       0,        "",                  "disabled"],       // 18
-  [true,           true,          false,         false,    true,      false,      USER,    false,       1,        "enabled",           ""],               // 19
-  [false,          true,          true,          true,     true,      false,      USER,    false,       2,        "incompatible",      "disabled"],       // 20
-  [true,           true,          true,          false,    true,      false,      USER,    false,       3,        "incompatible",      ""],               // 21
-  [false,          false,         false,         true,     true,      false,      SYSTEM,  false,       4,        "",                  "disabled"],       // 22
-  [true,           true,          false,         false,    true,      false,      SYSTEM,  false,       5,        "enabled",           ""],               // 23
-  [false,          true,          true,          true,     true,      false,      SYSTEM,  false,       6,        "incompatible",      "disabled"],       // 24
-  [true,           true,          true,          false,    true,      false,      SYSTEM,  false,       7,        "incompatible",      ""],               // 25
-  [false,          false,         false,         true,     true,      false,      APP,     false,       8,        "",                  "disabled"],       // 26
-  [true,           true,          false,         false,    true,      false,      APP,     false,       9,        "enabled",           ""],               // 27
-  [false,          true,          true,          true,     true,      false,      APP,     false,       10,       "incompatible",      "disabled"],       // 28
-  [true,           true,          true,          false,    true,      false,      APP,     false,       11,       "incompatible",      ""],               // 29
-];
-
-function waitForView(aView, aCallback) {
-  var view = gWin.document.getElementById(aView);
-  if (view.parentNode.selectedPanel == view) {
-    aCallback();
-    return;
-  }
-
-  view.addEventListener("ViewChanged", function() {
-    view.removeEventListener("ViewChanged", arguments.callee, false);
-    aCallback();
-  }, false);
-}
-
-function getString(aName) {
-  if (!aName)
-    return "";
-
-  var strings = Services.strings.createBundle("chrome://mozapps/locale/extensions/selectAddons.properties");
-  return strings.GetStringFromName("action." + aName);
-}
-
-function getSourceString(aSource) {
-  if (!aSource)
-    return "";
-
-  var strings = Services.strings.createBundle("chrome://mozapps/locale/extensions/selectAddons.properties");
-  switch (aSource) {
-    case PROFILE:
-      return strings.GetStringFromName("source.profile");
-    case DIST:
-      return strings.GetStringFromName("source.bundled");
-    default:
-      return strings.GetStringFromName("source.other");
-  }
-}
-
-function test() {
-  waitForExplicitFinish();
-
-  gProvider = new MockProvider();
-
-  // Set prefs for Distributed Extension Source tests.
-  Services.prefs.setBoolPref("extensions.installedDistroAddon.test3@tests.mozilla.org", true);
-  Services.prefs.setBoolPref("extensions.installedDistroAddon.test12@tests.mozilla.org", true);
-  Services.prefs.setBoolPref("extensions.installedDistroAddon.test15@tests.mozilla.org", true);
-
-  for (let pos in ADDONS) {
-    let addonItem = ADDONS[pos];
-    let addon = new MockAddon("test" + pos + "@tests.mozilla.org",
-                              "Test Add-on " + pos, "extension");
-    addon.version = "1.0";
-    addon.userDisabled = addonItem[0];
-    addon.appDisabled = addonItem[1];
-    addon.isActive = addonItem[3];
-    addon.applyBackgroundUpdates = addonItem[5] ? AddonManager.AUTOUPDATE_ENABLE
-                                             : AddonManager.AUTOUPDATE_DISABLE;
-    addon.scope = addonItem[6];
-
-    // Remove the upgrade permission from non-profile add-ons
-    if (addon.scope != AddonManager.SCOPE_PROFILE)
-      addon._permissions -= AddonManager.PERM_CAN_UPGRADE;
-
-    addon.findUpdates = function(aListener, aReason, aAppVersion, aPlatformVersion) {
-      addon.appDisabled = addonItem[2];
-      addon.isActive = addon.shouldBeActive;
-
-      if (addonItem[4]) {
-        var newAddon = new MockAddon(this.id, this.name, "extension");
-        newAddon.version = "2.0";
-        var install = new MockInstall(this.name, this.type, newAddon);
-        install.existingAddon = this;
-        aListener.onUpdateAvailable(this, install);
-      }
-
-      aListener.onUpdateFinished(this, AddonManager.UPDATE_STATUS_NO_ERROR);
-    };
-
-    gProvider.addAddon(addon);
-  }
-
-  gWin = Services.ww.openWindow(null,
-                                "chrome://mozapps/content/extensions/selectAddons.xul",
-                                "",
-                                "chrome,centerscreen,dialog,titlebar",
-                                null);
-  waitForFocus(function() {
-    waitForView("select", run_next_test);
-  }, gWin);
-}
-
-function end_test() {
-  gWin.close();
-  finish();
-}
-
-// Minimal test for the checking UI
-add_test(function checking_test() {
-  // By the time we're here the progress bar should be full
-  var progress = gWin.document.getElementById("checking-progress");
-  is(progress.mode, "determined", "Should be a determined progress bar");
-  is(progress.value, progress.max, "Should be at full progress");
-
-  run_next_test();
-});
-
-// Tests that the selection UI behaves correctly
-add_test(function selection_test() {
-  function check_state() {
-    var str = addon[keep.checked ? 9 : 10];
-    var expected = getString(str);
-    var showCheckbox = str == "neededupdate" || str == "unneededupdate";
-    is(action.textContent, expected, "Action message should have the right text");
-    is(!is_hidden(update), showCheckbox, "Checkbox should have the right visibility");
-    is(is_hidden(action), showCheckbox, "Message should have the right visibility");
-    if (showCheckbox)
-      ok(update.checked, "Optional update checkbox should be checked");
-
-    if (keep.checked) {
-      is(row.hasAttribute("active"), !addon[2] || hasUpdate,
-       "Add-on will be active if it isn't appDisabled or an update is available");
-
-      if (showCheckbox) {
-        info("Flipping update checkbox");
-        EventUtils.synthesizeMouseAtCenter(update, { }, gWin);
-        is(row.hasAttribute("active"), str == "unneededupdate",
-           "If the optional update isn't needed then the add-on will still be active");
-
-        info("Flipping update checkbox");
-        EventUtils.synthesizeMouseAtCenter(update, { }, gWin);
-        is(row.hasAttribute("active"), !addon[2] || hasUpdate,
-         "Add-on will be active if it isn't appDisabled or an update is available");
-      }
-    }
-    else {
-      ok(!row.hasAttribute("active"), "Add-on won't be active when not keeping");
-
-      if (showCheckbox) {
-        info("Flipping update checkbox");
-        EventUtils.synthesizeMouseAtCenter(update, { }, gWin);
-        ok(!row.hasAttribute("active"),
-           "Unchecking the update checkbox shouldn't make the add-on active");
-
-        info("Flipping update checkbox");
-        EventUtils.synthesizeMouseAtCenter(update, { }, gWin);
-        ok(!row.hasAttribute("active"),
-           "Re-checking the update checkbox shouldn't make the add-on active");
-      }
-    }
-  }
-
-  is(gWin.document.getElementById("view-deck").selectedPanel.id, "select",
-     "Should be on the right view");
-
-  var pos = 0;
-  var scrollbox = gWin.document.getElementById("select-scrollbox");
-  var scrollBoxObject = scrollbox.boxObject;
-  for (var row = gWin.document.getElementById("select-rows").firstChild; row; row = row.nextSibling) {
-    // Ignore separators but increase the position by a large amount so we
-    // can verify they were in the right place
-    if (row.localName == "separator") {
-      pos += 30;
-      continue;
-    }
-
-    is(row._addon.type, "extension", "Should only be listing extensions");
-
-    // Ignore non-test add-ons that may be present
-    if (row.id.substr(-18) != "@tests.mozilla.org")
-      continue;
-
-    var id = parseInt(row.id.substring(4, row.id.length - 18));
-    var addon = ADDONS[id];
-
-    info("Testing add-on " + id);
-    scrollBoxObject.ensureElementIsVisible(row);
-    var keep = gWin.document.getAnonymousElementByAttribute(row, "anonid", "keep");
-    var action = gWin.document.getAnonymousElementByAttribute(row, "class", "addon-action-message");
-    var update = gWin.document.getAnonymousElementByAttribute(row, "anonid", "update");
-    var source = gWin.document.getAnonymousElementByAttribute(row, "class", "addon-source");
-
-    if (id == 3 || id == 12 || id == 15) {
-      // Distro Installed To Profile
-      is(source.textContent, getSourceString(DIST), "Source message should have the right text for Distributed Addons");
-    } else {
-      is(source.textContent, getSourceString(addon[6]), "Source message should have the right text");
-    }
-
-    // Non-profile add-ons don't appear to have updates since we won't install
-    // them
-    var hasUpdate = addon[4] && addon[6] == PROFILE;
-
-    is(pos, addon[8], "Should have been in the right position");
-    is(keep.checked, addon[7], "Keep checkbox should be in the right state");
-
-    check_state();
-
-    info("Flipping keep");
-    EventUtils.synthesizeMouseAtCenter(keep, { }, gWin);
-    is(keep.checked, !addon[7], "Keep checkbox should be in the right state");
-
-    check_state();
-
-    pos++;
-  }
-
-  is(pos, 60, "Should have seen the right number of add-ons");
-
-  run_next_test();
-});
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_select_update.js
+++ /dev/null
@@ -1,181 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Tests the update part of the post-app-update dialog
-
-var gProvider;
-var gWin;
-
-function waitForView(aView, aCallback) {
-  var view = gWin.document.getElementById(aView);
-  if (view.parentNode.selectedPanel == view) {
-    aCallback();
-    return;
-  }
-
-  view.addEventListener("ViewChanged", function() {
-    view.removeEventListener("ViewChanged", arguments.callee, false);
-    aCallback();
-  }, false);
-}
-
-function waitForClose(aCallback) {
-  gWin.addEventListener("unload", function() {
-    gWin.removeEventListener("unload", arguments.callee, false);
-
-    aCallback();
-  }, false);
-}
-
-/**
- * Creates 4 test add-ons. Two are disabled and two enabled.
- */
-function setupUI(aFailDownloads, aFailInstalls, aCallback) {
-  if (gProvider)
-    gProvider.unregister();
-
-  gProvider = new MockProvider();
-
-  for (var i = 1; i < 5; i++) {
-    var addon = new MockAddon("test" + i + "@tests.mozilla.org",
-                              "Test Add-on " + i, "extension");
-    addon.version = "1.0";
-    addon.userDisabled = (i > 2);
-    addon.appDisabled = false;
-    addon.isActive = !addon.userDisabled && !addon.appDisabled;
-
-    addon.findUpdates = function(aListener, aReason, aAppVersion, aPlatformVersion) {
-      var newAddon = new MockAddon(this.id, this.name, "extension");
-      newAddon.version = "2.0";
-      var install = new MockInstall(this.name, this.type, newAddon);
-      install.existingAddon = this;
-
-      install.install = function() {
-        this.state = AddonManager.STATE_DOWNLOADING;
-        this.callListeners("onDownloadStarted");
-
-        var self = this;
-        executeSoon(function() {
-          if (aFailDownloads) {
-            self.state = AddonManager.STATE_DOWNLOAD_FAILED;
-            self.callListeners("onDownloadFailed");
-            return;
-          }
-
-          self.type = self._type;
-          self.addon = new MockAddon(self.existingAddon.id, self.name, self.type);
-          self.addon.version = self.version;
-          self.addon.pendingOperations = AddonManager.PENDING_INSTALL;
-          self.addon.install = self;
-
-          self.existingAddon.pendingUpgrade = self.addon;
-          self.existingAddon.pendingOperations |= AddonManager.PENDING_UPGRADE;
-
-          self.state = AddonManager.STATE_DOWNLOADED;
-          self.callListeners("onDownloadEnded");
-
-          self.state = AddonManager.STATE_INSTALLING;
-          self.callListeners("onInstallStarted");
-
-          if (aFailInstalls) {
-            self.state = AddonManager.STATE_INSTALL_FAILED;
-            self.callListeners("onInstallFailed");
-            return;
-          }
-
-          self.state = AddonManager.STATE_INSTALLED;
-          self.callListeners("onInstallEnded");
-        });
-      }
-
-      aListener.onUpdateAvailable(this, install);
-
-      aListener.onUpdateFinished(this, AddonManager.UPDATE_STATUS_NO_ERROR);
-    };
-
-    gProvider.addAddon(addon);
-  }
-
-  gWin = Services.ww.openWindow(null,
-                                "chrome://mozapps/content/extensions/selectAddons.xul",
-                                "",
-                                "chrome,centerscreen,dialog,titlebar",
-                                null);
-  waitForFocus(function() {
-    waitForView("select", function() {
-      var row = gWin.document.getElementById("select-rows").firstChild.nextSibling;
-      while (row) {
-        if (!row.id || row.id.indexOf("@tests.mozilla.org") < 0) {
-          // not a test add-on
-          row = row.nextSibling;
-          continue;
-        }
-
-        if (row.id == "test2@tests.mozilla.org" ||
-            row.id == "test4@tests.mozilla.org") {
-          row.disable();
-        }
-        else {
-          row.keep();
-        }
-        row = row.nextSibling;
-      }
-
-      waitForView("confirm", function() {
-        waitForView("update", aCallback);
-        EventUtils.synthesizeMouseAtCenter(gWin.document.getElementById("next"), {}, gWin);
-      });
-      EventUtils.synthesizeMouseAtCenter(gWin.document.getElementById("next"), {}, gWin);
-    });
-  }, gWin);
-}
-
-function test() {
-  waitForExplicitFinish();
-  run_next_test();
-}
-
-function end_test() {
-  finish();
-}
-
-// Test for working updates
-add_test(function working_test() {
-  setupUI(false, false, function() {
-    waitForClose(function() {
-      is(gWin.document.getElementById("update-progress").value, 2, "Should have finished 2 downloads");
-      run_next_test();
-    });
-
-    EventUtils.synthesizeMouseAtCenter(gWin.document.getElementById("next"), {}, gWin);
-  });
-});
-
-// Test for failed updates
-add_test(function working_test() {
-  setupUI(true, false, function() {
-    waitForView("errors", function() {
-      is(gWin.document.getElementById("update-progress").value, 2, "Should have finished 2 downloads");
-      gWin.close();
-
-      run_next_test();
-    });
-
-    EventUtils.synthesizeMouseAtCenter(gWin.document.getElementById("next"), {}, gWin);
-  });
-});
-
-// Test for failed updates
-add_test(function working_test() {
-  setupUI(false, true, function() {
-    waitForView("errors", function() {
-      is(gWin.document.getElementById("update-progress").value, 2, "Should have finished 2 downloads");
-      gWin.close();
-
-      run_next_test();
-    });
-
-    EventUtils.synthesizeMouseAtCenter(gWin.document.getElementById("next"), {}, gWin);
-  });
-});
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug596343.js
+++ /dev/null
@@ -1,77 +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/.
- */
-
-const URI_EXTENSION_SELECT_DIALOG     = "chrome://mozapps/content/extensions/selectAddons.xul";
-const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
-const PREF_EM_SHOW_MISMATCH_UI        = "extensions.showMismatchUI";
-const PREF_SHOWN_SELECTION_UI         = "extensions.shownSelectionUI";
-
-Components.utils.import("resource://testing-common/MockRegistrar.jsm");
-
-const profileDir = gProfD.clone();
-profileDir.append("extensions");
-
-var gExpectedURL = null;
-
-// This will be called to show the any update dialog.
-var WindowWatcher = {
-  openWindow: function(parent, url, name, features, args) {
-    do_check_eq(url, gExpectedURL);
-    gExpectedURL = null;
-  },
-
-  QueryInterface: function(iid) {
-    if (iid.equals(AM_Ci.nsIWindowWatcher)
-     || iid.equals(AM_Ci.nsISupports))
-      return this;
-
-    throw Components.results.NS_ERROR_NO_INTERFACE;
-  }
-}
-
-MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1", WindowWatcher);
-
-// Tests that the selection UI is displayed when upgrading an existing profile
-function run_test() {
-  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
-
-  Services.prefs.setBoolPref(PREF_EM_SHOW_MISMATCH_UI, true);
-
-  var dest = writeInstallRDFForExtension({
-    id: "addon1@tests.mozilla.org",
-    version: "1.0",
-    targetApplications: [{
-      id: "xpcshell@tests.mozilla.org",
-      minVersion: "1",
-      maxVersion: "2"
-    }],
-    name: "Test Addon 1",
-  }, profileDir);
-
-  // For a new profile it should disable showing the selection UI in the future
-  // without showing the selection UI
-  gExpectedURL = URI_EXTENSION_SELECT_DIALOG;
-  startupManager();
-
-  do_check_true(Services.prefs.getBoolPref(PREF_SHOWN_SELECTION_UI));
-  do_check_eq(gExpectedURL, URI_EXTENSION_SELECT_DIALOG);
-
-  // Reset the 'already shown' pref so that we can test that the first upgrade of
-  // an existing profile shows the selection UI
-  Services.prefs.clearUserPref(PREF_SHOWN_SELECTION_UI);
-
-  restartManager("2");
-
-  do_check_true(Services.prefs.getBoolPref(PREF_SHOWN_SELECTION_UI));
-  do_check_eq(gExpectedURL, null);
-
-  // Once we've seen the selection UI once, future upgrades will show the update dialog
-  // but only if this upgrade disabled an add-on
-  gExpectedURL = URI_EXTENSION_UPDATE_DIALOG;
-
-  restartManager("3");
-
-  do_check_eq(gExpectedURL, null);
-}
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -132,17 +132,16 @@ fail-if = os == "android"
 [test_bug567184.js]
 [test_bug569138.js]
 [test_bug570173.js]
 [test_bug576735.js]
 [test_bug587088.js]
 [test_bug594058.js]
 [test_bug595081.js]
 [test_bug595573.js]
-[test_bug596343.js]
 [test_bug596607.js]
 [test_bug616841.js]
 # Bug 676992: test consistently fails on Android
 fail-if = os == "android"
 [test_bug619730.js]
 [test_bug620837.js]
 [test_bug655254.js]
 [test_bug659772.js]
--- a/toolkit/mozapps/installer/upload-files.mk
+++ b/toolkit/mozapps/installer/upload-files.mk
@@ -332,16 +332,37 @@ ROBOCOP_PATH = $(topobjdir)/mobile/andro
 INNER_ROBOCOP_PACKAGE= \
   cp $(GECKO_APP_AP_PATH)/fennec_ids.txt $(ABS_DIST) && \
   $(call RELEASE_SIGN_ANDROID_APK,$(ROBOCOP_PATH)/robocop-debug-unsigned-unaligned.apk,$(ABS_DIST)/robocop.apk)
 endif
 else
 INNER_ROBOCOP_PACKAGE=echo 'Testing is disabled - No Android Robocop for you'
 endif
 
+ifdef MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER
+UPLOAD_EXTRA_FILES += bouncer.apk
+
+bouncer_package=$(ABS_DIST)/bouncer.apk
+
+# Package and release sign the install bouncer APK.  This assumes that the main
+# APK (that is, $(PACKAGE)) has already been produced, and verifies that the
+# bouncer APK and the main APK define the same set of permissions.  The
+# intention is to avoid permission-related surprises when bouncing to the
+# installation process in the Play Store.  N.b.: sort -u is Posix and saves
+# invoking uniq separately.  diff -u is *not* Posix, so we only add -c.
+INNER_INSTALL_BOUNCER_PACKAGE=\
+  $(call RELEASE_SIGN_ANDROID_APK,$(topobjdir)/mobile/android/bouncer/bouncer-unsigned-unaligned.apk,$(bouncer_package)) && \
+  ($(AAPT) dump permissions $(PACKAGE) | sort -u > $(PACKAGE).permissions && \
+   $(AAPT) dump permissions $(bouncer_package) | sort -u > $(bouncer_package).permissions && \
+   diff -c $(PACKAGE).permissions $(bouncer_package).permissions || \
+   (echo "*** Error: The permissions of the bouncer package differ from the permissions of the main package.  Ensure the bouncer and main package Android manifests agree, rebuild mobile/android, and re-package." && exit 1))
+else
+INNER_INSTALL_BOUNCER_PACKAGE=echo 'Install bouncer is disabled - No trampolines for you'
+endif # MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER
+
 # Create geckoview_library/geckoview_{assets,library}.zip for third-party GeckoView consumers.
 ifdef NIGHTLY_BUILD
 ifndef MOZ_DISABLE_GECKOVIEW
 INNER_MAKE_GECKOVIEW_LIBRARY= \
   $(MAKE) -C ../mobile/android/geckoview_library package
 else
 INNER_MAKE_GECKOVIEW_LIBRARY=echo 'GeckoView library packaging is disabled'
 endif
@@ -473,16 +494,17 @@ INNER_MAKE_PACKAGE	= \
   $(INNER_SZIP_LIBRARIES) && \
   make -C $(GECKO_APP_AP_PATH) gecko-nodeps.ap_ && \
   cp $(GECKO_APP_AP_PATH)/gecko-nodeps.ap_ $(ABS_DIST)/gecko.ap_ && \
   ( (test ! -f $(GECKO_APP_AP_PATH)/R.txt && echo "*** Warning: The R.txt that is being packaged might not agree with the R.txt that was built. This is normal during l10n repacks.") || \
     diff $(GECKO_APP_AP_PATH)/R.txt $(GECKO_APP_AP_PATH)/gecko-nodeps/R.txt >/dev/null || \
     (echo "*** Error: The R.txt that was built and the R.txt that is being packaged are not the same. Rebuild mobile/android/base and re-package." && exit 1)) && \
   $(INNER_MAKE_APK) && \
   $(INNER_ROBOCOP_PACKAGE) && \
+  $(INNER_INSTALL_BOUNCER_PACKAGE) && \
   $(INNER_MAKE_GECKOLIBS_AAR) && \
   $(INNER_MAKE_GECKOVIEW_LIBRARY)
 endif
 
 ifeq ($(MOZ_BUILD_APP),mobile/android/b2gdroid)
 INNER_MAKE_PACKAGE	= \
   $(INNER_SZIP_LIBRARIES) && \
   cp $(topobjdir)/mobile/android/b2gdroid/app/classes.dex $(ABS_DIST)/classes.dex && \
deleted file mode 100644
--- a/toolkit/themes/linux/mozapps/extensions/selectAddons.css
+++ /dev/null
@@ -1,162 +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/. */
-
-#view-deck {
-  background: Window;
-}
-
-.heading {
-  font-size: 270%;
-  text-align: center;
-  margin: 0 100px;
-}
-
-.progress {
-  margin: 10px 128px;
-}
-
-.progress-label,
-#errors-description {
-  text-align: center;
-  margin: 0 10px;
-}
-
-#checking-heading,
-#update-heading,
-#errors-heading {
-  margin-top: 90px;
-}
-
-#select-heading,
-#confirm-heading {
-  margin-top: 10px;
-  margin-bottom: 10px;
-  text-align: center;
-}
-
-#select-description,
-#confirm-description {
-  margin: 10px;
-}
-
-#select-list {
-  border-top: 1px solid WindowFrame;
-  background-color: Window;
-}
-
-#select-grid column {
-  -moz-box-align: center;
-}
-
-#select-grid row {
-  -moz-box-align: stretch;
-}
-
-#select-grid row:nth-of-type(odd) {
-  background-color: -moz-oddtreerow;
-}
-
-#select-grid label,
-#select-grid checkbox {
-  margin-top: 0;
-  margin-bottom: 0;
-}
-
-.select-cell {
-  -moz-box-align: center;
-  -moz-box-pack: start;
-}
-
-#select-header .select-cell {
-  -moz-appearance: treeheadercell;
-  box-sizing: border-box;
-}
-
-.select-keep {
-  -moz-box-pack: center;
-}
-
-.select-keep .checkbox-label-box {
-  display: none;
-}
-
-.select-keep .addon-keep-checkbox:-moz-focusring {
-  outline: 1px dotted ThreeDDarkShadow;
-}
-
-.select-icon {
-  width: 20px;
-}
-
-#select-grid separator {
-  display: none;
-}
-
-.addon-name,
-.addon-action-message,
-.addon-action-update {
-  box-sizing: border-box;
-  margin: 0;
-  padding-top: 1px;
-  padding-bottom: 2px;
-  -moz-padding-start: 6px;
-  -moz-padding-end: 5px;
-}
-
-.addon:not([active]) .addon-name,
-.addon:not([active]) .addon-action-message,
-.addon:not([active]) .addon-action-update {
-  color: GrayText;
-}
-
-.addon-icon {
-  height: 16px;
-  width: 16px;
-  margin: 2px;
-  list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric-16.png");
-}
-
-.addon-icon[type="theme"] {
-  list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric-16.png");
-}
-
-.addon-icon[type="plugin"] {
-  list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric-16.png");
-}
-
-.addon-icon[type="dictionary"] {
-  list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric-16.png");
-}
-
-.action-list {
-  margin-top: 10px;
-  -moz-margin-start: 5em;
-}
-
-.action-header {
-  margin-bottom: 10px;
-}
-
-#confirm .addon {
-  -moz-margin-start: 3em;
-  -moz-box-align: center;
-}
-
-.addon:not([active]) .addon-icon,
-#disable-list .addon-icon,
-#incompatible-list .addon-icon {
-  filter: grayscale(1);
-}
-
-#footer {
-  padding: 15px 12px;
-  border-top: 2px solid;
-  -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow;
-}
-
-.progress-label,
-#footer-label {
-  font-style: italic;
-  color: GrayText;
-}
--- a/toolkit/themes/linux/mozapps/jar.mn
+++ b/toolkit/themes/linux/mozapps/jar.mn
@@ -15,17 +15,16 @@ toolkit.jar:
   skin/classic/mozapps/extensions/category-available.png   (extensions/category-available.png)
   skin/classic/mozapps/extensions/extensionGeneric-16.png  (extensions/extensionGeneric-16.png)
   skin/classic/mozapps/extensions/dictionaryGeneric.png    (extensions/dictionaryGeneric.png)
   skin/classic/mozapps/extensions/dictionaryGeneric-16.png (extensions/dictionaryGeneric-16.png)
   skin/classic/mozapps/extensions/themeGeneric.png         (extensions/themeGeneric.png)
   skin/classic/mozapps/extensions/themeGeneric-16.png      (extensions/themeGeneric-16.png)
   skin/classic/mozapps/extensions/localeGeneric.png        (extensions/localeGeneric.png)
 * skin/classic/mozapps/extensions/newaddon.css             (extensions/newaddon.css)
-  skin/classic/mozapps/extensions/selectAddons.css         (extensions/selectAddons.css)
   skin/classic/mozapps/extensions/heart.png                (extensions/heart.png)
   skin/classic/mozapps/passwordmgr/key-16.png              (passwordmgr/key-16.png)
   skin/classic/mozapps/passwordmgr/key-64.png              (passwordmgr/key-64.png)
   skin/classic/mozapps/plugins/pluginGeneric.png           (plugins/pluginGeneric.png)
   skin/classic/mozapps/plugins/pluginBlocked.png           (plugins/pluginBlocked.png)
   skin/classic/mozapps/plugins/pluginGeneric-16.png        (plugins/pluginGeneric-16.png)
   skin/classic/mozapps/profile/profileicon.png             (profile/profileicon.png)
   skin/classic/mozapps/update/updates.css                  (update/updates.css)
deleted file mode 100644
--- a/toolkit/themes/osx/mozapps/extensions/selectAddons.css
+++ /dev/null
@@ -1,163 +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/. */
-
-%include ../../global/shared.inc
-
-.heading {
-  font-size: 270%;
-  text-align: center;
-  margin: 0 120px;
-}
-
-.progress {
-  margin: 10px 128px;
-}
-
-.progress-label,
-#errors-description {
-  text-align: center;
-  margin: 0 10px;
-}
-
-#checking-heading,
-#update-heading,
-#errors-heading {
-  margin-top: 90px;
-}
-
-#select-heading,
-#confirm-heading {
-  margin-top: 10px;
-  margin-bottom: 10px;
-  text-align: center;
-}
-
-#select-description,
-#confirm-description {
-  margin: 10px;
-}
-
-#select-list {
-  border: 1px solid WindowFrame;
-  background-color: Window;
-  margin: 10px;
-}
-
-#select-grid column {
-  -moz-box-align: center;
-}
-
-#select-grid row {
-  -moz-box-align: stretch;
-}
-
-#select-grid row:nth-of-type(odd) {
-  background-color: -moz-oddtreerow;
-}
-
-#select-grid label,
-#select-grid checkbox {
-  margin-top: 0;
-  margin-bottom: 0;
-}
-
-.select-cell {
-  -moz-box-align: center;
-  -moz-box-pack: start;
-  box-sizing: border-box;
-}
-
-#select-header {
-  background-color: Window !important;
-}
-
-#select-header .select-cell {
-  -moz-appearance: treeheadercell;
-  border: 2px solid;
-  -moz-border-top-colors: ThreeDHighlight ThreeDLightShadow;
-  -moz-border-right-colors: ThreeDDarkShadow ThreeDShadow;
-  -moz-border-bottom-colors: ThreeDDarkShadow ThreeDShadow;
-  -moz-border-left-colors: ThreeDHighlight ThreeDLightShadow;
-  background-color: -moz-Dialog;
-  color: -moz-DialogText;
-}
-
-.select-keep {
-  -moz-box-pack: center;
-}
-
-.select-icon {
-  width: 20px;
-}
-
-#select-grid separator {
-  display: none;
-}
-
-.addon-name,
-.addon-action-message,
-.addon-action-update {
-  box-sizing: border-box;
-  margin: 0;
-  padding: 2px 6px;
-}
-
-.addon:not([active]) .addon-name,
-.addon:not([active]) .addon-action-message,
-.addon:not([active]) .addon-action-update {
-  color: GrayText;
-}
-
-.addon-icon {
-  height: 16px;
-  width: 16px;
-  margin: 2px;
-  list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric-16.png");
-}
-
-.addon-icon[type="theme"] {
-  list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric-16.png");
-}
-
-.addon-icon[type="plugin"] {
-  list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric-16.png");
-}
-
-.addon-icon[type="dictionary"] {
-  list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric-16.png");
-}
-
-.action-list {
-  margin-top: 10px;
-  -moz-margin-start: 5em;
-}
-
-.action-header {
-  margin-bottom: 10px;
-}
-
-#confirm .addon {
-  -moz-margin-start: 3em;
-  -moz-box-align: center;
-}
-
-.addon:not([active]) .addon-icon,
-#disable-list .addon-icon,
-#incompatible-list .addon-icon {
-  filter: grayscale(1);
-}
-
-#footer {
-  padding: 15px 12px;
-  -moz-appearance: statusbar;
-  -moz-window-dragging: drag;
-}
-
-button {
-  -moz-appearance: toolbarbutton;
-  min-height: 22px;
-  margin: 0 6px;
-  padding: 0;
-  text-shadow: @loweredShadow@;
-}
--- a/toolkit/themes/osx/mozapps/jar.mn
+++ b/toolkit/themes/osx/mozapps/jar.mn
@@ -27,17 +27,16 @@ toolkit.jar:
   skin/classic/mozapps/extensions/rating-won.png                  (extensions/rating-won.png)
   skin/classic/mozapps/extensions/rating-not-won.png              (extensions/rating-not-won.png)
   skin/classic/mozapps/extensions/cancel.png                      (extensions/cancel.png)
   skin/classic/mozapps/extensions/toolbarbutton-dropmarker.png    (extensions/toolbarbutton-dropmarker.png)
   skin/classic/mozapps/extensions/heart.png                       (extensions/heart.png)
   skin/classic/mozapps/extensions/search.png                      (extensions/search.png)
   skin/classic/mozapps/extensions/about.css                       (extensions/about.css)
 * skin/classic/mozapps/extensions/extensions.css                  (extensions/extensions.css)
-* skin/classic/mozapps/extensions/selectAddons.css                (extensions/selectAddons.css)
   skin/classic/mozapps/extensions/update.css                      (extensions/update.css)
   skin/classic/mozapps/extensions/eula.css                        (extensions/eula.css)
   skin/classic/mozapps/extensions/blocklist.css                   (extensions/blocklist.css)
 * skin/classic/mozapps/extensions/newaddon.css                    (extensions/newaddon.css)
   skin/classic/mozapps/passwordmgr/key.png                        (passwordmgr/key.png)
   skin/classic/mozapps/passwordmgr/key-16.png                     (passwordmgr/key-16.png)
   skin/classic/mozapps/passwordmgr/key-16@2x.png                  (passwordmgr/key-16@2x.png)
   skin/classic/mozapps/passwordmgr/key-64.png                     (passwordmgr/key-64.png)
deleted file mode 100644
--- a/toolkit/themes/windows/mozapps/extensions/selectAddons.css
+++ /dev/null
@@ -1,187 +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/. */
-
-#view-deck {
-  background: Window;
-}
-
-.heading {
-  font-size: 270%;
-  text-align: center;
-  margin: 0 120px;
-}
-
-.progress {
-  margin: 10px 128px;
-}
-
-.progress-label,
-#errors-description {
-  text-align: center;
-  margin: 0 10px;
-}
-
-#checking-heading,
-#update-heading,
-#errors-heading {
-  margin-top: 90px;
-}
-
-#select-heading,
-#confirm-heading {
-  margin-top: 10px;
-  margin-bottom: 10px;
-  text-align: center;
-}
-
-#select-description,
-#confirm-description {
-  margin: 10px;
-}
-
-#select-list {
-  border-top: 1px solid #D6E5F5;
-  background-color: Window;
-}
-
-#select-grid column {
-  -moz-box-align: center;
-}
-
-#select-grid row {
-  -moz-box-align: stretch;
-}
-
-#select-grid label {
-  margin-top: 0;
-  margin-bottom: 0;
-}
-
-.select-cell {
-  -moz-box-align: center;
-  -moz-box-pack: start;
-}
-
-#select-header .select-cell {
-  box-sizing: border-box;
-}
-
-#select-header .select-keep,
-#select-header .select-icon,
-#select-header .select-name,
-#select-header .select-action {
-  background-image: linear-gradient(#D6E5F5 0%, Window 100%);
-  background-size: 1px 100%;
-  background-position: right;
-  background-repeat: no-repeat;
-}
-
-.select-keep {
-  -moz-box-pack: center;
-}
-
-.select-icon {
-  width: 20px;
-}
-
-.select-keep .addon-keep-checkbox {
-  margin: 0;
-  padding: 0;
-  width: 13px;
-}
-
-.select-keep .addon-keep-checkbox:-moz-focusring {
-  outline: 1px dotted ThreeDDarkShadow;
-}
-
-.select-keep .checkbox-label-box {
-  display: none;
-}
-
-.addon-name,
-.addon-action-message,
-.addon-action-update {
-  box-sizing: border-box;
-  margin: 0;
-  padding-top: 1px;
-  padding-bottom: 2px;
-  -moz-padding-start: 6px;
-  -moz-padding-end: 5px;
-}
-
-#select-grid separator {
-  border-top: 1px solid #D6E5F5;
-  height: 0;
-  margin-top: 0.4em;
-  margin-bottom: 0.4em;
-}
-
-.addon:not([active]) .addon-name,
-.addon:not([active]) .addon-action-message,
-.addon:not([active]) .addon-action-update {
-  color: GrayText;
-}
-
-.addon-icon {
-  height: 16px;
-  width: 16px;
-  list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric-16.png");
-}
-
-.addon-icon[type="theme"] {
-  list-style-image: url("chrome://mozapps/skin/extensions/themeGeneric-16.png");
-}
-
-.addon-icon[type="plugin"] {
-  list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric-16.png");
-}
-
-.addon-icon[type="dictionary"] {
-  list-style-image: url("chrome://mozapps/skin/extensions/dictionaryGeneric-16.png");
-}
-
-.action-list {
-  margin-top: 10px;
-  -moz-margin-start: 5em;
-}
-
-.action-header {
-  margin-bottom: 10px;
-}
-
-#confirm .addon {
-  -moz-margin-start: 3em;
-  -moz-box-align: center;
-}
-
-.addon:not([active]) .addon-icon,
-#disable-list .addon-icon,
-#incompatible-list .addon-icon {
-  filter: grayscale(1);
-}
-
-#footer {
-  padding: 15px 12px;
-}
-
-.progress-label,
-#footer-label {
-  color: GrayText;
-}
-
-%ifdef XP_WIN
-@media not all and (-moz-os-version: windows-xp) {
-  .progress-label,
-  #footer-label {
-    font-style: italic;
-  }
-
-  @media (-moz-windows-default-theme) {
-    #footer {
-      background-color: #f1f5fb;
-      box-shadow: 0px 1px 2px rgb(204,214,234) inset;
-    }
-  }
-}
-%endif
--- a/toolkit/themes/windows/mozapps/jar.mn
+++ b/toolkit/themes/windows/mozapps/jar.mn
@@ -2,17 +2,16 @@
 # 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/.
 
 toolkit.jar:
 #include ../../shared/mozapps.inc.mn
   skin/classic/mozapps/downloads/downloadIcon.png            (downloads/downloadIcon.png)
   skin/classic/mozapps/downloads/downloads.css               (downloads/downloads.css)
 * skin/classic/mozapps/extensions/extensions.css             (extensions/extensions.css)
-* skin/classic/mozapps/extensions/selectAddons.css           (extensions/selectAddons.css)
   skin/classic/mozapps/extensions/category-search.png        (extensions/category-search.png)
   skin/classic/mozapps/extensions/category-discover.png      (extensions/category-discover.png)
   skin/classic/mozapps/extensions/category-plugins.png       (extensions/category-plugins.png)
   skin/classic/mozapps/extensions/category-service.png       (extensions/category-service.png)
   skin/classic/mozapps/extensions/category-recent.png        (extensions/category-recent.png)
   skin/classic/mozapps/extensions/category-available.png     (extensions/category-available.png)
   skin/classic/mozapps/extensions/extensionGeneric-16.png    (extensions/extensionGeneric-16.png)
   skin/classic/mozapps/extensions/themeGeneric.png           (extensions/themeGeneric.png)