merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 27 Jan 2017 11:16:17 +0100
changeset 380407 3703867c611a64c40472658a56912eb35670fe2e
parent 380406 25c0a38d2d7ed50ed3c1d419f70c01595c481ae7 (current diff)
parent 380342 8dbe8993536645eceeeaf8cb6fc53c03602d7c84 (diff)
child 380408 2622687b9a9c18c2bc4a6505b9cd923893a3655a
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone54.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 mozilla-central to mozilla-inbound
browser/components/extensions/ext-browserAction.js
browser/components/extensions/ext-commands.js
browser/components/extensions/ext-pageAction.js
devtools/client/netmonitor/test/browser_net_details-no-duplicated-content.js
dom/media/test/test_gmp_playback.html
old-configure.in
toolkit/components/passwordmgr/LoginManagerParent.jsm
--- a/browser/components/extensions/ext-bookmarks.js
+++ b/browser/components/extensions/ext-bookmarks.js
@@ -315,56 +315,56 @@ extensions.registerSchemaAPI("bookmarks"
             .catch(error => Promise.reject({message: error.message}));
         } catch (e) {
           return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
         }
       },
 
       onCreated: new SingletonEventManager(context, "bookmarks.onCreated", fire => {
         let listener = (event, bookmark) => {
-          context.runSafe(fire, bookmark.id, bookmark);
+          fire.sync(bookmark.id, bookmark);
         };
 
         observer.on("created", listener);
         incrementListeners();
         return () => {
           observer.off("created", listener);
           decrementListeners();
         };
       }).api(),
 
       onRemoved: new SingletonEventManager(context, "bookmarks.onRemoved", fire => {
         let listener = (event, data) => {
-          context.runSafe(fire, data.guid, data.info);
+          fire.sync(data.guid, data.info);
         };
 
         observer.on("removed", listener);
         incrementListeners();
         return () => {
           observer.off("removed", listener);
           decrementListeners();
         };
       }).api(),
 
       onChanged: new SingletonEventManager(context, "bookmarks.onChanged", fire => {
         let listener = (event, data) => {
-          context.runSafe(fire, data.guid, data.info);
+          fire.sync(data.guid, data.info);
         };
 
         observer.on("changed", listener);
         incrementListeners();
         return () => {
           observer.off("changed", listener);
           decrementListeners();
         };
       }).api(),
 
       onMoved: new SingletonEventManager(context, "bookmarks.onMoved", fire => {
         let listener = (event, data) => {
-          context.runSafe(fire, data.guid, data.info);
+          fire.sync(data.guid, data.info);
         };
 
         observer.on("moved", listener);
         incrementListeners();
         return () => {
           observer.off("moved", listener);
           decrementListeners();
         };
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -13,17 +13,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "@mozilla.org/inspector/dom-utils;1",
                                    "inIDOMUtils");
 
 Cu.import("resource://devtools/shared/event-emitter.js");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 var {
-  EventManager,
+  SingletonEventManager,
   IconDetails,
 } = ExtensionUtils;
 
 const POPUP_PRELOAD_TIMEOUT_MS = 200;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 function isAncestorOrSelf(target, node) {
@@ -441,20 +441,20 @@ extensions.on("shutdown", (type, extensi
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 extensions.registerSchemaAPI("browserAction", "addon_parent", context => {
   let {extension} = context;
   return {
     browserAction: {
-      onClicked: new EventManager(context, "browserAction.onClicked", fire => {
+      onClicked: new SingletonEventManager(context, "browserAction.onClicked", fire => {
         let listener = () => {
           let tab = TabManager.activeTab;
-          fire(TabManager.convert(extension, tab));
+          fire.async(TabManager.convert(extension, tab));
         };
         BrowserAction.for(extension).on("click", listener);
         return () => {
           BrowserAction.for(extension).off("click", listener);
         };
       }).api(),
 
       enable: function(tabId) {
--- a/browser/components/extensions/ext-c-omnibox.js
+++ b/browser/components/extensions/ext-c-omnibox.js
@@ -1,25 +1,24 @@
 /* -*- 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 {
-  runSafeSyncWithoutClone,
   SingletonEventManager,
 } = ExtensionUtils;
 
 extensions.registerSchemaAPI("omnibox", "addon_child", context => {
   return {
     omnibox: {
       onInputChanged: new SingletonEventManager(context, "omnibox.onInputChanged", fire => {
         let listener = (text, id) => {
-          runSafeSyncWithoutClone(fire, text, suggestions => {
+          fire.asyncWithoutClone(text, suggestions => {
             // TODO: Switch to using callParentFunctionNoReturn once bug 1314903 is fixed.
             context.childManager.callParentAsyncFunction("omnibox_internal.addSuggestions", [
               id,
               suggestions,
             ]);
           });
         };
         context.childManager.getParentEvent("omnibox_internal.onInputChanged").addListener(listener);
--- a/browser/components/extensions/ext-commands.js
+++ b/browser/components/extensions/ext-commands.js
@@ -1,17 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 Cu.import("resource://devtools/shared/event-emitter.js");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
-  EventManager,
+  SingletonEventManager,
   PlatformInfo,
 } = ExtensionUtils;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 // WeakMap[Extension -> CommandList]
 var commandsMap = new WeakMap();
 
@@ -240,19 +240,19 @@ extensions.registerSchemaAPI("commands",
         return Promise.resolve(Array.from(commands, ([name, command]) => {
           return ({
             name,
             description: command.description,
             shortcut: command.shortcut,
           });
         }));
       },
-      onCommand: new EventManager(context, "commands.onCommand", fire => {
+      onCommand: new SingletonEventManager(context, "commands.onCommand", fire => {
         let listener = (eventName, commandName) => {
-          fire(commandName);
+          fire.async(commandName);
         };
         commandsMap.get(extension).on("command", listener);
         return () => {
           commandsMap.get(extension).off("command", listener);
         };
       }).api(),
     },
   };
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -3,19 +3,19 @@
 "use strict";
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/MatchPattern.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var {
-  EventManager,
   ExtensionError,
   IconDetails,
+  SingletonEventManager,
 } = ExtensionUtils;
 
 const ACTION_MENU_TOP_LEVEL_LIMIT = 6;
 
 // Map[Extension -> Map[ID -> MenuItem]]
 // Note: we want to enumerate all the menu items so
 // this cannot be a weak map.
 var gContextMenuMap = new Map();
@@ -620,19 +620,19 @@ extensions.registerSchemaAPI("contextMen
 
       removeAll: function() {
         let root = gRootItems.get(extension);
         if (root) {
           root.remove();
         }
       },
 
-      onClicked: new EventManager(context, "contextMenus.onClicked", fire => {
+      onClicked: new SingletonEventManager(context, "contextMenus.onClicked", fire => {
         let listener = (event, info, tab) => {
-          fire(info, tab);
+          fire.async(info, tab);
         };
 
         extension.on("webext-contextmenu-menuitem-click", listener);
         return () => {
           extension.off("webext-contextmenu-menuitem-click", listener);
         };
       }).api(),
     },
--- a/browser/components/extensions/ext-history.js
+++ b/browser/components/extensions/ext-history.js
@@ -217,28 +217,28 @@ extensions.registerSchemaAPI("history", 
         historyQuery.uri = NetUtil.newURI(url);
         let queryResult = PlacesUtils.history.executeQuery(historyQuery, options).root;
         let results = convertNavHistoryContainerResultNode(queryResult, convertNodeToVisitItem);
         return Promise.resolve(results);
       },
 
       onVisited: new SingletonEventManager(context, "history.onVisited", fire => {
         let listener = (event, data) => {
-          context.runSafe(fire, data);
+          fire.sync(data);
         };
 
         getObserver().on("visited", listener);
         return () => {
           getObserver().off("visited", listener);
         };
       }).api(),
 
       onVisitRemoved: new SingletonEventManager(context, "history.onVisitRemoved", fire => {
         let listener = (event, data) => {
-          context.runSafe(fire, data);
+          fire.sync(data);
         };
 
         getObserver().on("visitRemoved", listener);
         return () => {
           getObserver().off("visitRemoved", listener);
         };
       }).api(),
     },
--- a/browser/components/extensions/ext-omnibox.js
+++ b/browser/components/extensions/ext-omnibox.js
@@ -45,37 +45,37 @@ extensions.registerSchemaAPI("omnibox", 
           ExtensionSearchHandler.setDefaultSuggestion(keyword, suggestion);
         } catch (e) {
           return Promise.reject(e.message);
         }
       },
 
       onInputStarted: new SingletonEventManager(context, "omnibox.onInputStarted", fire => {
         let listener = (eventName) => {
-          fire();
+          fire.sync();
         };
         extension.on(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
         return () => {
           extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
         };
       }).api(),
 
       onInputCancelled: new SingletonEventManager(context, "omnibox.onInputCancelled", fire => {
         let listener = (eventName) => {
-          fire();
+          fire.sync();
         };
         extension.on(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
         return () => {
           extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
         };
       }).api(),
 
       onInputEntered: new SingletonEventManager(context, "omnibox.onInputEntered", fire => {
         let listener = (eventName, text, disposition) => {
-          fire(text, disposition);
+          fire.sync(text, disposition);
         };
         extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
         return () => {
           extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
         };
       }).api(),
     },
 
@@ -87,17 +87,17 @@ extensions.registerSchemaAPI("omnibox", 
         } catch (e) {
           // Silently fail because the extension developer can not know for sure if the user
           // has already invalidated the callback when asynchronously providing suggestions.
         }
       },
 
       onInputChanged: new SingletonEventManager(context, "omnibox_internal.onInputChanged", fire => {
         let listener = (eventName, text, id) => {
-          fire(text, id);
+          fire.sync(text, id);
         };
         extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
         return () => {
           extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
         };
       }).api(),
     },
   };
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -1,16 +1,16 @@
 /* -*- 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/Task.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
-  EventManager,
+  SingletonEventManager,
   IconDetails,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> PageAction]
 var pageActionMap = new WeakMap();
 
 // Handles URL bar icons, including the |page_action| manifest entry
 // and associated API.
@@ -239,19 +239,19 @@ PageAction.for = extension => {
 };
 
 global.pageActionFor = PageAction.for;
 
 extensions.registerSchemaAPI("pageAction", "addon_parent", context => {
   let {extension} = context;
   return {
     pageAction: {
-      onClicked: new EventManager(context, "pageAction.onClicked", fire => {
+      onClicked: new SingletonEventManager(context, "pageAction.onClicked", fire => {
         let listener = (evt, tab) => {
-          fire(TabManager.convert(extension, tab));
+          fire.async(TabManager.convert(extension, tab));
         };
         let pageAction = PageAction.for(extension);
 
         pageAction.on("click", listener);
         return () => {
           pageAction.off("click", listener);
         };
       }).api(),
--- a/browser/components/extensions/ext-sessions.js
+++ b/browser/components/extensions/ext-sessions.js
@@ -89,17 +89,17 @@ extensions.registerSchemaAPI("sessions",
           closedId = recentlyClosedTabs[0].closedId;
           session = SessionStore.undoCloseById(closedId);
         }
         return createSession(session, extension, closedId);
       },
 
       onChanged: new SingletonEventManager(context, "sessions.onChanged", fire => {
         let observer = () => {
-          context.runSafe(fire);
+          fire.async();
         };
 
         Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED, false);
         return () => {
           Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
         };
       }).api(),
     },
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -13,17 +13,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
-  EventManager,
+  SingletonEventManager,
   ignoreEvent,
 } = ExtensionUtils;
 
 // This function is pretty tightly tied to Extension.jsm.
 // Its job is to fill in the |tab| property of the sender.
 function getSender(extension, target, sender) {
   let tabId;
   if ("tabId" in sender) {
@@ -278,22 +278,22 @@ extensions.on("startup", () => {
 extensions.registerSchemaAPI("tabs", "addon_parent", context => {
   let {extension} = context;
   let self = {
     tabs: {
       onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
         let tabId = TabManager.getId(tab);
         let windowId = WindowManager.getId(tab.ownerGlobal);
-        fire({tabId, windowId});
+        fire.async({tabId, windowId});
       }).api(),
 
-      onCreated: new EventManager(context, "tabs.onCreated", fire => {
+      onCreated: new SingletonEventManager(context, "tabs.onCreated", fire => {
         let listener = (eventName, event) => {
-          fire(TabManager.convert(extension, event.tab));
+          fire.async(TabManager.convert(extension, event.tab));
         };
 
         tabListener.on("tab-created", listener);
         return () => {
           tabListener.off("tab-created", listener);
         };
       }).api(),
 
@@ -302,55 +302,55 @@ extensions.registerSchemaAPI("tabs", "ad
        * essentially acts an alias for self.tabs.onActivated but returns
        * the tabId in an array to match the API.
        * @see  https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
       */
       onHighlighted: new WindowEventManager(context, "tabs.onHighlighted", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
         let tabIds = [TabManager.getId(tab)];
         let windowId = WindowManager.getId(tab.ownerGlobal);
-        fire({tabIds, windowId});
+        fire.async({tabIds, windowId});
       }).api(),
 
-      onAttached: new EventManager(context, "tabs.onAttached", fire => {
+      onAttached: new SingletonEventManager(context, "tabs.onAttached", fire => {
         let listener = (eventName, event) => {
-          fire(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
+          fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
         };
 
         tabListener.on("tab-attached", listener);
         return () => {
           tabListener.off("tab-attached", listener);
         };
       }).api(),
 
-      onDetached: new EventManager(context, "tabs.onDetached", fire => {
+      onDetached: new SingletonEventManager(context, "tabs.onDetached", fire => {
         let listener = (eventName, event) => {
-          fire(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
+          fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
         };
 
         tabListener.on("tab-detached", listener);
         return () => {
           tabListener.off("tab-detached", listener);
         };
       }).api(),
 
-      onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
+      onRemoved: new SingletonEventManager(context, "tabs.onRemoved", fire => {
         let listener = (eventName, event) => {
-          fire(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
+          fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
         };
 
         tabListener.on("tab-removed", listener);
         return () => {
           tabListener.off("tab-removed", listener);
         };
       }).api(),
 
       onReplaced: ignoreEvent(context, "tabs.onReplaced"),
 
-      onMoved: new EventManager(context, "tabs.onMoved", fire => {
+      onMoved: new SingletonEventManager(context, "tabs.onMoved", fire => {
         // There are certain circumstances where we need to ignore a move event.
         //
         // Namely, the first time the tab is moved after it's created, we need
         // to report the final position as the initial position in the tab's
         // onAttached or onCreated event. This is because most tabs are inserted
         // in a temporary location and then moved after the TabOpen event fires,
         // which generates a TabOpen event followed by a TabMove event, which
         // does not match the contract of our API.
@@ -368,32 +368,32 @@ extensions.registerSchemaAPI("tabs", "ad
         let moveListener = event => {
           let tab = event.originalTarget;
 
           if (ignoreNextMove.has(tab)) {
             ignoreNextMove.delete(tab);
             return;
           }
 
-          fire(TabManager.getId(tab), {
+          fire.async(TabManager.getId(tab), {
             windowId: WindowManager.getId(tab.ownerGlobal),
             fromIndex: event.detail,
             toIndex: tab._tPos,
           });
         };
 
         AllWindowEvents.addListener("TabMove", moveListener);
         AllWindowEvents.addListener("TabOpen", openListener);
         return () => {
           AllWindowEvents.removeListener("TabMove", moveListener);
           AllWindowEvents.removeListener("TabOpen", openListener);
         };
       }).api(),
 
-      onUpdated: new EventManager(context, "tabs.onUpdated", fire => {
+      onUpdated: new SingletonEventManager(context, "tabs.onUpdated", fire => {
         const restricted = ["url", "favIconUrl", "title"];
 
         function sanitize(extension, changeInfo) {
           let result = {};
           let nonempty = false;
           for (let prop in changeInfo) {
             if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
               nonempty = true;
@@ -405,17 +405,17 @@ extensions.registerSchemaAPI("tabs", "ad
 
         let fireForBrowser = (browser, changed) => {
           let [needed, changeInfo] = sanitize(extension, changed);
           if (needed) {
             let gBrowser = browser.ownerGlobal.gBrowser;
             let tabElem = gBrowser.getTabForBrowser(browser);
 
             let tab = TabManager.convert(extension, tabElem);
-            fire(tab.id, changeInfo, tab);
+            fire.async(tab.id, changeInfo, tab);
           }
         };
 
         let listener = event => {
           let needed = [];
           if (event.type == "TabAttrModified") {
             let changed = event.detail.changed;
             if (changed.includes("image")) {
@@ -442,17 +442,17 @@ extensions.registerSchemaAPI("tabs", "ad
 
           if (needed.length) {
             let tab = TabManager.convert(extension, event.originalTarget);
 
             let changeInfo = {};
             for (let prop of needed) {
               changeInfo[prop] = tab[prop];
             }
-            fire(tab.id, changeInfo, tab);
+            fire.async(tab.id, changeInfo, tab);
           }
         };
         let progressListener = {
           onStateChange(browser, webProgress, request, stateFlags, statusCode) {
             if (!webProgress.isTopLevel) {
               return;
             }
 
@@ -1020,17 +1020,17 @@ extensions.registerSchemaAPI("tabs", "ad
         let currentSettings = this._getZoomSettings(tab.id);
 
         if (!Object.keys(settings).every(key => settings[key] === currentSettings[key])) {
           return Promise.reject(`Unsupported zoom settings: ${JSON.stringify(settings)}`);
         }
         return Promise.resolve();
       },
 
-      onZoomChange: new EventManager(context, "tabs.onZoomChange", fire => {
+      onZoomChange: new SingletonEventManager(context, "tabs.onZoomChange", fire => {
         let getZoomLevel = browser => {
           let {ZoomManager} = browser.ownerGlobal;
 
           return ZoomManager.getZoomForBrowser(browser);
         };
 
         // Stores the last known zoom level for each tab's browser.
         // WeakMap[<browser> -> number]
@@ -1070,17 +1070,17 @@ extensions.registerSchemaAPI("tabs", "ad
 
           let oldZoomFactor = zoomLevels.get(browser);
           let newZoomFactor = getZoomLevel(browser);
 
           if (oldZoomFactor != newZoomFactor) {
             zoomLevels.set(browser, newZoomFactor);
 
             let tabId = TabManager.getId(tab);
-            fire({
+            fire.async({
               tabId,
               oldZoomFactor,
               newZoomFactor,
               zoomSettings: self.tabs._getZoomSettings(tabId),
             });
           }
         };
 
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -23,18 +23,18 @@ Cu.import("resource://gre/modules/Extens
 Cu.import("resource://gre/modules/AppConstants.jsm");
 
 const POPUP_LOAD_TIMEOUT_MS = 200;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 var {
   DefaultWeakMap,
-  EventManager,
   promiseEvent,
+  SingletonEventManager,
 } = ExtensionUtils;
 
 // This file provides some useful code for the |tabs| and |windows|
 // modules. All of the code is installed on |global|, which is a scope
 // shared among the different ext-*.js scripts.
 
 global.makeWidgetId = id => {
   id = id.toLowerCase();
@@ -1276,21 +1276,21 @@ global.AllWindowEvents = {
         this.addWindowListener(window, eventType, listener);
       }
     }
   },
 };
 
 AllWindowEvents.openListener = AllWindowEvents.openListener.bind(AllWindowEvents);
 
-// Subclass of EventManager where we just need to call
+// Subclass of SingletonEventManager where we just need to call
 // add/removeEventListener on each XUL window.
-global.WindowEventManager = function(context, name, event, listener) {
-  EventManager.call(this, context, name, fire => {
-    let listener2 = (...args) => listener(fire, ...args);
-    AllWindowEvents.addListener(event, listener2);
-    return () => {
-      AllWindowEvents.removeListener(event, listener2);
-    };
-  });
+global.WindowEventManager = class extends SingletonEventManager {
+  constructor(context, name, event, listener) {
+    super(context, name, fire => {
+      let listener2 = (...args) => listener(fire, ...args);
+      AllWindowEvents.addListener(event, listener2);
+      return () => {
+        AllWindowEvents.removeListener(event, listener2);
+      };
+    });
+  }
 };
-
-WindowEventManager.prototype = Object.create(EventManager.prototype);
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -7,50 +7,50 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "nsIAboutNewTabService");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
-  EventManager,
+  SingletonEventManager,
   promiseObserved,
 } = ExtensionUtils;
 
 function onXULFrameLoaderCreated({target}) {
   target.messageManager.sendAsyncMessage("AllowScriptsToClose", {});
 }
 
 extensions.registerSchemaAPI("windows", "addon_parent", context => {
   let {extension} = context;
   return {
     windows: {
       onCreated:
       new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
-        fire(WindowManager.convert(extension, window));
+        fire.async(WindowManager.convert(extension, window));
       }).api(),
 
       onRemoved:
       new WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
-        fire(WindowManager.getId(window));
+        fire.async(WindowManager.getId(window));
       }).api(),
 
-      onFocusChanged: new EventManager(context, "windows.onFocusChanged", fire => {
+      onFocusChanged: new SingletonEventManager(context, "windows.onFocusChanged", fire => {
         // Keep track of the last windowId used to fire an onFocusChanged event
         let lastOnFocusChangedWindowId;
 
         let listener = event => {
           // Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
           // event when switching focus between two Firefox windows.
           Promise.resolve().then(() => {
             let window = Services.focus.activeWindow;
             let windowId = window ? WindowManager.getId(window) : WindowManager.WINDOW_ID_NONE;
             if (windowId !== lastOnFocusChangedWindowId) {
-              fire(windowId);
+              fire.async(windowId);
               lastOnFocusChangedWindowId = windowId;
             }
           });
         };
         AllWindowEvents.addListener("focus", listener);
         AllWindowEvents.addListener("blur", listener);
         return () => {
           AllWindowEvents.removeListener("focus", listener);
--- a/browser/extensions/formautofill/.eslintrc.js
+++ b/browser/extensions/formautofill/.eslintrc.js
@@ -1,16 +1,19 @@
 "use strict";
 
 module.exports = { // eslint-disable-line no-undef
   "extends": "../../.eslintrc.js",
 
   "globals": {
+    "addMessageListener": false,
     "Components": true,
     "dump": true,
+    "removeMessageListener": false,
+    "sendAsyncMessage": false,
     "TextDecoder": false,
     "TextEncoder": false,
   },
 
   "rules": {
     // Rules from the mozilla plugin
     "mozilla/balanced-listeners": "error",
     "mozilla/no-aArgs": "warn",
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -51,30 +51,34 @@ let FormAutofillParent = {
       OS.Path.join(OS.Constants.Path.profileDir, PROFILE_JSON_FILE_NAME);
 
     this._profileStore = new ProfileStorage(storePath);
     this._profileStore.initialize();
 
     let mm = Cc["@mozilla.org/globalmessagemanager;1"]
                .getService(Ci.nsIMessageListenerManager);
     mm.addMessageListener("FormAutofill:PopulateFieldValues", this);
+    mm.addMessageListener("FormAutofill:GetProfiles", this);
   },
 
   /**
    * Handles the message coming from FormAutofillContent.
    *
    * @param   {string} message.name The name of the message.
    * @param   {object} message.data The data of the message.
    * @param   {nsIFrameMessageManager} message.target Caller's message manager.
    */
   receiveMessage: function({name, data, target}) {
     switch (name) {
       case "FormAutofill:PopulateFieldValues":
         this._populateFieldValues(data, target);
         break;
+      case "FormAutofill:GetProfiles":
+        this._getProfiles(data, target);
+        break;
     }
   },
 
   /**
    * Returns the instance of ProfileStorage. To avoid syncing issues, anyone
    * who needs to access the profile should request the instance by this instead
    * of creating a new one.
    *
@@ -93,16 +97,17 @@ let FormAutofillParent = {
     if (this._profileStore) {
       this._profileStore._saveImmediately();
       this._profileStore = null;
     }
 
     let mm = Cc["@mozilla.org/globalmessagemanager;1"]
                .getService(Ci.nsIMessageListenerManager);
     mm.removeMessageListener("FormAutofill:PopulateFieldValues", this);
+    mm.removeMessageListener("FormAutofill:GetProfiles", this);
   },
 
   /**
    * Populates the field values and notifies content to fill in. Exception will
    * be thrown if there's no matching profile.
    *
    * @private
    * @param  {string} data.guid
@@ -114,16 +119,39 @@ let FormAutofillParent = {
    */
   _populateFieldValues({guid, fields}, target) {
     this._profileStore.notifyUsed(guid);
     this._fillInFields(this._profileStore.get(guid), fields);
     target.sendAsyncMessage("FormAutofill:fillForm", {fields});
   },
 
   /**
+   * Get the profile data from profile store and return profiles back to content process.
+   *
+   * @private
+   * @param  {string} data.searchString
+   *         The typed string for filtering out the matched profile.
+   * @param  {string} data.info
+   *         The input autocomplete property's information.
+   * @param  {nsIFrameMessageManager} target
+   *         Content's message manager.
+   */
+  _getProfiles({searchString, info}, target) {
+    let profiles = [];
+
+    if (info && info.fieldName) {
+      profiles = this._profileStore.getByFilter({searchString, info});
+    } else {
+      profiles = this._profileStore.getAll();
+    }
+
+    target.messageManager.sendAsyncMessage("FormAutofill:Profiles", profiles);
+  },
+
+  /**
    * Transforms a word with hyphen into camel case.
    * (e.g. transforms "address-type" into "addressType".)
    *
    * @private
    * @param   {string} str The original string with hyphen.
    * @returns {string} The camel-cased output string.
    */
   _camelCase(str) {
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -61,16 +61,30 @@ const VALID_FIELDS = [
   "addressLevel2",
   "addressLevel1",
   "postalCode",
   "country",
   "tel",
   "email",
 ];
 
+// TODO: Remove this once we can add profile from preference.
+const MOCK_MODE = false;
+const MOCK_STORAGE = [{
+  guid: "test-guid-1",
+  organization: "Sesame Street",
+  streetAddress: "123 Sesame Street.",
+  tel: "1-345-345-3456",
+}, {
+  guid: "test-guid-2",
+  organization: "Mozilla",
+  streetAddress: "331 E. Evelyn Avenue",
+  tel: "1-650-903-0800",
+}];
+
 function ProfileStorage(path) {
   this._path = path;
 }
 
 ProfileStorage.prototype = {
   /**
    * Loads the profile data from file to memory.
    *
@@ -205,24 +219,54 @@ ProfileStorage.prototype = {
    */
   getAll() {
     this._store.ensureDataReady();
 
     // Profiles are cloned to avoid accidental modifications from outside.
     return this._store.data.profiles.map(this._clone);
   },
 
+  /**
+   * Returns the filtered profiles based on input's information and searchString.
+   *
+   * @returns {Array.<Profile>}
+   *          An array containing clones of matched profiles.
+   */
+  getByFilter({info, searchString}) {
+    this._store.ensureDataReady();
+
+    // Profiles are cloned to avoid accidental modifications from outside.
+    return this._findByFilter({info, searchString}).map(this._clone);
+  },
+
   _clone(profile) {
     return Object.assign({}, profile);
   },
 
   _findByGUID(guid) {
     return this._store.data.profiles.find(profile => profile.guid == guid);
   },
 
+  _findByFilter({info, searchString}) {
+    let profiles = MOCK_MODE ? MOCK_STORAGE : this._store.data.profiles;
+    let lcSearchString = searchString.toLowerCase();
+
+    return profiles.filter(profile => {
+      // Return true if string is not provided and field exists.
+      // TODO: We'll need to check if the address is for billing or shipping.
+      let name = profile[info.fieldName];
+
+      if (!searchString) {
+        return !!name;
+      }
+
+      return name.toLowerCase().startsWith(lcSearchString);
+    });
+  },
+
   _normalizeProfile(profile) {
     let result = {};
     for (let key in profile) {
       if (!VALID_FIELDS.includes(key)) {
         throw new Error(`"${key}" is not a valid field.`);
       }
       if (typeof profile[key] !== "string" &&
           typeof profile[key] !== "number") {
--- a/browser/extensions/formautofill/content/FormAutofillContent.js
+++ b/browser/extensions/formautofill/content/FormAutofillContent.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* eslint-disable no-use-before-define */
+
 /*
  * Form Autofill frame script.
  */
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, manager: Cm} = Components;
 
@@ -222,38 +224,84 @@ AutofillProfileAutoCompleteSearch.protot
    * or asynchronously) of the result
    *
    * @param {string} searchString the string to search for
    * @param {string} searchParam
    * @param {Object} previousResult a previous result to use for faster searchinig
    * @param {Object} listener the listener to notify when the search is complete
    */
   startSearch(searchString, searchParam, previousResult, listener) {
-    // TODO: These mock data should be replaced by form autofill API
-    let fieldName = "name";
-    let profiles = [{
-      guid: "test-guid-1",
-      organization: "Sesame Street",
-      streetAddress: "123 Sesame Street.",
-      tel: "1-345-345-3456.",
-    }, {
-      guid: "test-guid-2",
-      organization: "Mozilla",
-      streetAddress: "331 E. Evelyn Avenue",
-      tel: "1-650-903-0800",
-    }];
-    let result = new ProfileAutoCompleteResult(searchString, fieldName, profiles, {});
+    this.forceStop = false;
+    let info = this.getInputDetails();
 
-    listener.onSearchResult(this, result);
+    this.getProfiles({info, searchString}).then((profiles) => {
+      if (this.forceStop) {
+        return;
+      }
+
+      // TODO: Set formInfo for ProfileAutoCompleteResult
+      // let formInfo = this.getFormDetails();
+      let result = new ProfileAutoCompleteResult(searchString, info, profiles, {});
+
+      listener.onSearchResult(this, result);
+    });
   },
 
   /**
    * Stops an asynchronous search that is in progress
    */
   stopSearch() {
+    this.forceStop = true;
+  },
+
+  /**
+   * Get the profile data from parent process for AutoComplete result.
+   *
+   * @private
+   * @param  {Object} data
+   *         Parameters for querying the corresponding result.
+   * @param  {string} data.searchString
+   *         The typed string for filtering out the matched profile.
+   * @param  {string} data.info
+   *         The input autocomplete property's information.
+   * @returns {Promise}
+   *          Promise that resolves when profiles returned from parent process.
+   */
+  getProfiles(data) {
+    return new Promise((resolve) => {
+      addMessageListener("FormAutofill:Profiles", function getResult(result) {
+        removeMessageListener("FormAutofill:Profiles", getResult);
+        resolve(result.data);
+      });
+
+      sendAsyncMessage("FormAutofill:GetProfiles", data);
+    });
+  },
+
+
+  /**
+   * Get the input's information from FormAutofillContent's cache.
+   *
+   * @returns {Object}
+   *          Target input's information that cached in FormAutofillContent.
+   */
+  getInputDetails() {
+    // TODO: Maybe we'll need to wait for cache ready if detail is empty.
+    return FormAutofillContent.getInputDetails(formFillController.getFocusedInput());
+  },
+
+  /**
+   * Get the form's information from FormAutofillContent's cache.
+   *
+   * @returns {Array<Object>}
+   *          Array of the inputs' information for the target form.
+   */
+  getFormDetails() {
+    // TODO: Maybe we'll need to wait for cache ready if details is empty.
+    return FormAutofillContent.getFormDetails(formFillController.getFocusedInput());
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AutofillProfileAutoCompleteSearch]);
 
 let ProfileAutocomplete = {
   _registered: false,
   _factory: null,
@@ -302,39 +350,99 @@ var FormAutofillContent = {
         if (!(doc instanceof Ci.nsIDOMHTMLDocument)) {
           return;
         }
         this._identifyAutofillFields(doc);
         break;
     }
   },
 
+  /**
+   * Get the input's information from cache which is created after page identified.
+   *
+   * @param {HTMLInputElement} element Focused input which triggered profile searching
+   * @returns {Object|null}
+   *          Return target input's information that cloned from content cache
+   *          (or return null if the information is not found in the cache).
+   */
+  getInputDetails(element) {
+    for (let formDetails of this._formsDetails) {
+      for (let detail of formDetails) {
+        if (element == detail.element) {
+          return this._serializeInfo(detail);
+        }
+      }
+    }
+    return null;
+  },
+
+  /**
+   * Get the form's information from cache which is created after page identified.
+   *
+   * @param {HTMLInputElement} element Focused input which triggered profile searching
+   * @returns {Array<Object>|null}
+   *          Return target form's information that cloned from content cache
+   *          (or return null if the information is not found in the cache).
+   *
+   */
+  getFormDetails(element) {
+    for (let formDetails of this._formsDetails) {
+      if (formDetails.some((detail) => detail.element == element)) {
+        return formDetails.map((detail) => this._serializeInfo(detail));
+      }
+    }
+    return null;
+  },
+
+  /**
+   * Create a clone the information object without element reference.
+   *
+   * @param {Object} detail Profile autofill information for specific input.
+   * @returns {Object}
+   *          Return a copy of cached information object without element reference
+   *          since it's not needed for creating result.
+   */
+  _serializeInfo(detail) {
+    let info = Object.assign({}, detail);
+    delete info.element;
+    return info;
+  },
+
   _identifyAutofillFields(doc) {
     let forms = [];
+    this._formsDetails = [];
 
     // Collects root forms from inputs.
     for (let field of doc.getElementsByTagName("input")) {
+      // We only consider text-like fields for now until we support radio and
+      // checkbox buttons in the future.
+      if (!field.mozIsTextField(true)) {
+        continue;
+      }
+
       let formLike = FormLikeFactory.createFromField(field);
       if (!forms.some(form => form.rootElement === formLike.rootElement)) {
         forms.push(formLike);
       }
     }
 
     // Collects the fields that can be autofilled from each form and marks them
     // as autofill fields if the amount is above the threshold.
     forms.forEach(form => {
       let formHandler = new FormAutofillHandler(form);
       formHandler.collectFormFields();
       if (formHandler.fieldDetails.length < AUTOFILL_FIELDS_THRESHOLD) {
         return;
       }
 
+      this._formsDetails.push(formHandler.fieldDetails);
       formHandler.fieldDetails.forEach(
         detail => this._markAsAutofillField(detail.element));
     });
   },
 
   _markAsAutofillField(field) {
     formFillController.markAsAutofillField(field);
   },
 };
 
+
 FormAutofillContent.init();
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_getFormInputDetails.js
@@ -0,0 +1,90 @@
+"use strict";
+
+let {FormAutofillContent} = loadFormAutofillContent();
+
+const TESTCASES = [
+  {
+    description: "Form containing 5 fields with autocomplete attribute.",
+    document: `<form>
+                 <input id="street-addr" autocomplete="street-address">
+                 <input id="city" autocomplete="address-level2">
+                 <input id="country" autocomplete="country">
+                 <input id="email" autocomplete="email">
+                 <input id="tel" autocomplete="tel">
+               </form>`,
+    targetInput: ["street-addr", "country"],
+    expectedResult: [{
+      input: {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+      form: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+      ],
+    },
+    {
+      input: {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+      form: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+      ],
+    }],
+  },
+  {
+    description: "2 forms that are able to be auto filled",
+    document: `<form>
+                 <input id="home-addr" autocomplete="street-address">
+                 <input id="city" autocomplete="address-level2">
+                 <input id="country" autocomplete="country">
+               </form>
+               <form>
+                 <input id="office-addr" autocomplete="street-address">
+                 <input id="email" autocomplete="email">
+                 <input id="tel" autocomplete="tel">
+               </form>`,
+    targetInput: ["home-addr", "office-addr"],
+    expectedResult: [{
+      input: {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+      form: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "address-level2"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "country"},
+      ],
+    },
+    {
+      input: {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+      form: [
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "street-address"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "email"},
+        {"section": "", "addressType": "", "contactType": "", "fieldName": "tel"},
+      ],
+    }],
+  },
+];
+
+
+TESTCASES.forEach(testcase => {
+  add_task(function* () {
+    do_print("Starting testcase: " + testcase.description);
+
+    let doc = MockDocument.createTestDocument(
+              "http://localhost:8080/test/", testcase.document);
+    FormAutofillContent._identifyAutofillFields(doc);
+
+    for (let i in testcase.targetInput) {
+      let input = doc.getElementById(testcase.targetInput[i]);
+
+      Assert.deepEqual(FormAutofillContent.getInputDetails(input),
+                       testcase.expectedResult[i].input,
+                       "Check if returned input information is correct.");
+
+      Assert.deepEqual(FormAutofillContent.getFormDetails(input),
+                       testcase.expectedResult[i].form,
+                       "Check if returned form information is correct.");
+    }
+  });
+});
--- a/browser/extensions/formautofill/test/unit/test_profileStorage.js
+++ b/browser/extensions/formautofill/test/unit/test_profileStorage.js
@@ -104,16 +104,48 @@ add_task(function* test_get() {
   // Modifying output shouldn't affect the storage.
   profile.organization = "test";
   do_check_profile_matches(profileStorage.get(guid), TEST_PROFILE_1);
 
   Assert.throws(() => profileStorage.get("INVALID_GUID"),
     /No matching profile\./);
 });
 
+add_task(function* test_getByFilter() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  yield prepareTestProfiles(path);
+
+  let profileStorage = new ProfileStorage(path);
+  yield profileStorage.initialize();
+
+  let filter = {info: {fieldName: "streetAddress"}, searchString: "Some"};
+  let profiles = profileStorage.getByFilter(filter);
+  do_check_eq(profiles.length, 1);
+  do_check_profile_matches(profiles[0], TEST_PROFILE_2);
+
+  filter = {info: {fieldName: "country"}, searchString: "u"};
+  profiles = profileStorage.getByFilter(filter);
+  do_check_eq(profiles.length, 2);
+  do_check_profile_matches(profiles[0], TEST_PROFILE_1);
+  do_check_profile_matches(profiles[1], TEST_PROFILE_2);
+
+  filter = {info: {fieldName: "streetAddress"}, searchString: "test"};
+  profiles = profileStorage.getByFilter(filter);
+  do_check_eq(profiles.length, 0);
+
+  filter = {info: {fieldName: "streetAddress"}, searchString: ""};
+  profiles = profileStorage.getByFilter(filter);
+  do_check_eq(profiles.length, 2);
+
+  // Check if the filtering logic is free from searching special chars.
+  filter = {info: {fieldName: "streetAddress"}, searchString: ".*"};
+  profiles = profileStorage.getByFilter(filter);
+  do_check_eq(profiles.length, 0);
+});
+
 add_task(function* test_add() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   yield prepareTestProfiles(path);
 
   let profileStorage = new ProfileStorage(path);
   yield profileStorage.initialize();
 
   let profiles = profileStorage.getAll();
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 firefox-appdir = browser
 head = head.js
 support-files =
 
 [test_autofillFormFields.js]
 [test_collectFormFields.js]
+[test_getFormInputDetails.js]
 [test_markAsAutofillField.js]
 [test_populateFieldValues.js]
 [test_profileAutocompleteResult.js]
 [test_profileStorage.js]
--- a/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
@@ -31,17 +31,17 @@
 <!ENTITY dataChoicesTab.label            "Data Choices">
 
 <!ENTITY healthReportDesc.label          "Helps you understand your browser performance and shares data with &vendorShortName; about your browser health">
 <!ENTITY enableHealthReport.label        "Enable &brandShortName; Health Report">
 <!ENTITY enableHealthReport.accesskey    "R">
 <!ENTITY healthReportLearnMore.label     "Learn More">
 
 <!ENTITY telemetryDesc.label             "Shares performance, usage, hardware and customization data about your browser with &vendorShortName; to help us make &brandShortName; better">
-<!ENTITY enableTelemetryData.label       "Share additional data (i.e., Telemetry)">
+<!ENTITY enableTelemetryData.label       "Share Additional Data (i.e., Telemetry)">
 <!ENTITY enableTelemetryData.accesskey   "T">
 <!ENTITY telemetryLearnMore.label        "Learn More">
 
 <!ENTITY crashReporterDesc2.label         "Crash reports help &vendorShortName; fix problems and make your browser more stable and secure">
 <!ENTITY alwaysSubmitCrashReports.label   "Allow &brandShortName; to send backlogged crash reports on your behalf">
 <!ENTITY alwaysSubmitCrashReports.accesskey "c">
 <!ENTITY crashReporterLearnMore.label     "Learn More">
 
@@ -78,31 +78,31 @@
 <!ENTITY clearCacheNow.accesskey         "C">
 <!ENTITY clearOfflineAppCacheNow.label   "Clear Now">
 <!ENTITY clearOfflineAppCacheNow.accesskey "N">
 <!ENTITY overrideSmartCacheSize.label    "Override automatic cache management">
 <!ENTITY overrideSmartCacheSize.accesskey "O">
 
 <!ENTITY updateTab.label                 "Update">
 
-<!ENTITY updateApplication.label         "&brandShortName; updates">
+<!ENTITY updateApplication.label         "&brandShortName; Updates">
 <!ENTITY updateAuto1.label               "Automatically install updates (recommended: improved security)">
 <!ENTITY updateAuto1.accesskey           "A">
 <!ENTITY updateCheckChoose.label         "Check for updates, but let you choose whether to install them">
 <!ENTITY updateCheckChoose.accesskey     "C">
 <!ENTITY updateManual.label              "Never check for updates (not recommended: security risk)">
 <!ENTITY updateManual.accesskey          "N">
 
 <!ENTITY updateHistory.label             "Show Update History">
 <!ENTITY updateHistory.accesskey         "p">
 
 <!ENTITY useService.label                "Use a background service to install updates">
 <!ENTITY useService.accesskey            "b">
 
-<!ENTITY autoUpdateOthers.label          "Automatically update">
+<!ENTITY autoUpdateOthers.label          "Automatically Update">
 <!ENTITY enableSearchUpdate.label        "Search Engines">
 <!ENTITY enableSearchUpdate.accesskey    "E">
 
 <!ENTITY offlineStorageNotify.label               "Tell you when a website asks to store data for offline use">
 <!ENTITY offlineStorageNotify.accesskey           "T">
 <!ENTITY offlineStorageNotifyExceptions.label     "Exceptions…">
 <!ENTITY offlineStorageNotifyExceptions.accesskey "x">
 
--- a/browser/locales/en-US/chrome/browser/preferences/content.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/content.dtd
@@ -48,13 +48,13 @@
   -  These 2 strings are displayed before and after a 'Microsoft Translator'
   -  logo.
   -  The translations for these strings should match the translations in
   -  browser/translation.dtd
   -->
 <!ENTITY translation.options.attribution.beforeLogo "Translations by">
 <!ENTITY translation.options.attribution.afterLogo "">
 
-<!ENTITY  drmContent.label             "DRM content">
+<!ENTITY  drmContent.label             "DRM Content">
 
 <!ENTITY  playDRMContent.label         "Play DRM content">
 <!ENTITY  playDRMContent.accesskey     "P">
 <!ENTITY  playDRMContent.learnMore.label "Learn more">
--- a/browser/locales/en-US/chrome/browser/preferences/search.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/search.dtd
@@ -8,17 +8,17 @@
 
 <!ENTITY provideSearchSuggestions.label        "Provide search suggestions">
 <!ENTITY provideSearchSuggestions.accesskey    "s">
 
 <!ENTITY showURLBarSuggestions.label           "Show search suggestions in location bar results">
 <!ENTITY showURLBarSuggestions.accesskey       "l">
 <!ENTITY urlBarSuggestionsPermanentPB.label    "Search suggestions will not be shown in location bar results because you have configured &brandShortName; to never remember history.">
 
-<!ENTITY oneClickSearchEngines.label           "One-click search engines">
+<!ENTITY oneClickSearchEngines.label           "One-click Search Engines">
 
 <!ENTITY chooseWhichOneToDisplay.label         "The search bar lets you search alternate engines directly. Choose which ones to display.">
 
 <!ENTITY engineNameColumn.label                "Search Engine">
 <!ENTITY engineKeywordColumn.label             "Keyword">
 
 <!ENTITY restoreDefaultSearchEngines.label     "Restore Default Search Engines">
 <!ENTITY restoreDefaultSearchEngines.accesskey "d">
--- a/browser/locales/en-US/chrome/browser/preferences/sync.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -85,25 +85,25 @@ both, to better adapt this sentence to t
 <!ENTITY forget.accesskey            "F">
 
 <!ENTITY welcome.description "Access your tabs, bookmarks, passwords and more wherever you use &brandShortName;.">
 <!ENTITY welcome.signIn.label "Sign In">
 <!ENTITY welcome.createAccount.label "Create Account">
 
 <!ENTITY welcome.useOldSync.label "Using an older version of Sync?">
 
-<!ENTITY signedOut.caption            "Take your Web with you">
+<!ENTITY signedOut.caption            "Take Your Web With You">
 <!ENTITY signedOut.description        "Synchronize your bookmarks, history, tabs, passwords, add-ons, and preferences across all your devices.">
 <!ENTITY signedOut.accountBox.title   "Connect with a &syncBrand.fxAccount.label;">
 <!ENTITY signedOut.accountBox.create  "Create Account">
 <!ENTITY signedOut.accountBox.create.accesskey  "C">
 <!ENTITY signedOut.accountBox.signin  "Sign In">
 <!ENTITY signedOut.accountBox.signin.accesskey  "I">
 
-<!ENTITY signedIn.engines.label       "Sync across all devices">
+<!ENTITY signedIn.engines.label       "Sync Across All Devices">
 
 <!-- LOCALIZATION NOTE (mobilePromo3.*): the following strings will be used to
      create a single sentence with active links.
      The resulting sentence in English is: "Download Firefox for
      Android or iOS to sync with your mobile device." -->
 
 <!ENTITY mobilePromo3.start            "Download Firefox for ">
 <!-- LOCALIZATION NOTE (mobilePromo3.androidLink): This is a link title that links to https://www.mozilla.org/firefox/android/ -->
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -268,19 +268,19 @@ this.ExtensionsUI = {
       popupIconURL: info.icon,
       persistent: true,
 
       eventCallback(topic) {
         if (topic == "showing") {
           let doc = this.browser.ownerDocument;
           doc.getElementById("addon-webext-perm-header").innerHTML = header;
 
-          if (text) {
-            doc.getElementById("addon-webext-perm-text").innerHTML = text;
-          }
+          let textEl = doc.getElementById("addon-webext-perm-text");
+          textEl.innerHTML = text;
+          textEl.hidden = !text;
 
           let listIntroEl = doc.getElementById("addon-webext-perm-intro");
           listIntroEl.value = listIntro;
           listIntroEl.hidden = (msgs.length == 0);
 
           let list = doc.getElementById("addon-webext-perm-list");
           while (list.firstChild) {
             list.firstChild.remove();
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -412,16 +412,33 @@ def split_triplet(triplet):
         raw_cpu=cpu,
         raw_os=os,
         # Toolchains, most notably for cross compilation may use cpu-os
         # prefixes.
         toolchain='%s-%s' % (cpu, os),
     )
 
 
+# This defines a fake target/host namespace for when running with --help
+@depends('--help')
+def help_host_target(help):
+    if help:
+        return namespace(
+            alias='unknown-unknown-unknown',
+            cpu='unknown',
+            bitness='unknown',
+            kernel='unknown',
+            os='unknown',
+            endianness='unknown',
+            raw_cpu='unknown',
+            raw_os='unknown',
+            toolchain='unknown-unknown',
+        )
+
+
 @imports('subprocess')
 def config_sub(shell, triplet):
     config_sub = os.path.join(os.path.dirname(__file__), '..',
                               'autoconf', 'config.sub')
     return subprocess.check_output([shell, config_sub, triplet]).strip()
 
 
 @depends('--host', shell)
@@ -432,24 +449,28 @@ def host(value, shell):
         config_guess = os.path.join(os.path.dirname(__file__), '..',
                                     'autoconf', 'config.guess')
         host = subprocess.check_output([shell, config_guess]).strip()
     else:
         host = value[0]
 
     return split_triplet(config_sub(shell, host))
 
+host = help_host_target | host
+
 
 @depends('--target', host, shell)
 @checking('for target system type', lambda t: t.alias)
 def target(value, host, shell):
     if not value:
         return host
     return split_triplet(config_sub(shell, value[0]))
 
+target = help_host_target | target
+
 
 @depends(host, target)
 @checking('whether cross compiling')
 def cross_compiling(host, target):
     return host != target
 
 set_config('CROSS_COMPILE', cross_compiling)
 set_define('CROSS_COMPILE', cross_compiling)
--- a/devtools/client/netmonitor/actions/ui.js
+++ b/devtools/client/netmonitor/actions/ui.js
@@ -43,22 +43,22 @@ function resizeWaterfall(width) {
     type: WATERFALL_RESIZE,
     width
   };
 }
 
 /**
  * Change the selected tab for details panel.
  *
- * @param {number} index - tab index to be selected
+ * @param {string} id - tab id to be selected
  */
-function selectDetailsPanelTab(index) {
+function selectDetailsPanelTab(id) {
   return {
     type: SELECT_DETAILS_PANEL_TAB,
-    index,
+    id,
   };
 }
 
 /**
  * Toggle sidebar open state.
  */
 function toggleSidebar() {
   return (dispatch, getState) => dispatch(openSidebar(!getState().ui.sidebarOpen));
--- a/devtools/client/netmonitor/components/request-list-content.js
+++ b/devtools/client/netmonitor/components/request-list-content.js
@@ -259,13 +259,13 @@ module.exports = connect(
     /**
      * A handler that opens the security tab in the details view if secure or
      * broken security indicator is clicked.
      */
     onSecurityIconClick: (e, item) => {
       const { securityState } = item;
       // Choose the security tab.
       if (securityState && securityState !== "insecure") {
-        dispatch(Actions.selectDetailsPanelTab(5));
+        dispatch(Actions.selectDetailsPanelTab("security"));
       }
     },
   })
 )(RequestListContent);
--- a/devtools/client/netmonitor/events.js
+++ b/devtools/client/netmonitor/events.js
@@ -54,25 +54,19 @@ const EVENTS = {
 
   // When the request post params are displayed in the UI.
   REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
 
   // When the image response thumbnail is displayed in the UI.
   RESPONSE_IMAGE_THUMBNAIL_DISPLAYED:
     "NetMonitor:ResponseImageThumbnailAvailable",
 
-  // When a tab is selected in the NetworkDetailsView and subsequently rendered.
-  TAB_UPDATED: "NetMonitor:TabUpdated",
-
   // Fired when Sidebar has finished being populated.
   SIDEBAR_POPULATED: "NetMonitor:SidebarPopulated",
 
-  // Fired when NetworkDetailsView has finished being populated.
-  NETWORKDETAILSVIEW_POPULATED: "NetMonitor:NetworkDetailsViewPopulated",
-
   // Fired when CustomRequestView has finished being populated.
   CUSTOMREQUESTVIEW_POPULATED: "NetMonitor:CustomRequestViewPopulated",
 
   // Fired when charts have been displayed in the PerformanceStatisticsView.
   PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed",
   PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed",
   EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed",
 
--- a/devtools/client/netmonitor/reducers/ui.js
+++ b/devtools/client/netmonitor/reducers/ui.js
@@ -8,17 +8,17 @@ const I = require("devtools/client/share
 const {
   OPEN_SIDEBAR,
   OPEN_STATISTICS,
   SELECT_DETAILS_PANEL_TAB,
   WATERFALL_RESIZE,
 } = require("../constants");
 
 const UI = I.Record({
-  detailsPanelSelectedTab: 0,
+  detailsPanelSelectedTab: "headers",
   sidebarOpen: false,
   statisticsOpen: false,
   waterfallWidth: null,
 });
 
 // Safe bounds for waterfall width (px)
 const REQUESTS_WATERFALL_SAFE_BOUNDS = 90;
 
@@ -30,17 +30,17 @@ function openSidebar(state, action) {
   return state.set("sidebarOpen", action.open);
 }
 
 function openStatistics(state, action) {
   return state.set("statisticsOpen", action.open);
 }
 
 function setDetailsPanelTab(state, action) {
-  return state.set("detailsPanelSelectedTab", action.index);
+  return state.set("detailsPanelSelectedTab", action.id);
 }
 
 function ui(state = new UI(), action) {
   switch (action.type) {
     case OPEN_SIDEBAR:
       return openSidebar(state, action);
     case OPEN_STATISTICS:
       return openStatistics(state, action);
--- a/devtools/client/netmonitor/shared/components/details-panel.js
+++ b/devtools/client/netmonitor/shared/components/details-panel.js
@@ -33,31 +33,32 @@ const TIMINGS_TITLE = L10N.getStr("netmo
 const SECURITY_TITLE = L10N.getStr("netmonitor.tab.security");
 const PREVIEW_TITLE = L10N.getStr("netmonitor.tab.preview");
 
 /*
  * Details panel component
  * Display the network request details
  */
 function DetailsPanel({
+  activeTabId,
   cloneSelectedRequest,
   request,
-  setTabIndex,
-  tabIndex,
+  selectTab,
   toolbox,
 }) {
   if (!request) {
     return null;
   }
 
   return (
     Tabbar({
-      onSelect: setTabIndex,
+      activeTabId,
+      onSelect: selectTab,
+      renderOnlySelected: true,
       showAllTabsMenu: true,
-      tabActive: tabIndex,
       toolbox,
     },
       TabPanel({
         id: "headers",
         title: HEADERS_TITLE,
       },
         HeadersPanel({ request, cloneSelectedRequest }),
       ),
@@ -104,22 +105,22 @@ function DetailsPanel({
 }
 
 DetailsPanel.displayName = "DetailsPanel";
 
 DetailsPanel.propTypes = {
   cloneSelectedRequest: PropTypes.func.isRequired,
   request: PropTypes.object,
   setTabIndex: PropTypes.func.isRequired,
-  tabIndex: PropTypes.number.isRequired,
+  selectedTab: PropTypes.number.isRequired,
   toolbox: PropTypes.object.isRequired,
 };
 
 module.exports = connect(
   (state) => ({
+    activeTabId: state.ui.detailsPanelSelectedTab,
     request: getSelectedRequest(state),
-    tabIndex: state.ui.detailsPanelSelectedTab,
   }),
   (dispatch) => ({
     cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
-    setTabIndex: (index) => dispatch(Actions.selectDetailsPanelTab(index)),
+    selectTab: (tabId) => dispatch(Actions.selectDetailsPanelTab(tabId)),
   }),
 )(DetailsPanel);
--- a/devtools/client/netmonitor/shared/components/editor.js
+++ b/devtools/client/netmonitor/shared/components/editor.js
@@ -5,16 +5,17 @@
 /* eslint-disable react/prop-types */
 
 "use strict";
 
 const { createClass, DOM, PropTypes } = require("devtools/client/shared/vendor/react");
 const SourceEditor = require("devtools/client/sourceeditor/editor");
 
 const { div } = DOM;
+const SYNTAX_HIGHLIGHT_MAX_SIZE = 102400;
 
 /**
  * CodeMirror editor as a React component
  */
 const Editor = createClass({
   displayName: "Editor",
 
   propTypes: {
@@ -34,32 +35,32 @@ const Editor = createClass({
     };
   },
 
   componentDidMount() {
     const { mode, text } = this.props;
 
     this.editor = new SourceEditor({
       lineNumbers: true,
-      mode,
+      mode: text.length < SYNTAX_HIGHLIGHT_MAX_SIZE ? mode : null,
       readOnly: true,
       value: text,
     });
 
     this.deferEditor = this.editor.appendTo(this.refs.editorElement);
   },
 
   componentDidUpdate(prevProps) {
     const { mode, open, text } = this.props;
 
     if (!open) {
       return;
     }
 
-    if (prevProps.mode !== mode) {
+    if (prevProps.mode !== mode && text.length < SYNTAX_HIGHLIGHT_MAX_SIZE) {
       this.deferEditor.then(() => {
         this.editor.setMode(mode);
       });
     }
 
     if (prevProps.text !== text) {
       this.deferEditor.then(() => {
         // FIXME: Workaround for browser_net_accessibility test to
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -90,18 +90,16 @@ skip-if = (os == 'linux' && bits == 32 &
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_copy_as_curl.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_cors_requests.js]
 [browser_net_cyrillic-01.js]
 [browser_net_cyrillic-02.js]
-[browser_net_details-no-duplicated-content.js]
-skip-if = true # Test broken in React version, is too low-level
 [browser_net_frame.js]
 skip-if = (os == 'linux' && debug && bits == 32) # Bug 1321434
 [browser_net_filter-01.js]
 skip-if = (os == 'linux' && debug && bits == 32) # Bug 1303439
 [browser_net_filter-02.js]
 [browser_net_filter-03.js]
 [browser_net_filter-04.js]
 [browser_net_footer-summary.js]
--- a/devtools/client/netmonitor/test/browser_net_brotli.js
+++ b/devtools/client/netmonitor/test/browser_net_brotli.js
@@ -33,20 +33,20 @@ add_task(function* () {
       statusText: "Connected",
       type: "plain",
       fullMimeType: "text/plain",
       transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 10),
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 64),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3 .editor-mount iframe");
+  wait = waitForDOM(document, "#response-panel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   let [editorFrame] = yield wait;
 
   yield once(editorFrame, "DOMContentLoaded");
   yield waitForDOM(editorFrame.contentDocument, ".CodeMirror-code");
   yield testResponse("br");
 
   yield teardown(monitor);
 
--- a/devtools/client/netmonitor/test/browser_net_complex-params.js
+++ b/devtools/client/netmonitor/test/browser_net_complex-params.js
@@ -20,62 +20,62 @@ add_task(function* () {
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, 1, 6);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  wait = waitForDOM(document, "#panel-2 .tree-section", 2);
+  wait = waitForDOM(document, "#params-panel .tree-section", 2);
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-2 a").click();
+  document.querySelector("#params-tab").click();
   yield wait;
   testParamsTab1("a", '""', '{ "foo": "bar" }', '""');
 
-  wait = waitForDOM(document, "#panel-2 .tree-section", 2);
+  wait = waitForDOM(document, "#params-panel .tree-section", 2);
   RequestsMenu.selectedIndex = 1;
   yield wait;
   testParamsTab1("a", '"b"', '{ "foo": "bar" }', '""');
 
-  wait = waitForDOM(document, "#panel-2 .tree-section", 2);
+  wait = waitForDOM(document, "#params-panel .tree-section", 2);
   RequestsMenu.selectedIndex = 2;
   yield wait;
   testParamsTab1("a", '"b"', "foo", '"bar"');
 
-  wait = waitForDOM(document, "#panel-2 tr:not(.tree-section).treeRow", 2);
+  wait = waitForDOM(document, "#params-panel tr:not(.tree-section).treeRow", 2);
   RequestsMenu.selectedIndex = 3;
   yield wait;
   testParamsTab2("a", '""', '{ "foo": "bar" }', "js");
 
-  wait = waitForDOM(document, "#panel-2 tr:not(.tree-section).treeRow", 2);
+  wait = waitForDOM(document, "#params-panel tr:not(.tree-section).treeRow", 2);
   RequestsMenu.selectedIndex = 4;
   yield wait;
   testParamsTab2("a", '"b"', '{ "foo": "bar" }', "js");
 
   // Wait for all tree sections and editor updated by react
-  let waitSections = waitForDOM(document, "#panel-2 .tree-section", 2);
-  let waitEditor = waitForDOM(document, "#panel-2 .editor-mount iframe");
+  let waitSections = waitForDOM(document, "#params-panel .tree-section", 2);
+  let waitEditor = waitForDOM(document, "#params-panel .editor-mount iframe");
   RequestsMenu.selectedIndex = 5;
   let [, editorFrames] = yield Promise.all([waitSections, waitEditor]);
   yield once(editorFrames[0], "DOMContentLoaded");
   yield waitForDOM(editorFrames[0].contentDocument, ".CodeMirror-code");
   testParamsTab2("a", '"b"', "?foo=bar", "text");
 
-  wait = waitForDOM(document, "#panel-2 .empty-notice");
+  wait = waitForDOM(document, "#params-panel .empty-notice");
   RequestsMenu.selectedIndex = 6;
   yield wait;
   testParamsTab3();
 
   yield teardown(monitor);
 
   function testParamsTab1(queryStringParamName, queryStringParamValue,
                           formDataParamName, formDataParamValue) {
-    let tabpanel = document.querySelector("#panel-2");
+    let tabpanel = document.querySelector("#params-panel");
 
     is(tabpanel.querySelectorAll(".tree-section").length, 2,
       "The number of param tree sections displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll("tr:not(.tree-section).treeRow").length, 2,
       "The number of param rows displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
@@ -106,17 +106,17 @@ add_task(function* () {
       "The first form data param name was incorrect.");
     is(values[1].textContent, formDataParamValue,
       "The first form data param value was incorrect.");
   }
 
   function testParamsTab2(queryStringParamName, queryStringParamValue,
                           requestPayload, editorMode) {
     let isJSON = editorMode === "js";
-    let tabpanel = document.querySelector("#panel-2");
+    let tabpanel = document.querySelector("#params-panel");
 
     is(tabpanel.querySelectorAll(".tree-section").length, 2,
       "The number of param tree sections displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll("tr:not(.tree-section).treeRow").length, isJSON ? 2 : 1,
       "The number of param rows displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll(".empty-notice").length, 0,
       "The empty notice should not be displayed in this tabpanel.");
 
@@ -159,17 +159,17 @@ add_task(function* () {
     } else {
       let editor = editorFrames[0].contentDocument.querySelector(".CodeMirror-code");
       ok(editor.textContent.includes(requestPayload),
         "The text shown in the source editor is incorrect.");
     }
   }
 
   function testParamsTab3() {
-    let tabpanel = document.querySelector("#panel-2");
+    let tabpanel = document.querySelector("#params-panel");
 
     is(tabpanel.querySelectorAll(".tree-section").length, 0,
       "The number of param tree sections displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll("tr:not(.tree-section).treeRow").length, 0,
       "The number of param rows displayed in this tabpanel is incorrect.");
     is(tabpanel.querySelectorAll(".empty-notice").length, 1,
       "The empty notice should be displayed in this tabpanel.");
 
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -85,24 +85,16 @@ add_task(function* () {
       statusText: "OK",
       type: "plain",
       fullMimeType: "text/plain",
       transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 73),
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
-  yield wait;
-
-  RequestsMenu.selectedIndex = -1;
-
   yield selectIndexAndWaitForEditor(0);
   yield testResponseTab("xml");
 
   yield selectIndexAndWaitForEditor(1);
   yield testResponseTab("css");
 
   yield selectIndexAndWaitForEditor(2);
   yield testResponseTab("js");
@@ -117,17 +109,17 @@ add_task(function* () {
   yield testResponseTab("png");
 
   yield selectIndexAndWaitForEditor(6);
   yield testResponseTab("gzip");
 
   yield teardown(monitor);
 
   function* testResponseTab(type) {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
 
     function checkVisibility(box) {
       is(tabpanel.querySelector(".response-error-header") === null,
         true,
         "The response error header doesn't display");
       let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
       is(jsonView.textContent !== L10N.getStr("jsonScopeName"),
         box != "json",
@@ -227,36 +219,37 @@ add_task(function* () {
         is(text, new Array(1000).join("Hello gzip!"),
           "The text shown in the source editor is incorrect for the gzip request.");
         break;
       }
     }
   }
 
   function* selectIndexAndWaitForEditor(index) {
-    let editor = document.querySelector("#panel-3 .editor-mount iframe");
+    let editor = document.querySelector("#response-panel .editor-mount iframe");
     if (!editor) {
-      let waitDOM = waitForDOM(document, ".editor-mount iframe");
+      let waitDOM = waitForDOM(document, "#response-panel .editor-mount iframe");
       RequestsMenu.selectedIndex = index;
+      document.querySelector("#response-tab").click();
       [editor] = yield waitDOM;
       yield once(editor, "DOMContentLoaded");
     } else {
       RequestsMenu.selectedIndex = index;
     }
 
     yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   }
 
   function* selectIndexAndWaitForJSONView(index) {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
     let waitDOM = waitForDOM(tabpanel, ".treeTable");
     RequestsMenu.selectedIndex = index;
     yield waitDOM;
   }
 
   function* selectIndexAndWaitForImageView(index) {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
     let waitDOM = waitForDOM(tabpanel, ".response-image");
     RequestsMenu.selectedIndex = index;
     let [imageNode] = yield waitDOM;
     yield once(imageNode, "load");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
@@ -23,20 +23,20 @@ add_task(function* () {
   yield wait;
 
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CONTENT_TYPE_SJS + "?fmt=txt", {
       status: 200,
       statusText: "DA DA DA"
     });
 
-  wait = waitForDOM(document, "#panel-3 .editor-mount iframe");
+  wait = waitForDOM(document, "#response-panel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   let [editor] = yield wait;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   let text = editor.contentDocument
           .querySelector(".CodeMirror-line").textContent;
 
   ok(text.includes("\u0411\u0440\u0430\u0442\u0430\u043d"),
     "The text shown in the source editor is correct.");
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
@@ -22,20 +22,20 @@ add_task(function* () {
   yield wait;
 
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CYRILLIC_URL, {
       status: 200,
       statusText: "OK"
     });
 
-  wait = waitForDOM(document, "#panel-3 .editor-mount iframe");
+  wait = waitForDOM(document, "#response-panel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   let [editor] = yield wait;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   let text = editor.contentDocument
           .querySelector(".CodeMirror-code").textContent;
 
   ok(text.includes("\u0411\u0440\u0430\u0442\u0430\u043d"),
     "The text shown in the source editor is correct.");
deleted file mode 100644
--- a/devtools/client/netmonitor/test/browser_net_details-no-duplicated-content.js
+++ /dev/null
@@ -1,171 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// A test to ensure that the content in details pane is not duplicated.
-
-add_task(function* () {
-  let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let panel = monitor.panelWin;
-  let { NetMonitorView, EVENTS } = panel;
-  let { RequestsMenu, NetworkDetails } = NetMonitorView;
-
-  const COOKIE_UNIQUE_PATH = "/do-not-use-in-other-tests-using-cookies";
-
-  let TEST_CASES = [
-    {
-      desc: "Test headers tab",
-      pageURI: CUSTOM_GET_URL,
-      requestURI: null,
-      isPost: false,
-      tabIndex: 0,
-      variablesView: NetworkDetails._headers,
-      expectedScopeLength: 2,
-    },
-    {
-      desc: "Test cookies tab",
-      pageURI: CUSTOM_GET_URL,
-      requestURI: COOKIE_UNIQUE_PATH,
-      isPost: false,
-      tabIndex: 1,
-      variablesView: NetworkDetails._cookies,
-      expectedScopeLength: 1,
-    },
-    {
-      desc: "Test params tab",
-      pageURI: POST_RAW_URL,
-      requestURI: null,
-      isPost: true,
-      tabIndex: 2,
-      variablesView: NetworkDetails._params,
-      expectedScopeLength: 1,
-    },
-  ];
-
-  info("Adding a cookie for the \"Cookie\" tab test");
-  yield setDocCookie("a=b; path=" + COOKIE_UNIQUE_PATH);
-
-  info("Running tests");
-  for (let spec of TEST_CASES) {
-    yield runTestCase(spec);
-  }
-
-  // Remove the cookie. If an error occurs the path of the cookie ensures it
-  // doesn't mess with the other tests.
-  info("Removing the added cookie.");
-  yield setDocCookie(
-    "a=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" + COOKIE_UNIQUE_PATH);
-
-  yield teardown(monitor);
-
-  /**
-   * Set a content document cookie
-   */
-  function setDocCookie(cookie) {
-    return ContentTask.spawn(tab.linkedBrowser, cookie, function* (cookieArg) {
-      content.document.cookie = cookieArg;
-    });
-  }
-
-  /**
-   * A helper that handles the execution of each case.
-   */
-  function* runTestCase(spec) {
-    info("Running case: " + spec.desc);
-    let wait = waitForNetworkEvents(monitor, 1);
-    tab.linkedBrowser.loadURI(spec.pageURI);
-    yield wait;
-
-    RequestsMenu.clear();
-    yield waitForFinalDetailTabUpdate(spec.tabIndex, spec.isPost, spec.requestURI);
-
-    is(spec.variablesView._store.length, spec.expectedScopeLength,
-       "View contains " + spec.expectedScopeLength + " scope headers");
-  }
-
-  /**
-   * A helper that prepares the variables view for the actual testing. It
-   * - selects the correct tab
-   * - performs the specified request to specified URI
-   * - opens the details view
-   * - waits for the final update to happen
-   */
-  function* waitForFinalDetailTabUpdate(tabIndex, isPost, uri) {
-    let onNetworkEvent = panel.once(EVENTS.NETWORK_EVENT);
-    let onDetailsPopulated = panel.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
-    let onRequestFinished = isPost ?
-      waitForNetworkEvents(monitor, 0, 1) :
-      waitForNetworkEvents(monitor, 1);
-
-    info("Performing a request");
-    yield ContentTask.spawn(tab.linkedBrowser, uri, function* (url) {
-      content.wrappedJSObject.performRequests(1, url);
-    });
-
-    info("Waiting for NETWORK_EVENT");
-    yield onNetworkEvent;
-
-    if (!RequestsMenu.getItemAtIndex(0)) {
-      info("Waiting for the request to be added to the view");
-      yield monitor.panelWin.once(EVENTS.REQUEST_ADDED);
-    }
-
-    ok(true, "Received NETWORK_EVENT. Selecting the item.");
-    let item = RequestsMenu.getItemAtIndex(0);
-    RequestsMenu.selectedItem = item;
-
-    info("Item selected. Waiting for NETWORKDETAILSVIEW_POPULATED");
-    yield onDetailsPopulated;
-
-    info("Received populated event. Selecting tab at index " + tabIndex);
-    NetworkDetails.widget.selectedIndex = tabIndex;
-
-    info("Waiting for request to finish.");
-    yield onRequestFinished;
-
-    ok(true, "Request finished.");
-
-    /**
-     * Because this test uses lazy updates there's four scenarios to consider:
-     * #1: Everything is updated and test is ready to continue.
-     * #2: There's updates that are waiting to be flushed.
-     * #3: Updates are flushed but the tab update is still running.
-     * #4: There's pending updates and a tab update is still running.
-     *
-     * For case #1 there's not going to be a TAB_UPDATED event so don't wait for
-     * it (bug 1106181).
-     *
-     * For cases #2 and #3 it's enough to wait for one TAB_UPDATED event as for
-     * - case #2 the next flush will perform the final update and single
-     *   TAB_UPDATED event is emitted.
-     * - case #3 the running update is the final update that'll emit one
-     *   TAB_UPDATED event.
-     *
-     * For case #4 we must wait for the updates to be flushed before we can
-     * start waiting for TAB_UPDATED event or we'll continue the test right
-     * after the pending update finishes.
-     */
-    let hasQueuedUpdates = RequestsMenu._updateQueue.length !== 0;
-    let hasRunningTabUpdate = NetworkDetails._viewState.updating[tabIndex];
-
-    if (hasQueuedUpdates || hasRunningTabUpdate) {
-      info("There's pending updates - waiting for them to finish.");
-      info("  hasQueuedUpdates: " + hasQueuedUpdates);
-      info("  hasRunningTabUpdate: " + hasRunningTabUpdate);
-
-      if (hasQueuedUpdates && hasRunningTabUpdate) {
-        info("Waiting for updates to be flushed.");
-        // _flushRequests calls .populate which emits the following event
-        yield panel.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
-
-        info("Requests flushed.");
-      }
-
-      info("Waiting for final tab update.");
-      yield waitFor(panel, EVENTS.TAB_UPDATED);
-    }
-
-    info("All updates completed.");
-  }
-});
--- a/devtools/client/netmonitor/test/browser_net_html-preview.js
+++ b/devtools/client/netmonitor/test/browser_net_html-preview.js
@@ -20,49 +20,49 @@ add_task(function* () {
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
 
-  ok(document.querySelector("#tab-0.is-active"),
+  ok(document.querySelector("#headers-tab[aria-selected=true]"),
     "The headers tab in the details panel should be selected.");
-  ok(!document.querySelector("#tab-5"),
+  ok(!document.querySelector("#preview-tab"),
     "The preview tab should be hidden for non html responses.");
-  ok(!document.querySelector("#panel-5"),
+  ok(!document.querySelector("#preview-panel"),
     "The preview panel is hidden for non html responses.");
 
-  wait = waitForDOM(document, "#tab-5");
+  wait = waitForDOM(document, ".tabs");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[4]);
   yield wait;
 
-  document.querySelector("#tab-5 a").click();
+  document.querySelector("#preview-tab").click();
 
-  ok(document.querySelector("#tab-5.is-active"),
+  ok(document.querySelector("#preview-tab[aria-selected=true]"),
     "The preview tab in the details panel should be selected.");
-  ok(document.querySelector("#panel-5"),
+  ok(document.querySelector("#preview-panel"),
     "The preview panel should be visible now.");
 
-  let iframe = document.querySelector("#panel-5 iframe");
+  let iframe = document.querySelector("#preview-panel iframe");
   yield once(iframe, "DOMContentLoaded");
 
   ok(iframe,
     "There should be a response preview iframe available.");
   ok(iframe.contentDocument,
     "The iframe's content document should be available.");
   is(iframe.contentDocument.querySelector("blink").textContent, "Not Found",
     "The iframe's content document should be loaded and correct.");
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[5]);
 
-  ok(document.querySelector("#tab-0.is-active"),
+  ok(document.querySelector("#headers-tab[aria-selected=true]"),
     "The headers tab in the details panel should be selected again.");
-  ok(!document.querySelector("#tab-5"),
+  ok(!document.querySelector("#preview-tab"),
     "The preview tab should be hidden again for non html responses.");
-  ok(!document.querySelector("#panel-5"),
+  ok(!document.querySelector("#preview-panel"),
     "The preview panel is hidden again for non html responses.");
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_json-long.js
+++ b/devtools/client/netmonitor/test/browser_net_json-long.js
@@ -34,28 +34,28 @@ add_task(function* () {
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8",
       size: L10N.getFormatStr("networkMenu.sizeKB",
         L10N.numberWithDecimals(85975 / 1024, 2)),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3");
+  wait = waitForDOM(document, "#response-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
 
     is(tabpanel.querySelector(".response-error-header") === null, true,
       "The response error header doesn't have the intended visibility.");
     let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
     is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
       "The response json view has the intended visibility.");
     is(tabpanel.querySelector(".editor-mount iframe") === null, true,
       "The response editor doesn't have the intended visibility.");
--- a/devtools/client/netmonitor/test/browser_net_json-malformed.js
+++ b/devtools/client/netmonitor/test/browser_net_json-malformed.js
@@ -26,25 +26,25 @@ add_task(function* () {
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CONTENT_TYPE_SJS + "?fmt=json-malformed", {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8"
     });
 
-  wait = waitForDOM(document, "#panel-3 .editor-mount iframe");
+  wait = waitForDOM(document, "#response-panel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   let [editor] = yield wait;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
 
-  let tabpanel = document.querySelector("#panel-3");
+  let tabpanel = document.querySelector("#response-panel");
   is(tabpanel.querySelector(".response-error-header") === null, false,
     "The response error header doesn't have the intended visibility.");
   is(tabpanel.querySelector(".response-error-header").textContent,
     "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data" +
       " at line 1 column 40 of the JSON data",
     "The response error header doesn't have the intended text content.");
   is(tabpanel.querySelector(".response-error-header").getAttribute("title"),
     "SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data" +
--- a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
@@ -29,28 +29,28 @@ add_task(function* () {
       status: 200,
       statusText: "OK",
       type: "x-bigcorp-json",
       fullMimeType: "text/x-bigcorp-json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3");
+  wait = waitForDOM(document, "#response-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
 
     is(tabpanel.querySelector(".response-error-header") === null, true,
       "The response error header doesn't have the intended visibility.");
     let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
     is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
       "The response json view has the intended visibility.");
     is(tabpanel.querySelector(".editor-mount iframe") === null, true,
       "The response editor doesn't have the intended visibility.");
--- a/devtools/client/netmonitor/test/browser_net_json_text_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
@@ -29,28 +29,28 @@ add_task(function* () {
       status: 200,
       statusText: "OK",
       type: "plain",
       fullMimeType: "text/plain; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3");
+  wait = waitForDOM(document, "#response-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
 
     is(tabpanel.querySelector(".response-error-header") === null, true,
       "The response error header doesn't have the intended visibility.");
     let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
     is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
       "The response json view has the intended visibility.");
     is(tabpanel.querySelector(".editor-mount iframe") === null, true,
       "The response editor doesn't have the intended visibility.");
--- a/devtools/client/netmonitor/test/browser_net_jsonp.js
+++ b/devtools/client/netmonitor/test/browser_net_jsonp.js
@@ -38,34 +38,34 @@ add_task(function* () {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 54),
       time: true
     });
 
-  wait = waitForDOM(document, "#panel-3");
+  wait = waitForDOM(document, "#response-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   yield wait;
 
   testResponseTab("$_0123Fun", "\"Hello JSONP!\"");
 
-  wait = waitForDOM(document, "#panel-3 .tree-section");
+  wait = waitForDOM(document, "#response-panel .tree-section");
   RequestsMenu.selectedIndex = 1;
   yield wait;
 
   testResponseTab("$_4567Sad", "\"Hello weird JSONP!\"");
 
   yield teardown(monitor);
 
   function testResponseTab(func, greeting) {
-    let tabpanel = document.querySelector("#panel-3");
+    let tabpanel = document.querySelector("#response-panel");
 
     is(tabpanel.querySelector(".response-error-header") === null, true,
       "The response error header doesn't have the intended visibility.");
     is(tabpanel.querySelector(".tree-section .treeLabel").textContent,
       L10N.getFormatStr("jsonpScopeName", func),
       "The response json view has the intened visibility and correct title.");
     is(tabpanel.querySelector(".editor-mount iframe") === null, true,
       "The response editor doesn't have the intended visibility.");
--- a/devtools/client/netmonitor/test/browser_net_large-response.js
+++ b/devtools/client/netmonitor/test/browser_net_large-response.js
@@ -29,20 +29,20 @@ add_task(function* () {
   yield wait;
 
   verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
     "GET", CONTENT_TYPE_SJS + "?fmt=html-long", {
       status: 200,
       statusText: "OK"
     });
 
-  let waitDOM = waitForDOM(document, "#panel-3 .editor-mount iframe");
+  let waitDOM = waitForDOM(document, "#response-panel .editor-mount iframe");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   let [editor] = yield waitDOM;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
 
   let text = editor.contentDocument
         .querySelector(".CodeMirror-line").textContent;
 
   ok(text.match(/^<p>/), "The text shown in the source editor is incorrect.");
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -42,36 +42,36 @@ add_task(function* () {
       statusText: "Och Aye",
       type: "plain",
       fullMimeType: "text/plain; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
       time: true
     });
 
   // Wait for all tree sections updated by react
-  wait = waitForDOM(document, "#panel-2 .tree-section", 2);
+  wait = waitForDOM(document, "#params-panel .tree-section", 2);
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-2 a").click();
+  document.querySelector("#params-tab").click();
   yield wait;
   yield testParamsTab("urlencoded");
 
   // Wait for all tree sections and editor updated by react
-  let waitForSections = waitForDOM(document, "#panel-2 .tree-section", 2);
-  let waitForEditor = waitForDOM(document, "#panel-2 .editor-mount iframe");
+  let waitForSections = waitForDOM(document, "#params-panel .tree-section", 2);
+  let waitForEditor = waitForDOM(document, "#params-panel .editor-mount iframe");
   RequestsMenu.selectedIndex = 1;
   let [, editorFrames] = yield Promise.all([waitForSections, waitForEditor]);
   yield once(editorFrames[0], "DOMContentLoaded");
   yield waitForDOM(editorFrames[0].contentDocument, ".CodeMirror-code");
   yield testParamsTab("multipart");
 
   return teardown(monitor);
 
   function* testParamsTab(type) {
-    let tabpanel = document.querySelector("#panel-2");
+    let tabpanel = document.querySelector("#params-panel");
 
     function checkVisibility(box) {
       is(!tabpanel.querySelector(".treeTable"), !box.includes("params"),
         "The request params doesn't have the indended visibility.");
       is(tabpanel.querySelector(".editor-mount") === null,
         !box.includes("editor"),
         "The request post data doesn't have the indended visibility.");
     }
--- a/devtools/client/netmonitor/test/browser_net_post-data-02.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-02.js
@@ -21,23 +21,23 @@ add_task(function* () {
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
-  wait = waitForDOM(document, "#panel-2 .tree-section");
+  wait = waitForDOM(document, "#params-panel .tree-section");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-2 a").click();
+  document.querySelector("#params-tab").click();
   yield wait;
 
-  let tabpanel = document.querySelector("#panel-2");
+  let tabpanel = document.querySelector("#params-panel");
 
   ok(tabpanel.querySelector(".treeTable"),
     "The request params doesn't have the indended visibility.");
   ok(tabpanel.querySelector(".editor-mount") === null,
     "The request post data doesn't have the indended visibility.");
 
   is(tabpanel.querySelectorAll(".tree-section").length, 1,
     "There should be 1 tree sections displayed in this tabpanel.");
--- a/devtools/client/netmonitor/test/browser_net_post-data-03.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-03.js
@@ -21,23 +21,23 @@ add_task(function* () {
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
-  wait = waitForDOM(document, "#panel-0");
+  wait = waitForDOM(document, "#headers-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-0 a").click();
+  document.querySelector("#headers-tab").click();
   yield wait;
 
-  let tabpanel = document.querySelector("#panel-0");
+  let tabpanel = document.querySelector("#headers-panel");
 
   is(tabpanel.querySelectorAll(".tree-section .treeLabel").length, 3,
     "There should be 3 header sections displayed in this tabpanel.");
 
   is(tabpanel.querySelectorAll(".tree-section .treeLabel")[2].textContent,
     L10N.getStr("requestHeadersFromUpload") + " (" +
     L10N.getFormatStr("networkMenu.sizeB", 74) + ")",
     "The request headers from upload section doesn't have the correct title.");
@@ -52,21 +52,21 @@ add_task(function* () {
   is(values[values.length - 2].textContent, "\"application/x-www-form-urlencoded\"",
     "The first request header value was incorrect.");
   is(labels[labels.length - 1].textContent, "custom-header",
     "The second request header name was incorrect.");
   is(values[values.length - 1].textContent, "\"hello world!\"",
     "The second request header value was incorrect.");
 
   // Wait for all tree sections updated by react
-  wait = waitForDOM(document, "#panel-2 .tree-section");
-  document.querySelector("#tab-2 a").click();
+  wait = waitForDOM(document, "#params-panel .tree-section");
+  document.querySelector("#params-tab").click();
   yield wait;
 
-  tabpanel = document.querySelector("#panel-2");
+  tabpanel = document.querySelector("#params-panel");
 
   ok(tabpanel.querySelector(".treeTable"),
     "The params tree view should be displayed.");
   ok(tabpanel.querySelector(".editor-mount") === null,
     "The post data shouldn't be displayed.");
 
   is(tabpanel.querySelector(".tree-section .treeLabel").textContent,
     L10N.getStr("paramsFormData"),
--- a/devtools/client/netmonitor/test/browser_net_post-data-04.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-04.js
@@ -21,23 +21,23 @@ add_task(function* () {
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
-  wait = waitForDOM(document, "#panel-2 .tree-section");
+  wait = waitForDOM(document, "#params-panel .tree-section");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-2 a").click();
+  document.querySelector("#params-tab").click();
   yield wait;
 
-  let tabpanel = document.querySelector("#panel-2");
+  let tabpanel = document.querySelector("#params-panel");
 
   ok(tabpanel.querySelector(".treeTable"),
     "The request params doesn't have the indended visibility.");
   ok(tabpanel.querySelector(".editor-mount") === null,
     "The request post data doesn't have the indended visibility.");
 
   is(tabpanel.querySelectorAll(".tree-section").length, 1,
     "There should be 1 tree sections displayed in this tabpanel.");
--- a/devtools/client/netmonitor/test/browser_net_security-details.js
+++ b/devtools/client/netmonitor/test/browser_net_security-details.js
@@ -17,23 +17,23 @@ add_task(function* () {
   info("Performing a secure request.");
   const REQUESTS_URL = "https://example.com" + CORS_SJS_PATH;
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, REQUESTS_URL, function* (url) {
     content.wrappedJSObject.performRequests(1, url);
   });
   yield wait;
 
-  wait = waitForDOM(document, "#panel-5");
+  wait = waitForDOM(document, "#security-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelector("#details-pane-toggle"));
-  document.querySelector("#tab-5 a").click();
+  document.querySelector("#security-tab").click();
   yield wait;
 
-  let tabpanel = document.querySelector("#panel-5");
+  let tabpanel = document.querySelector("#security-panel");
   let textboxes = tabpanel.querySelectorAll(".textbox-input");
 
   // Connection
   // The protocol will be TLS but the exact version depends on which protocol
   // the test server example.com supports.
   let protocol = textboxes[0].value;
   ok(protocol.startsWith("TLS"), "The protocol " + protocol + " seems valid.");
 
--- a/devtools/client/netmonitor/test/browser_net_security-error.js
+++ b/devtools/client/netmonitor/test/browser_net_security-error.js
@@ -16,20 +16,20 @@ add_task(function* () {
   info("Requesting a resource that has a certificate problem.");
 
   let wait = waitForSecurityBrokenNetworkEvent();
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests(1, "https://nocert.example.com");
   });
   yield wait;
 
-  wait = waitForDOM(document, "#panel-5");
+  wait = waitForDOM(document, "#security-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelector("#details-pane-toggle"));
-  document.querySelector("#tab-5 a").click();
+  document.querySelector("#security-tab").click();
   yield wait;
 
   let errormsg = document.querySelector(".security-info-value");
   isnot(errormsg.textContent, "", "Error message is not empty.");
 
   return teardown(monitor);
 
   /**
--- a/devtools/client/netmonitor/test/browser_net_security-icon-click.js
+++ b/devtools/client/netmonitor/test/browser_net_security-icon-click.js
@@ -19,17 +19,17 @@ add_task(function* () {
   yield performRequestAndWait("https://example.com" + CORS_SJS_PATH + "?request_1");
 
   is(RequestsMenu.itemCount, 2, "Two events event logged.");
 
   yield clickAndTestSecurityIcon();
 
   info("Selecting headers panel again.");
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector("#tab-0 a"));
+    document.querySelector("#headers-tab"));
 
   info("Sorting the items by filename.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-menu-file-button"));
 
   info("Testing that security icon can be clicked after the items were sorted.");
   yield clickAndTestSecurityIcon();
 
@@ -42,15 +42,16 @@ add_task(function* () {
     });
     return wait;
   }
 
   function* clickAndTestSecurityIcon() {
     let item = RequestsMenu.getItemAtIndex(0);
     let icon = document.querySelector(".requests-security-state-icon");
 
-    let wait = waitForDOM(document, "#panel-5");
+    let wait = waitForDOM(document, "#security-panel");
     info("Clicking security icon of the first request and waiting for panel update.");
     EventUtils.synthesizeMouseAtCenter(icon, {}, monitor.panelWin);
     yield wait;
-    ok(document.querySelector("#tab-5.is-active"), "Security tab is selected.");
+
+    ok(document.querySelector("#security-tab[aria-selected=true]"), "Security tab is selected.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
@@ -31,19 +31,19 @@ add_task(function* () {
   info("Selecting secure request.");
   wait = waitForDOM(document, ".tabs");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   yield wait;
 
   info("Selecting security tab.");
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector("#tab-5 a"));
+    document.querySelector("#security-tab"));
 
   info("Selecting insecure request.");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[1]);
 
-  ok(document.querySelector("#tab-0.is-active"),
+  ok(document.querySelector("#headers-tab[aria-selected=true]"),
     "Selected tab was reset when selected security tab was hidden.");
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
@@ -53,44 +53,35 @@ add_task(function* () {
     info("Waiting for new network event.");
     yield onNewItem;
 
     info("Selecting the request.");
     RequestsMenu.selectedIndex = 0;
 
     is(RequestsMenu.selectedItem.securityState, undefined,
        "Security state has not yet arrived.");
-    is(!!document.querySelector("#tab-5"), testcase.visibleOnNewEvent,
+    is(!!document.querySelector("#security-tab"), testcase.visibleOnNewEvent,
       "Security tab is " + (testcase.visibleOnNewEvent ? "visible" : "hidden") +
       " after new request was added to the menu.");
-    is(!!document.querySelector("#panel-5"), testcase.visibleOnNewEvent,
-      "Security panel is " + (testcase.visibleOnNewEvent ? "visible" : "hidden") +
-      " after new request was added to the menu.");
 
     info("Waiting for security information to arrive.");
     yield onSecurityInfo;
 
     ok(RequestsMenu.selectedItem.securityState,
        "Security state arrived.");
-    is(!!document.querySelector("#tab-5"), testcase.visibleOnSecurityInfo,
+    is(!!document.querySelector("#security-tab"), testcase.visibleOnSecurityInfo,
        "Security tab is " + (testcase.visibleOnSecurityInfo ? "visible" : "hidden") +
        " after security information arrived.");
-    is(!!document.querySelector("#panel-5"), testcase.visibleOnSecurityInfo,
-      "Security panel is " + (testcase.visibleOnSecurityInfo? "visible" : "hidden") +
-      " after security information arrived.");
 
     info("Waiting for request to complete.");
     yield onComplete;
 
-    is(!!document.querySelector("#tab-5"), testcase.visibleOnceComplete,
+    is(!!document.querySelector("#security-tab"), testcase.visibleOnceComplete,
        "Security tab is " + (testcase.visibleOnceComplete ? "visible" : "hidden") +
        " after request has been completed.");
-    is(!!document.querySelector("#panel-5"), testcase.visibleOnceComplete,
-      "Security panel is " + (testcase.visibleOnceComplete? "visible" : "hidden") +
-      " after request has been completed.");
 
     info("Clearing requests.");
     RequestsMenu.clear();
   }
 
   return teardown(monitor);
 
   /**
--- a/devtools/client/netmonitor/test/browser_net_security-warnings.js
+++ b/devtools/client/netmonitor/test/browser_net_security-warnings.js
@@ -33,20 +33,20 @@ add_task(function* () {
     yield wait;
 
     info("Selecting the request.");
     wait = waitForDOM(document, ".tabs");
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[0]);
     yield wait;
 
-    if (!document.querySelector("#tab-5.is-active")) {
+    if (!document.querySelector("#security-tab[aria-selected=true]")) {
       info("Selecting security tab.");
-      wait = waitForDOM(document, "#panel-5 .properties-view");
-      document.querySelector("#tab-5 a").click();
+      wait = waitForDOM(document, "#security-panel .properties-view");
+      document.querySelector("#security-tab").click();
       yield wait;
     }
 
     is(document.querySelector("#security-warning-cipher"),
       test.warnCipher,
       "Cipher suite warning is hidden.");
 
     RequestsMenu.clear();
--- a/devtools/client/netmonitor/test/browser_net_status-codes.js
+++ b/devtools/client/netmonitor/test/browser_net_status-codes.js
@@ -140,44 +140,44 @@ add_task(function* () {
       counter++;
     }
   }
 
   /**
    * A function that tests "Headers" panel contains correct information.
    */
   function* testHeaders(data, index) {
-    let wait = waitForDOM(document, "#panel-0");
+    let wait = waitForDOM(document, "#headers-panel");
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[index]);
     yield wait;
 
-    let panel = document.querySelector("#panel-0");
+    let panel = document.querySelector("#headers-panel");
     let summaryValues = panel.querySelectorAll(".tabpanel-summary-value.textbox-input");
     let { method, uri, details: { status, statusText } } = data;
 
     is(summaryValues[0].value, uri, "The url summary value is incorrect.");
     is(summaryValues[1].value, method, "The method summary value is incorrect.");
     is(panel.querySelector(".requests-menu-status-icon").dataset.code, status,
       "The status summary code is incorrect.");
     is(summaryValues[3].value, status + " " + statusText,
       "The status summary value is incorrect.");
   }
 
   /**
    * A function that tests "Params" panel contains correct information.
    */
   function* testParams(data, index) {
-    let wait = waitForDOM(document, "#panel-2 .properties-view");
+    let wait = waitForDOM(document, "#params-panel .properties-view");
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[index]);
-    document.querySelector("#tab-2 a").click();
+    document.querySelector("#params-tab").click();
     yield wait;
 
-    let panel = document.querySelector("#panel-2");
+    let panel = document.querySelector("#params-panel");
     let statusParamValue = data.uri.split("=").pop();
     let statusParamShownValue = "\"" + statusParamValue + "\"";
     let treeSections = panel.querySelectorAll(".tree-section");
     debugger
 
     is(treeSections.length, 1,
       "There should be 1 param section displayed in this panel.");
     is(panel.querySelectorAll("tr:not(.tree-section).treeRow").length, 1,
--- a/devtools/client/netmonitor/test/browser_net_streaming-response.js
+++ b/devtools/client/netmonitor/test/browser_net_streaming-response.js
@@ -35,49 +35,50 @@ add_task(function* () {
   REQUESTS.forEach(([ fmt ], i) => {
     verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(i),
       "GET", CONTENT_TYPE_SJS + "?fmt=" + fmt, {
         status: 200,
         statusText: "OK"
       });
   });
 
-  wait = waitForDOM(document, "#panel-3");
+  wait = waitForDOM(document, "#response-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
-  document.querySelector("#tab-3 a").click();
+  document.querySelector("#response-tab").click();
   yield wait;
 
   RequestsMenu.selectedIndex = -1;
 
   yield selectIndexAndWaitForEditor(0);
   // the hls-m3u8 part
   testEditorContent(REQUESTS[0]);
 
   yield selectIndexAndWaitForEditor(1);
   // the mpeg-dash part
   testEditorContent(REQUESTS[1]);
 
   return teardown(monitor);
 
   function* selectIndexAndWaitForEditor(index) {
-    let editor = document.querySelector("#panel-3 .editor-mount iframe");
+    let editor = document.querySelector("#response-panel .editor-mount iframe");
     if (!editor) {
-      let waitDOM = waitForDOM(document, "#panel-3 .editor-mount iframe");
+      let waitDOM = waitForDOM(document, "#response-panel .editor-mount iframe");
       RequestsMenu.selectedIndex = index;
+      document.querySelector("#response-tab").click();
       [editor] = yield waitDOM;
       yield once(editor, "DOMContentLoaded");
     } else {
       RequestsMenu.selectedIndex = index;
     }
 
     yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   }
 
   function testEditorContent([ fmt, textRe ]) {
-    let editor = document.querySelector("#panel-3 .editor-mount iframe");
+    let editor = document.querySelector("#response-panel .editor-mount iframe");
     let text = editor.contentDocument
           .querySelector(".CodeMirror-line").textContent;
 
     ok(text.match(textRe),
       "The text shown in the source editor for " + fmt + " is correct.");
   }
 });
--- a/devtools/client/shared/components/tabs/tabbar.js
+++ b/devtools/client/shared/components/tabs/tabbar.js
@@ -20,49 +20,58 @@ const { div } = DOM;
  */
 let Tabbar = createClass({
   displayName: "Tabbar",
 
   propTypes: {
     children: PropTypes.object,
     onSelect: PropTypes.func,
     showAllTabsMenu: PropTypes.bool,
-    tabActive: PropTypes.number,
+    activeTabId: PropTypes.string,
     toolbox: PropTypes.object,
+    renderOnlySelected: PropTypes.bool,
   },
 
   getDefaultProps: function () {
     return {
       showAllTabsMenu: false,
-      tabActive: 0,
     };
   },
 
   getInitialState: function () {
-    let { children } = this.props;
+    let { activeTabId, children = [] } = this.props;
+    let tabs = this.createTabs(children);
+    let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
+
     return {
-      tabs: children ? this.createTabs(children) : [],
-      activeTab: 0
+      activeTab: activeTab === -1 ? 0 : activeTab,
+      tabs,
     };
   },
 
   componentWillReceiveProps: function (nextProps) {
-    let { children } = nextProps;
+    let { activeTabId, children = [] } = nextProps;
+    let tabs = this.createTabs(children);
+    let activeTab = tabs.findIndex((tab, index) => tab.id === activeTabId);
 
-    if (children && children !== this.props.children) {
-      this.setState({ tabs: this.createTabs(children) });
+    if (activeTab !== this.state.activeTab ||
+        (children !== this.props.children)) {
+      this.setState({
+        activeTab: activeTab === -1 ? 0 : activeTab,
+        tabs,
+      });
     }
   },
 
   createTabs: function (children) {
     return children
       .filter((panel) => panel)
       .map((panel, index) =>
         Object.assign({}, children[index], {
-          id: index,
+          id: panel.props.id || index,
           panel,
           title: panel.props.title,
         })
       );
   },
 
   // Public API
 
@@ -132,17 +141,17 @@ let Tabbar = createClass({
     });
   },
 
   // Helpers
 
   getTabIndex: function (tabId) {
     let tabIndex = -1;
     this.state.tabs.forEach((tab, index) => {
-      if (tab.id == tabId) {
+      if (tab.id === tabId) {
         tabIndex = index;
       }
     });
     return tabIndex;
   },
 
   getTabId: function (index) {
     return this.state.tabs[index].id;
@@ -208,18 +217,19 @@ let Tabbar = createClass({
 
   render: function () {
     let tabs = this.state.tabs.map((tab) => this.renderTab(tab));
 
     return (
       div({className: "devtools-sidebar-tabs"},
         Tabs({
           onAllTabsMenuClick: this.onAllTabsMenuClick,
+          renderOnlySelected: this.props.renderOnlySelected,
           showAllTabsMenu: this.props.showAllTabsMenu,
-          tabActive: this.props.tabActive || this.state.activeTab,
+          tabActive: this.state.activeTab,
           onAfterChange: this.onTabChanged,
         },
           tabs
         )
       )
     );
   },
 });
--- a/devtools/client/shared/components/tabs/tabs.js
+++ b/devtools/client/shared/components/tabs/tabs.js
@@ -45,22 +45,28 @@ define(function (require, exports, modul
       onBeforeChange: React.PropTypes.func,
       onAfterChange: React.PropTypes.func,
       children: React.PropTypes.oneOfType([
         React.PropTypes.array,
         React.PropTypes.element
       ]).isRequired,
       showAllTabsMenu: React.PropTypes.bool,
       onAllTabsMenuClick: React.PropTypes.func,
+
+      // Set true will only render selected panel on DOM. It's complete
+      // opposite of the created array, and it's useful if panels content
+      // is unpredictable and update frequently.
+      renderOnlySelected: React.PropTypes.bool,
     },
 
     getDefaultProps: function () {
       return {
         tabActive: 0,
         showAllTabsMenu: false,
+        renderOnlySelected: false,
       };
     },
 
     getInitialState: function () {
       return {
         tabActive: this.props.tabActive,
 
         // This array is used to store an information whether a tab
@@ -100,18 +106,19 @@ define(function (require, exports, modul
     componentWillReceiveProps: function (nextProps) {
       let { children, tabActive } = nextProps;
 
       // Check type of 'tabActive' props to see if it's valid
       // (it's 0-based index).
       if (typeof tabActive === "number") {
         let panels = children.filter((panel) => panel);
 
-        // Reset to index 0 if index larger than number of panels
-        tabActive = tabActive < panels.length ? tabActive : 0;
+        // Reset to index 0 if index overflows the range of panel array
+        tabActive = (tabActive < panels.length && tabActive >= 0) ?
+          tabActive : 0;
 
         let created = [...this.state.created];
         created[tabActive] = true;
 
         this.setState({
           created,
           tabActive,
         });
@@ -219,48 +226,47 @@ define(function (require, exports, modul
         throw new Error("There must be at least one Tab");
       }
 
       if (!Array.isArray(this.props.children)) {
         this.props.children = [this.props.children];
       }
 
       let tabs = this.props.children
-        .map(tab => {
-          return typeof tab === "function" ? tab() : tab;
-        }).filter(tab => {
-          return tab;
-        }).map((tab, index) => {
-          let ref = ("tab-menu-" + index);
+        .map((tab) => typeof tab === "function" ? tab() : tab)
+        .filter((tab) => tab)
+        .map((tab, index) => {
+          let id = tab.props.id;
+          let ref = "tab-menu-" + index;
           let title = tab.props.title;
           let tabClassName = tab.props.className;
           let isTabSelected = this.state.tabActive === index;
 
-          let classes = [
+          let className = [
             "tabs-menu-item",
             tabClassName,
             isTabSelected ? "is-active" : ""
           ].join(" ");
 
           // Set tabindex to -1 (except the selected tab) so, it's focusable,
           // but not reachable via sequential tab-key navigation.
           // Changing selected tab (and so, moving focus) is done through
           // left and right arrow keys.
           // See also `onKeyDown()` event handler.
           return (
             DOM.li({
-              ref: ref,
+              className,
               key: index,
-              id: "tab-" + index,
-              className: classes,
+              ref,
               role: "presentation",
             },
               DOM.a({
-                tabIndex: this.state.tabActive === index ? 0 : -1,
-                "aria-controls": "panel-" + index,
+                id: id ? id + "-tab" : "tab-" + index,
+                tabIndex: isTabSelected ? 0 : -1,
+                "aria-controls": id ? id + "-panel" : "panel-" + index,
                 "aria-selected": isTabSelected,
                 role: "tab",
                 onClick: this.onClickTab.bind(this, index),
               },
                 title
               )
             )
           );
@@ -281,71 +287,74 @@ define(function (require, exports, modul
             tabs
           ),
           allTabsMenu
         )
       );
     },
 
     renderPanels: function () {
-      if (!this.props.children) {
+      let { children, renderOnlySelected } = this.props;
+
+      if (!children) {
         throw new Error("There must be at least one Tab");
       }
 
-      if (!Array.isArray(this.props.children)) {
-        this.props.children = [this.props.children];
+      if (!Array.isArray(children)) {
+        children = [children];
       }
 
       let selectedIndex = this.state.tabActive;
 
-      let panels = this.props.children
-        .map(tab => {
-          return typeof tab === "function" ? tab() : tab;
-        }).filter(tab => {
-          return tab;
-        }).map((tab, index) => {
-          let selected = selectedIndex == index;
+      let panels = children
+        .map((tab) => typeof tab === "function" ? tab() : tab)
+        .filter((tab) => tab)
+        .map((tab, index) => {
+          let selected = selectedIndex === index;
+          if (renderOnlySelected && !selected) {
+            return null;
+          }
+
+          let id = tab.props.id;
 
           // Use 'visibility:hidden' + 'width/height:0' for hiding
           // content of non-selected tab. It's faster (not sure why)
           // than display:none and visibility:collapse.
           let style = {
             visibility: selected ? "visible" : "hidden",
             height: selected ? "100%" : "0",
             width: selected ? "100%" : "0",
           };
 
           return (
             DOM.div({
+              id: id ? id + "-panel" : "panel-" + index,
               key: index,
-              id: "panel-" + index,
               style: style,
               className: "tab-panel-box",
               role: "tabpanel",
-              "aria-labelledby": "tab-" + index,
+              "aria-labelledby": id ? id + "-tab" : "tab-" + index,
             },
               (selected || this.state.created[index]) ? tab : null
             )
           );
         });
 
       return (
         DOM.div({className: "panels"},
           panels
         )
       );
     },
 
     render: function () {
-      let classNames = ["tabs", this.props.className].join(" ");
-
       return (
-        DOM.div({className: classNames},
+        DOM.div({ className: ["tabs", this.props.className].join(" ") },
           this.renderMenuItems(),
-          this.renderPanels()
+          this.renderPanels(),
         )
       );
     },
   });
 
   /**
    * Renders simple tab 'panel'.
    */
--- a/devtools/client/shared/components/test/mochitest/test_tabs_accessibility.html
+++ b/devtools/client/shared/components/test/mochitest/test_tabs_accessibility.html
@@ -35,38 +35,38 @@ window.onload = Task.async(function* () 
     yield addTabWithPanel(0);
     yield addTabWithPanel(1);
 
     const tabAnchors = tabbarEl.querySelectorAll("li.tabs-menu-item a");
 
     is(tabAnchors[0].parentElement.getAttribute("role"), "presentation", "li role is set correctly");
     is(tabAnchors[0].getAttribute("role"), "tab", "Anchor role is set correctly");
     is(tabAnchors[0].getAttribute("aria-selected"), "true", "Anchor aria-selected is set correctly by default");
-    is(tabAnchors[0].getAttribute("aria-controls"), "panel-0", "Anchor aria-controls is set correctly");
+    is(tabAnchors[0].getAttribute("aria-controls"), "sidebar-0-panel", "Anchor aria-controls is set correctly");
     is(tabAnchors[1].parentElement.getAttribute("role"), "presentation", "li role is set correctly");
     is(tabAnchors[1].getAttribute("role"), "tab", "Anchor role is set correctly");
     is(tabAnchors[1].getAttribute("aria-selected"), "false", "Anchor aria-selected is set correctly by default");
-    is(tabAnchors[1].getAttribute("aria-controls"), "panel-1", "Anchor aria-controls is set correctly");
+    is(tabAnchors[1].getAttribute("aria-controls"), "sidebar-1-panel", "Anchor aria-controls is set correctly");
 
     yield setState(tabbarReact, Object.assign({}, tabbarReact.state, {
       activeTab: 1
     }));
 
     is(tabAnchors[0].getAttribute("aria-selected"), "false", "Anchor aria-selected is reset correctly");
     is(tabAnchors[1].getAttribute("aria-selected"), "true", "Anchor aria-selected is reset correctly");
 
     function addTabWithPanel(tabId) {
       // Setup for InspectorTabPanel
       let panel = document.createElement("div");
-      panel.id = `sidebar-panel-${tabId}`;
+      panel.id = `sidebar-${tabId}`;
       document.body.appendChild(panel);
 
       return setState(tabbarReact, Object.assign({}, tabbarReact.state, {
         tabs: tabbarReact.state.tabs.concat({
-          id: `sidebar-panel-${tabId}`,
+          id: `sidebar-${tabId}`,
           title: `tab-${tabId}`,
           panel: InspectorTabPanel
         }),
       }));
     }
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1333,16 +1333,17 @@ XrayResolveAttribute(JSContext* cx, JS::
             if (!funobj)
               return false;
             desc.setSetterObject(funobj);
             desc.attributesRef() |= JSPROP_SETTER;
           } else {
             desc.setSetter(nullptr);
           }
           desc.object().set(wrapper);
+          desc.value().setUndefined();
           return true;
         }
       }
     }
   }
   return true;
 }
 
--- a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.cpp
@@ -378,17 +378,25 @@ WidevineVideoDecoder::Drain()
   mDrainPending = false;
   mCallback->DrainComplete();
 }
 
 void
 WidevineVideoDecoder::DecodingComplete()
 {
   Log("WidevineVideoDecoder::DecodingComplete()");
+
   if (mCDMWrapper) {
-    CDM()->DeinitializeDecoder(kStreamTypeVideo);
+    // mCallback will be null if the decoder has not been fully initialized.
+    if (mCallback) {
+      CDM()->DeinitializeDecoder(kStreamTypeVideo);
+    } else {
+      Log("WideVineDecoder::DecodingComplete() Decoder was not fully initialized!");
+    }
+
     mCDMWrapper = nullptr;
   }
+
   // Release that corresponds to AddRef() in constructor.
   Release();
 }
 
 } // namespace mozilla
--- a/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
+++ b/dom/media/gmp/widevine-adapter/WidevineVideoDecoder.h
@@ -54,17 +54,17 @@ private:
 
   bool ReturnOutput(WidevineVideoFrame& aFrame);
   void CompleteReset();
 
   GMPVideoHost* mVideoHost;
   RefPtr<CDMWrapper> mCDMWrapper;
   RefPtr<MediaByteBuffer> mExtraData;
   RefPtr<MediaByteBuffer> mAnnexB;
-  GMPVideoDecoderCallback* mCallback;
+  GMPVideoDecoderCallback* mCallback = nullptr;
   std::map<uint64_t, uint64_t> mFrameDurations;
   bool mSentInput;
   GMPVideoCodecType mCodecType;
   // Frames waiting on allocation
   std::deque<WidevineVideoFrame> mFrameAllocationQueue;
   // Number of calls of ReturnOutput currently in progress.
   int32_t mReturnOutputCallDepth;
   // If we're waiting to drain. Used to prevent drain completing while
--- a/dom/media/platforms/wmf/WMFDecoderModule.cpp
+++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp
@@ -205,32 +205,32 @@ bool
 WMFDecoderModule::Supports(const TrackInfo& aTrackInfo,
                            DecoderDoctorDiagnostics* aDiagnostics) const
 {
   if ((aTrackInfo.mMimeType.EqualsLiteral("audio/mp4a-latm") ||
        aTrackInfo.mMimeType.EqualsLiteral("audio/mp4")) &&
        WMFDecoderModule::HasAAC()) {
     return true;
   }
-  if (MP4Decoder::IsH264(aTrackInfo.mMimeType) &&
-      WMFDecoderModule::HasH264() &&
-      !MediaPrefs::PDMWMFAllowUnsupportedResolutions()) {
-    const VideoInfo* videoInfo = aTrackInfo.GetAsVideoInfo();
-    MOZ_ASSERT(videoInfo);
-    // Check Windows format constraints, based on:
-    // https://msdn.microsoft.com/en-us/library/windows/desktop/dd797815(v=vs.85).aspx
-    if (IsWin8OrLater()) {
-      // Windows >7 supports at most 4096x2304.
-      if (videoInfo->mImage.width > 4096 || videoInfo->mImage.height > 2304) {
-        return false;
-      }
-    } else {
-      // Windows <=7 supports at most 1920x1088.
-      if (videoInfo->mImage.width > 1920 || videoInfo->mImage.height > 1088) {
-        return false;
+  if (MP4Decoder::IsH264(aTrackInfo.mMimeType) && WMFDecoderModule::HasH264()) {
+    if (!MediaPrefs::PDMWMFAllowUnsupportedResolutions()) {
+      const VideoInfo* videoInfo = aTrackInfo.GetAsVideoInfo();
+      MOZ_ASSERT(videoInfo);
+      // Check Windows format constraints, based on:
+      // https://msdn.microsoft.com/en-us/library/windows/desktop/dd797815(v=vs.85).aspx
+      if (IsWin8OrLater()) {
+        // Windows >7 supports at most 4096x2304.
+        if (videoInfo->mImage.width > 4096 || videoInfo->mImage.height > 2304) {
+          return false;
+        }
+      } else {
+        // Windows <=7 supports at most 1920x1088.
+        if (videoInfo->mImage.width > 1920 || videoInfo->mImage.height > 1088) {
+          return false;
+        }
       }
     }
     return true;
   }
   if (aTrackInfo.mMimeType.EqualsLiteral("audio/mpeg") &&
       CanCreateWMFDecoder<CLSID_CMP3DecMediaObject>()) {
     return true;
   }
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -713,18 +713,16 @@ tags=msg capturestream
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_waitingforkey.html]
 skip-if = toolkit == 'android' # bug 1149374
 [test_empty_resource.html]
 [test_error_in_video_document.html]
 [test_error_on_404.html]
 [test_fastSeek.html]
 [test_fastSeek-forwards.html]
-[test_gmp_playback.html]
-skip-if = (os != 'win' || os_version == '5.1') # Only gmp-clearkey on Windows Vista and later decodes
 [test_imagecapture.html]
 [test_info_leak.html]
 [test_invalid_reject.html]
 [test_invalid_reject_play.html]
 [test_invalid_seek.html]
 [test_load.html]
 [test_load_candidates.html]
 [test_load_same_resource.html]
deleted file mode 100644
--- a/dom/media/test/test_gmp_playback.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test playback of media files that should play OK</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-  <script type="text/javascript" src="manifest.js"></script>
-</head>
-<body>
-<div id="log"></div>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-
-function startTest() {
-  var v = document.createElement("video");
-  ok(v.canPlayType('video/mp4; codecs="avc1.64000d,mp4a.40.2"') != "",
-     "Should be able to play MP4/H.264/AAC via <video> using GMP");
-  v.src = "short.mp4";
-  v.addEventListener("ended", function(event) {
-    ok(true, "Reached end");
-    SimpleTest.finish();
-  });
-  document.body.appendChild(v);
-  v.play();
-}
-
-var testPrefs = [
-  ['media.gmp.decoder.aac', 1],  // gmp-clearkey
-  ['media.gmp.decoder.h264', 1], // gmp-clearkey
-  ['media.gmp.decoder.enabled', true]
-];
-
-SpecialPowers.pushPrefEnv({'set': testPrefs}, startTest);
-
-</script>
-</pre>
-</body>
-</html>
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -111,17 +111,16 @@ PluginModuleChild::CreateForContentProce
 }
 
 PluginModuleChild::PluginModuleChild(bool aIsChrome)
   : mLibrary(0)
   , mPluginFilename("")
   , mQuirks(QUIRKS_NOT_INITIALIZED)
   , mIsChrome(aIsChrome)
   , mHasShutdown(false)
-  , mTransport(nullptr)
   , mShutdownFunc(0)
   , mInitializeFunc(0)
 #if defined(OS_WIN) || defined(OS_MACOSX)
   , mGetEntryPointsFunc(0)
 #elif defined(MOZ_WIDGET_GTK)
   , mNestedLoopTimerId(0)
 #endif
 #ifdef OS_WIN
@@ -197,18 +196,16 @@ bool
 PluginModuleChild::InitForContent(base::ProcessId aParentPid,
                                   MessageLoop* aIOLoop,
                                   IPC::Channel* aChannel)
 {
     if (!CommonInit(aParentPid, aIOLoop, aChannel)) {
         return false;
     }
 
-    mTransport = aChannel;
-
     mLibrary = GetChrome()->mLibrary;
     mFunctions = GetChrome()->mFunctions;
 
     return true;
 }
 
 mozilla::ipc::IPCResult
 PluginModuleChild::RecvDisableFlashProtectedMode()
--- a/dom/plugins/ipc/PluginModuleChild.h
+++ b/dom/plugins/ipc/PluginModuleChild.h
@@ -268,17 +268,16 @@ private:
 #endif
 
     PRLibrary* mLibrary;
     nsCString mPluginFilename; // UTF8
     int mQuirks;
 
     bool mIsChrome;
     bool mHasShutdown; // true if NP_Shutdown has run
-    Transport* mTransport;
 
     // we get this from the plugin
     NP_PLUGINSHUTDOWN mShutdownFunc;
 #if defined(OS_LINUX) || defined(OS_BSD)
     NP_PLUGINUNIXINIT mInitializeFunc;
 #elif defined(OS_WIN) || defined(OS_MACOSX)
     NP_PLUGININIT mInitializeFunc;
     NP_GETENTRYPOINTS mGetEntryPointsFunc;
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -632,38 +632,38 @@ U2FRegisterRunnable::Run()
       prepPromiseList.AppendElement(compTask->Execute());
     }
 
     // Treat each call to Promise::All as a work unit, as it completes together
     status->WaitGroupAdd();
 
     U2FPrepPromise::All(mAbstractMainThread, prepPromiseList)
     ->Then(mAbstractMainThread, __func__,
-      [status] (const nsTArray<Authenticator>& aTokens) {
+      [&status] (const nsTArray<Authenticator>& aTokens) {
         MOZ_LOG(gU2FLog, LogLevel::Debug,
                 ("ALL: None of the RegisteredKeys were recognized. n=%d",
                  aTokens.Length()));
 
         status->WaitGroupDone();
       },
-      [status] (ErrorCode aErrorCode) {
+      [&status] (ErrorCode aErrorCode) {
         status->Stop(aErrorCode);
         status->WaitGroupDone();
     });
   }
 
   // Wait for all the IsRegistered tasks to complete
   status->WaitGroupWait();
 
   // Check to see whether we're supposed to stop, because one of the keys was
   // recognized.
   if (status->IsStopped()) {
     status->WaitGroupAdd();
     mAbstractMainThread->Dispatch(NS_NewRunnableFunction(
-      [status, this] () {
+      [&status, this] () {
         RegisterResponse response;
         response.mErrorCode.Construct(
             static_cast<uint32_t>(status->GetErrorCode()));
         SendResponse(response);
         status->WaitGroupDone();
       }
     ));
 
@@ -708,45 +708,43 @@ U2FRegisterRunnable::Run()
       RefPtr<U2FRegisterTask> registerTask = new U2FRegisterTask(mOrigin, mAppId,
                                                                  token, appParam,
                                                                  challengeParam,
                                                                  req,
                                                                  mAbstractMainThread);
       status->WaitGroupAdd();
 
       registerTask->Execute()->Then(mAbstractMainThread, __func__,
-        [status, this] (nsString aResponse) {
-          if (status->IsStopped()) {
-            return;
+        [&status, this] (nsString aResponse) {
+          if (!status->IsStopped()) {
+            status->Stop(ErrorCode::OK, aResponse);
           }
-          status->Stop(ErrorCode::OK, aResponse);
           status->WaitGroupDone();
         },
-        [status, this] (ErrorCode aErrorCode) {
-          if (status->IsStopped()) {
-            return;
+        [&status, this] (ErrorCode aErrorCode) {
+          if (!status->IsStopped()) {
+            status->Stop(aErrorCode);
           }
-          status->Stop(aErrorCode);
           status->WaitGroupDone();
      });
     }
   }
 
   // Wait until the first key is successfuly generated
   status->WaitGroupWait();
 
   // If none of the tasks completed, then nothing could satisfy.
   if (!status->IsStopped()) {
     status->Stop(ErrorCode::BAD_REQUEST);
   }
 
   // Transmit back to the JS engine from the Main Thread
   status->WaitGroupAdd();
   mAbstractMainThread->Dispatch(NS_NewRunnableFunction(
-    [status, this] () {
+    [&status, this] () {
       RegisterResponse response;
       if (status->GetErrorCode() == ErrorCode::OK) {
         response.Init(status->GetResponse());
       } else {
         response.mErrorCode.Construct(
             static_cast<uint32_t>(status->GetErrorCode()));
       }
       SendResponse(response);
@@ -896,45 +894,43 @@ U2FSignRunnable::Run()
       RefPtr<U2FSignTask> signTask = new U2FSignTask(mOrigin, mAppId,
                                                      key.mVersion, token,
                                                      appParam, challengeParam,
                                                      mClientData, keyHandle,
                                                      mAbstractMainThread);
       status->WaitGroupAdd();
 
       signTask->Execute()->Then(mAbstractMainThread, __func__,
-        [status, this] (nsString aResponse) {
-          if (status->IsStopped()) {
-            return;
+        [&status, this] (nsString aResponse) {
+          if (!status->IsStopped()) {
+            status->Stop(ErrorCode::OK, aResponse);
           }
-          status->Stop(ErrorCode::OK, aResponse);
           status->WaitGroupDone();
         },
-        [status, this] (ErrorCode aErrorCode) {
-          if (status->IsStopped()) {
-            return;
+        [&status, this] (ErrorCode aErrorCode) {
+          if (!status->IsStopped()) {
+            status->Stop(aErrorCode);
           }
-          status->Stop(aErrorCode);
           status->WaitGroupDone();
       });
     }
   }
 
   // Wait for the authenticators to finish
   status->WaitGroupWait();
 
   // If none of the tasks completed, then nothing could satisfy.
   if (!status->IsStopped()) {
     status->Stop(ErrorCode::DEVICE_INELIGIBLE);
   }
 
   // Transmit back to the JS engine from the Main Thread
   status->WaitGroupAdd();
   mAbstractMainThread->Dispatch(NS_NewRunnableFunction(
-    [status, this] () {
+    [&status, this] () {
       SignResponse response;
       if (status->GetErrorCode() == ErrorCode::OK) {
         response.Init(status->GetResponse());
       } else {
         response.mErrorCode.Construct(
           static_cast<uint32_t>(status->GetErrorCode()));
       }
       SendResponse(response);
--- a/dom/u2f/U2F.h
+++ b/dom/u2f/U2F.h
@@ -165,16 +165,17 @@ private:
 };
 
 // Mediate inter-thread communication for multiple authenticators being queried
 // in concert. Operates as a cyclic buffer with a stop-work method.
 class U2FStatus
 {
 public:
   U2FStatus();
+  U2FStatus(const U2FStatus&) = delete;
 
   void WaitGroupAdd();
   void WaitGroupDone();
   void WaitGroupWait();
 
   void Stop(const ErrorCode aErrorCode);
   void Stop(const ErrorCode aErrorCode, const nsAString& aResponse);
   bool IsStopped();
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/README.md
@@ -0,0 +1,8 @@
+Note:
+
+While conceptually similar to the tests for Web Authentication (dom/webauthn),
+the tests for U2F require an iframe while `window.u2f` remains hidden behind a
+preference, though WebAuthn does not. The reason is that the `window` object
+doesn't mutate upon a call by SpecialPowers.setPrefEnv() the way that the
+`navigator` objects do, rather you have to load a different page with a different
+`window` object for the preference change to be honored.
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/frame_multiple_keys.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <script type="text/javascript" src="u2futil.js"></script>
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+
+function keyHandleFromRegResponse(aRegResponse) {
+  // Parse the response data from the U2F token
+  var registrationData = base64ToBytesUrlSafe(aRegResponse.registrationData);
+  local_is(registrationData[0], 0x05, "Reserved byte is correct")
+
+  var keyHandleLength = registrationData[66];
+  var keyHandleBytes = registrationData.slice(67, 67 + keyHandleLength);
+
+  return {
+    version: "U2F_V2",
+    keyHandle: bytesToBase64UrlSafe(keyHandleBytes),
+  };
+}
+
+local_setParentOrigin("https://example.com");
+local_expectThisManyTests(1);
+
+// Ensure the SpecialPowers push worked properly
+local_isnot(window.u2f, undefined, "U2F API endpoint must exist");
+
+var challenge = new Uint8Array(16);
+window.crypto.getRandomValues(challenge);
+
+var regRequest = {
+  version: "U2F_V2",
+  challenge: bytesToBase64UrlSafe(challenge),
+};
+
+var testState = {
+  key1: null,
+  key2: null,
+}
+
+// Get two valid keys and present them
+window.u2f.register(window.location.origin, [regRequest], [], function(aRegResponse) {
+  testState.key1 = keyHandleFromRegResponse(aRegResponse);
+  registerSecondKey();
+});
+
+// Get the second key...
+// It's OK to repeat the regRequest; not material for this test
+function registerSecondKey() {
+  window.u2f.register(window.location.origin, [regRequest], [], function(aRegResponse) {
+    testState.key2 = keyHandleFromRegResponse(aRegResponse);
+    testSignSingleKey();
+  });
+}
+
+// It should also work with just one key
+function testSignSingleKey() {
+  window.u2f.sign(window.location.origin, bytesToBase64UrlSafe(challenge),
+                  [testState.key1], function(aSignResponse) {
+    local_is(aSignResponse.errorCode, 0, "The signing did not error with one key");
+    local_isnot(aSignResponse.clientData, undefined, "The signing provided clientData with one key");
+
+    testSignDual();
+  });
+}
+
+function testSignDual() {
+  // It's OK to sign with either one
+  window.u2f.sign(window.location.origin, bytesToBase64UrlSafe(challenge),
+                  [testState.key1, testState.key2], function(aSignResponse) {
+    local_is(aSignResponse.errorCode, 0, "The signing did not error with two keys");
+    local_isnot(aSignResponse.clientData, undefined, "The signing provided clientData with two keys");
+
+    local_completeTest();
+  });
+}
+</script>
+
+</body>
+</html>
\ No newline at end of file
--- a/dom/u2f/tests/mochitest.ini
+++ b/dom/u2f/tests/mochitest.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 support-files =
   frame_appid_facet.html
   frame_appid_facet_insecure.html
   frame_appid_facet_subdomain.html
   frame_no_token.html
   frame_register.html
   frame_register_sign.html
+  frame_multiple_keys.html
   pkijs/asn1.js
   pkijs/common.js
   pkijs/x509_schema.js
   pkijs/x509_simpl.js
   u2futil.js
 
 # Feature does not function on e10s (Disabled in Bug 1297552)
 [test_util_methods.html]
@@ -22,8 +23,11 @@ skip-if = !e10s
 [test_register_sign.html]
 skip-if = !e10s
 [test_appid_facet.html]
 skip-if = !e10s
 [test_appid_facet_insecure.html]
 skip-if = !e10s
 [test_appid_facet_subdomain.html]
 skip-if = !e10s
+[test_multiple_keys.html]
+skip-if = !e10s
+scheme = https
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/test_multiple_keys.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <title>Test for Multiple Keys for FIDO U2F</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="u2futil.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<h1>Test Multiple Keys for FIDO U2F</h1>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1333592">Mozilla Bug 1333592</a>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+
+ /** Test for XBL scope behavior. **/
+SimpleTest.waitForExplicitFinish();
+
+// Embed the real test. It will take care of everything else.
+//
+// This is necessary since the U2F object on window is hidden behind a preference
+// and window won't pick up changes by pref without a reload.
+SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
+                                   ["security.webauth.u2f_enable_softtoken", true],
+                                   ["security.webauth.u2f_enable_usbtoken", false]]},
+function() {
+  // listen for messages from the test harness
+  window.addEventListener("message", handleEventMessage);
+  document.getElementById('testing_frame').src = "frame_multiple_keys.html";
+});
+</script>
+
+<div id="framediv">
+  <iframe id="testing_frame"></iframe>
+</div>
+
+</body>
+</html>
--- a/dom/u2f/tests/u2futil.js
+++ b/dom/u2f/tests/u2futil.js
@@ -1,11 +1,16 @@
 // Used by local_addTest() / local_completeTest()
 var _countCompletions = 0;
 var _expectedCompletions = 0;
+var _parentOrigin = "http://mochi.test:8888";
+
+function local_setParentOrigin(aOrigin) {
+  _parentOrigin = aOrigin;
+}
 
 function handleEventMessage(event) {
   if ("test" in event.data) {
     let summary = event.data.test + ": " + event.data.msg;
     log(event.data.status + ": " + summary);
     ok(event.data.status, summary);
   } else if ("done" in event.data) {
     SimpleTest.finish();
@@ -35,17 +40,17 @@ function local_isnot(value, expected, me
     local_ok(true, message);
   } else {
     local_ok(false, message + " unexpectedly: " + value + " === " + expected);
   }
 }
 
 function local_ok(expression, message) {
   let body = {"test": this.location.pathname, "status":expression, "msg": message}
-  parent.postMessage(body, "http://mochi.test:8888");
+  parent.postMessage(body, _parentOrigin);
 }
 
 function local_doesThrow(fn, name) {
   let gotException = false;
   try {
     fn();
   } catch (ex) { gotException = true; }
   local_ok(gotException, name);
@@ -65,17 +70,17 @@ function local_completeTest() {
     local_finished();
   }
   if (_countCompletions > _expectedCompletions) {
     local_ok(false, "Error: local_completeTest called more than local_addTest.");
   }
 }
 
 function local_finished() {
-  parent.postMessage({"done":true}, "http://mochi.test:8888");
+  parent.postMessage({"done":true}, _parentOrigin);
 }
 
 function string2buffer(str) {
   return (new Uint8Array(str.length)).map((x, i) => str.charCodeAt(i));
 }
 
 function buffer2string(buf) {
   let str = "";
--- a/ipc/chromium/chromium-config.mozbuild
+++ b/ipc/chromium/chromium-config.mozbuild
@@ -7,17 +7,16 @@
 LOCAL_INCLUDES += [
     '!/ipc/ipdl/_ipdlheaders',
     '/ipc/chromium/src',
     '/ipc/glue',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     OS_LIBS += [
-        'psapi',
         'shell32',
         'dbghelp',
     ]
 
     DEFINES.update({
         'UNICODE': True,
         '_UNICODE': True,
         '_CRT_RAND_S': True,
--- a/js/src/build.rs
+++ b/js/src/build.rs
@@ -35,17 +35,16 @@ fn main() {
         .status()
         .expect("Should spawn autospider OK");
     assert!(result.success(), "autospider should exit OK");
 
     println!("cargo:rustc-link-search=native={}/js/src", out_dir);
 
     if target.contains("windows") {
         println!("cargo:rustc-link-lib=winmm");
-        println!("cargo:rustc-link-lib=psapi");
         if target.contains("gnu") {
             println!("cargo:rustc-link-lib=stdc++");
         }
     } else {
         println!("cargo:rustc-link-lib=stdc++");
     }
 
     println!("cargo:rustc-link-lib=static=js_static");
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -128,17 +128,17 @@ else
 fi
 
 MOZ_TOOL_VARIABLES
 
 dnl Special win32 checks
 dnl ========================================================
 
 # Target the Windows 8.1 SDK by default
-WINVER=502
+WINVER=601
 
 case "$target" in
 *-mingw*)
     if test "$GCC" != "yes"; then
         # Check to see if we are really running in a msvc environemnt
         _WIN32_MSVC=1
 
         # Make sure compilers are valid
@@ -247,18 +247,18 @@ case "$target" in
                 "$_WINDRES_RELEASE_VERSION" -lt "$WINDRES_RELEASE_VERSION"
         then
             AC_MSG_ERROR([windres version $WINDRES_VERSION or higher is required to build.])
         fi
     fi # !GNU_CC
 
     AC_DEFINE_UNQUOTED(WINVER,0x$WINVER)
     AC_DEFINE_UNQUOTED(_WIN32_WINNT,0x$WINVER)
-    # Require OS features provided by IE 6.0 SP2 (XP SP2)
-    AC_DEFINE_UNQUOTED(_WIN32_IE,0x0603)
+    # Require OS features provided by IE 8.0 (Win7)
+    AC_DEFINE_UNQUOTED(_WIN32_IE,0x0800)
 
     ;;
 esac
 
 if test -n "$_WIN32_MSVC"; then
     SKIP_PATH_CHECKS=1
     SKIP_COMPILER_CHECKS=1
     SKIP_LIBRARY_CHECKS=1
@@ -654,17 +654,17 @@ case "$target" in
         MKSHLIB='$(CXX) $(DSO_LDOPTS) -o $@'
         MKCSHLIB='$(CC) $(DSO_LDOPTS) -o $@'
         RC='$(WINDRES)'
         # Use static libgcc and libstdc++
         LDFLAGS="$LDFLAGS -static"
         # Use temp file for windres (bug 213281)
         RCFLAGS='-O coff --use-temp-file'
         # mingw doesn't require kernel32, user32, and advapi32 explicitly
-        LIBS="$LIBS -lgdi32 -lwinmm -lwsock32 -lpsapi"
+        LIBS="$LIBS -lgdi32 -lwinmm -lwsock32"
         MOZ_FIX_LINK_PATHS=
         DLL_PREFIX=
         IMPORT_LIB_SUFFIX=a
 
         WIN32_CONSOLE_EXE_LDFLAGS=-mconsole
         WIN32_GUI_EXE_LDFLAGS=-mwindows
     else
         TARGET_COMPILER_ABI=msvc
@@ -784,17 +784,17 @@ case "$target" in
             CXXFLAGS="$CXXFLAGS -Wno-unused-local-typedef"
             # jemalloc uses __declspec(allocator) as a profiler hint,
             # which clang-cl doesn't understand.
             CXXFLAGS="$CXXFLAGS -Wno-ignored-attributes"
         fi
         # make 'foo == bar;' error out
         CFLAGS="$CFLAGS -we4553"
         CXXFLAGS="$CXXFLAGS -we4553"
-        LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib psapi.lib"
+        LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib"
         MOZ_DEBUG_LDFLAGS='-DEBUG -DEBUGTYPE:CV'
         WARNINGS_AS_ERRORS='-WX'
         MOZ_OPTIMIZE_FLAGS="-O2"
         MOZ_FIX_LINK_PATHS=
         LDFLAGS="$LDFLAGS -LARGEADDRESSAWARE -NXCOMPAT"
         if test -z "$DEVELOPER_OPTIONS"; then
             LDFLAGS="$LDFLAGS -RELEASE"
         fi
--- a/js/src/vm/Stopwatch.cpp
+++ b/js/src/vm/Stopwatch.cpp
@@ -314,17 +314,18 @@ AutoStopwatch::exit()
         // `getCPU()`.  We hope that this will happen rarely
         // enough that the impact on our statistics will remain
         // limited.
         const cpuid_t cpuEnd = this->getCPU();
         if (isSameCPU(cpuStart_, cpuEnd)) {
             const uint64_t cyclesEnd = getCycles(runtime);
             cyclesDelta = cyclesEnd - cyclesStart_; // Always >= 0 by definition of `getCycles`.
         }
-#if WINVER >= 0x600
+// Temporary disable untested code path.
+#if 0 // WINVER >= 0x600
         updateTelemetry(cpuStart_, cpuEnd);
 #elif defined(__linux__)
         updateTelemetry(cpuStart_, cpuEnd);
 #endif // WINVER >= 0x600 || _linux__
     }
 
     uint64_t CPOWTimeDelta = 0;
     if (isMonitoringCPOW_ && runtime->performanceMonitoring.isMonitoringCPOW()) {
@@ -406,31 +407,33 @@ uint64_t
 AutoStopwatch::getCycles(JSRuntime* runtime) const
 {
     return runtime->performanceMonitoring.monotonicReadTimestampCounter();
 }
 
 cpuid_t inline
 AutoStopwatch::getCPU() const
 {
-#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA
+// Temporary disable untested code path.
+#if 0 // defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA
     PROCESSOR_NUMBER proc;
     GetCurrentProcessorNumberEx(&proc);
 
     cpuid_t result(proc.Group, proc.Number);
     return result;
 #else
     return {};
 #endif // defined(XP_WIN)
 }
 
 bool inline
 AutoStopwatch::isSameCPU(const cpuid_t& a, const cpuid_t& b) const
 {
-#if defined(XP_WIN)  && WINVER >= _WIN32_WINNT_VISTA
+// Temporary disable untested code path.
+#if 0 // defined(XP_WIN)  && WINVER >= _WIN32_WINNT_VISTA
     return a.group_ == b.group_ && a.number_ == b.number_;
 #else
     return true;
 #endif
 }
 
 PerformanceGroup::PerformanceGroup()
     : recentCycles_(0)
--- a/js/src/vm/Stopwatch.h
+++ b/js/src/vm/Stopwatch.h
@@ -294,17 +294,18 @@ struct PerformanceMonitoring {
 
     /**
      * The highest value of the timestamp counter encountered
      * during this iteration.
      */
     uint64_t highestTimestampCounter_;
 };
 
-#if WINVER >= 0x0600
+// Temporary disable untested code path.
+#if 0 // WINVER >= 0x0600
 struct cpuid_t {
     uint16_t group_;
     uint8_t number_;
     cpuid_t(uint16_t group, uint8_t number)
         : group_(group),
           number_(number)
     { }
     cpuid_t()
--- a/media/webrtc/trunk/build/common.gypi
+++ b/media/webrtc/trunk/build/common.gypi
@@ -3322,17 +3322,16 @@
       },  # target_defaults
     }],  # OS=="ios"
     ['OS=="win"', {
       'target_defaults': {
         'defines': [
           'WIN32',
           '_WINDOWS',
           'NOMINMAX',
-          'PSAPI_VERSION=1',
           '_CRT_RAND_S',
           'CERT_CHAIN_PARA_HAS_EXTRA_FIELDS',
           'WIN32_LEAN_AND_MEAN',
           '_ATL_NO_OPENGL',
         ],
         'conditions': [
           ['build_with_mozilla==0', {
               'defines': [
@@ -3450,17 +3449,16 @@
           'VCLinkerTool': {
             'AdditionalDependencies': [
               'wininet.lib',
               'dnsapi.lib',
               'version.lib',
               'msimg32.lib',
               'ws2_32.lib',
               'usp10.lib',
-              'psapi.lib',
               'dbghelp.lib',
               'winmm.lib',
               'shlwapi.lib',
             ],
 
             'conditions': [
               ['msvs_express', {
                 # Explicitly required when using the ATL with express
--- a/mobile/android/components/extensions/ext-pageAction.js
+++ b/mobile/android/components/extensions/ext-pageAction.js
@@ -127,17 +127,17 @@ extensions.on("shutdown", (type, extensi
 /* eslint-enable mozilla/balanced-listeners */
 
 extensions.registerSchemaAPI("pageAction", "addon_parent", context => {
   let {extension} = context;
   return {
     pageAction: {
       onClicked: new SingletonEventManager(context, "pageAction.onClicked", fire => {
         let listener = (event) => {
-          fire();
+          fire.async();
         };
         pageActionMap.get(extension).on("click", listener);
         return () => {
           pageActionMap.get(extension).off("click", listener);
         };
       }).api(),
 
       show(tabId) {
--- a/old-configure.in
+++ b/old-configure.in
@@ -198,17 +198,17 @@ if test -n "$MOZ_WINCONSOLE"; then
 fi
 
 MOZ_TOOL_VARIABLES
 
 dnl ========================================================
 dnl Special win32 checks
 dnl ========================================================
 
-WINVER=502
+WINVER=601
 
 case "$target" in
 *-mingw*)
     if test "$GCC" != "yes"; then
         # Check to see if we are really running in a msvc environemnt
         _WIN32_MSVC=1
         AC_CHECK_PROGS(MIDL, midl)
 
@@ -393,18 +393,18 @@ case "$target" in
 
         # strsafe.h on mingw uses macros for function deprecation that pollutes namespace
         # causing problems with local implementations with the same name.
         AC_DEFINE(STRSAFE_NO_DEPRECATE)
     fi # !GNU_CC
 
     AC_DEFINE_UNQUOTED(WINVER,0x$WINVER)
     AC_DEFINE_UNQUOTED(_WIN32_WINNT,0x$WINVER)
-    # Require OS features provided by IE 6.0 SP2 (XP SP2)
-    AC_DEFINE_UNQUOTED(_WIN32_IE,0x0603)
+    # Require OS features provided by IE 8.0 (Win7)
+    AC_DEFINE_UNQUOTED(_WIN32_IE,0x0800)
 
     ;;
 esac
 
 if test -n "$_WIN32_MSVC"; then
     SKIP_PATH_CHECKS=1
     SKIP_COMPILER_CHECKS=1
     SKIP_LIBRARY_CHECKS=1
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -1428,17 +1428,17 @@ class RecursiveMakeBackend(CommonBackend
 
         for k, manifest in manifests.items():
             with self._write_file(mozpath.join(man_dir, k)) as fh:
                 manifest.write(fileobj=fh)
 
     def _write_master_test_manifest(self, path, manifests):
         with self._write_file(path) as master:
             master.write(
-                '; THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT MODIFY BY HAND.\n\n')
+                '# THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT MODIFY BY HAND.\n\n')
 
             for manifest in sorted(manifests):
                 master.write('[include:%s]\n' % manifest)
 
     class Substitution(object):
         """BaseConfigSubstitution-like class for use with _create_makefile."""
         __slots__ = (
             'input_path',
--- a/python/mozbuild/mozbuild/configure/__init__.py
+++ b/python/mozbuild/mozbuild/configure/__init__.py
@@ -26,122 +26,140 @@ from mozbuild.configure.help import Help
 from mozbuild.configure.util import (
     ConfigureOutputHandler,
     getpreferredencoding,
     LineIO,
 )
 from mozbuild.util import (
     exec_,
     memoize,
-    memoized_property,
     ReadOnlyDict,
     ReadOnlyNamespace,
 )
 
 import mozpack.path as mozpath
 
 
 class ConfigureError(Exception):
     pass
 
 
 class SandboxDependsFunction(object):
     '''Sandbox-visible representation of @depends functions.'''
+    def __init__(self, unsandboxed):
+        self._or = unsandboxed.__or__
+
     def __call__(self, *arg, **kwargs):
         raise ConfigureError('The `%s` function may not be called'
                              % self.__name__)
 
+    def __or__(self, other):
+        if not isinstance(other, SandboxDependsFunction):
+            raise ConfigureError('Can only do binary arithmetic operations '
+                                 'with another @depends function.')
+        return self._or(other).sandboxed
+
 
 class DependsFunction(object):
     __slots__ = (
-        'func', 'dependencies', 'when', 'sandboxed', 'sandbox', '_result')
+        '_func', '_name', 'dependencies', 'when', 'sandboxed', 'sandbox',
+        '_result')
 
     def __init__(self, sandbox, func, dependencies, when=None):
         assert isinstance(sandbox, ConfigureSandbox)
-        self.func = func
+        assert not inspect.isgeneratorfunction(func)
+        self._func = func
+        self._name = func.__name__
         self.dependencies = dependencies
-        self.sandboxed = wraps(func)(SandboxDependsFunction())
+        self.sandboxed = wraps(func)(SandboxDependsFunction(self))
         self.sandbox = sandbox
         self.when = when
         sandbox._depends[self.sandboxed] = self
 
         # Only @depends functions with a dependency on '--help' are executed
         # immediately. Everything else is queued for later execution.
         if sandbox._help_option in dependencies:
             sandbox._value_for(self)
         elif not sandbox._help:
             sandbox._execution_queue.append((sandbox._value_for, (self,)))
 
     @property
     def name(self):
-        return self.func.__name__
+        return self._name
+
+    @name.setter
+    def name(self, value):
+        self._name = value
 
     @property
     def sandboxed_dependencies(self):
         return [
             d.sandboxed if isinstance(d, DependsFunction) else d
             for d in self.dependencies
         ]
 
-    @memoized_property
-    def result(self):
-        if self.when and not self.sandbox._value_for(self.when):
+    @memoize
+    def result(self, need_help_dependency=False):
+        if self.when and not self.sandbox._value_for(self.when,
+                                                     need_help_dependency):
             return None
 
-        resolved_args = [self.sandbox._value_for(d) for d in self.dependencies]
-        return self.func(*resolved_args)
+        resolved_args = [self.sandbox._value_for(d, need_help_dependency)
+                         for d in self.dependencies]
+        return self._func(*resolved_args)
 
     def __repr__(self):
         return '<%s.%s %s(%s)>' % (
             self.__class__.__module__,
             self.__class__.__name__,
             self.name,
             ', '.join(repr(d) for d in self.dependencies),
         )
 
+    def __or__(self, other):
+        if isinstance(other, SandboxDependsFunction):
+            other = self.sandbox._depends.get(other)
+        assert isinstance(other, DependsFunction)
+        assert self.sandbox is other.sandbox
+        return CombinedDependsFunction(self.sandbox, self.first_true,
+                                       (self, other))
+
+    @staticmethod
+    def first_true(iterable):
+        # Like the builtin any(), but returns the first element that is true,
+        # instead of True. If none are true, returns the last element.
+        for i in iterable:
+            if i:
+                return i
+        return i
+
 
 class CombinedDependsFunction(DependsFunction):
     def __init__(self, sandbox, func, dependencies):
-        @memoize
-        @wraps(func)
-        def wrapper(*args):
-            return func(args)
-
         flatten_deps = []
         for d in dependencies:
-            if isinstance(d, CombinedDependsFunction) and d.func == wrapper:
+            if isinstance(d, CombinedDependsFunction) and d._func is func:
                 for d2 in d.dependencies:
                     if d2 not in flatten_deps:
                         flatten_deps.append(d2)
             elif d not in flatten_deps:
                 flatten_deps.append(d)
 
-        # Automatically add a --help dependency if one of the dependencies
-        # depends on it.
-        for d in flatten_deps:
-            if (isinstance(d, DependsFunction) and
-                sandbox._help_option in d.dependencies):
-                flatten_deps.insert(0, sandbox._help_option)
-                break
+        super(CombinedDependsFunction, self).__init__(
+            sandbox, func, flatten_deps)
 
-        super(CombinedDependsFunction, self).__init__(
-            sandbox, wrapper, flatten_deps)
-
-    @memoized_property
-    def result(self):
-        # Ignore --help for the combined result
-        deps = self.dependencies
-        if deps[0] == self.sandbox._help_option:
-            deps = deps[1:]
-        resolved_args = [self.sandbox._value_for(d) for d in deps]
-        return self.func(*resolved_args)
+    @memoize
+    def result(self, need_help_dependency=False):
+        resolved_args = (self.sandbox._value_for(d, need_help_dependency)
+                         for d in self.dependencies)
+        return self._func(resolved_args)
 
     def __eq__(self, other):
         return (isinstance(other, self.__class__) and
-                self.func == other.func and
+                self._func is other._func and
                 set(self.dependencies) == set(other.dependencies))
 
     def __ne__(self, other):
         return not self == other
 
 class SandboxedGlobal(dict):
     '''Identifiable dict type for use as function global'''
 
@@ -384,17 +402,17 @@ class ConfigureSandbox(dict):
 
         elif (not isinstance(value, SandboxDependsFunction) and
                 value not in self._templates and
                 not (inspect.isclass(value) and issubclass(value, Exception))):
             raise KeyError('Cannot assign `%s` because it is neither a '
                            '@depends nor a @template' % key)
 
         if isinstance(value, SandboxDependsFunction):
-            self._depends[value].func.__name__ = key
+            self._depends[value].name = key
 
         return super(ConfigureSandbox, self).__setitem__(key, value)
 
     def _resolve(self, arg, need_help_dependency=True):
         if isinstance(arg, SandboxDependsFunction):
             return self._value_for_depends(self._depends[arg],
                                            need_help_dependency)
         return arg
@@ -410,18 +428,17 @@ class ConfigureSandbox(dict):
 
         elif isinstance(obj, Option):
             return self._value_for_option(obj)
 
         assert False
 
     @memoize
     def _value_for_depends(self, obj, need_help_dependency=False):
-        assert not inspect.isgeneratorfunction(obj.func)
-        return obj.result
+        return obj.result(need_help_dependency)
 
     @memoize
     def _value_for_option(self, option):
         implied = {}
         for implied_option in self._implied_options[:]:
             if implied_option.name not in (option.name, option.env):
                 continue
             self._implied_options.remove(implied_option)
--- a/python/mozbuild/mozbuild/configure/lint.py
+++ b/python/mozbuild/mozbuild/configure/lint.py
@@ -35,17 +35,17 @@ class LintSandbox(ConfigureSandbox):
 
         for dep in self._depends.itervalues():
             self._check_dependencies(dep)
 
     def _check_dependencies(self, obj):
         if isinstance(obj, CombinedDependsFunction) or obj in (self._always,
                                                                self._never):
             return
-        func, glob = self.unwrap(obj.func)
+        func, glob = self.unwrap(obj._func)
         loc = '%s:%d' % (func.func_code.co_filename,
                          func.func_code.co_firstlineno)
         func_args = inspect.getargspec(func)
         if func_args.keywords:
             raise ConfigureError(
                 '%s: Keyword arguments are not allowed in @depends functions'
                 % loc
             )
@@ -75,17 +75,17 @@ class LintSandbox(ConfigureSandbox):
 
     def _missing_help_dependency(self, obj):
         if isinstance(obj, CombinedDependsFunction):
             return False
         if isinstance(obj, DependsFunction):
             if (self._help_option in obj.dependencies or
                 obj in (self._always, self._never)):
                 return False
-            func, glob = self.unwrap(obj.func)
+            func, glob = self.unwrap(obj._func)
             # We allow missing --help dependencies for functions that:
             # - don't use @imports
             # - don't have a closure
             # - don't use global variables
             if func in self._imports or func.func_closure:
                 return True
             for op, arg in disassemble_as_iter(func):
                 if op in ('LOAD_GLOBAL', 'STORE_GLOBAL'):
@@ -109,16 +109,23 @@ class LintSandbox(ConfigureSandbox):
                         % (obj.name, arg.name, arg.name))
         elif ((self._help or need_help_dependency) and
               self._missing_help_dependency(obj)):
             raise ConfigureError("Missing @depends for `%s`: '--help'" %
                                  obj.name)
         return super(LintSandbox, self)._value_for_depends(
             obj, need_help_dependency)
 
+    def option_impl(self, *args, **kwargs):
+        result = super(LintSandbox, self).option_impl(*args, **kwargs)
+        when = self._conditions.get(result)
+        if when:
+            self._value_for(when, need_help_dependency=True)
+        return result
+
     def unwrap(self, func):
         glob = func.func_globals
         while func in self._wrapped:
             if isinstance(func.func_globals, SandboxedGlobal):
                 glob = func.func_globals
             func = self._wrapped[func]
         return func, glob
 
--- a/python/mozbuild/mozbuild/mozinfo.py
+++ b/python/mozbuild/mozbuild/mozinfo.py
@@ -91,16 +91,17 @@ def build_dict(config, env=os.environ):
     d['tsan'] = substs.get('MOZ_TSAN') == '1'
     d['telemetry'] = substs.get('MOZ_TELEMETRY_REPORTING') == '1'
     d['tests_enabled'] = substs.get('ENABLE_TESTS') == "1"
     d['bin_suffix'] = substs.get('BIN_SUFFIX', '')
     d['addon_signing'] = substs.get('MOZ_ADDON_SIGNING') == '1'
     d['require_signing'] = substs.get('MOZ_REQUIRE_SIGNING') == '1'
     d['official'] = bool(substs.get('MOZILLA_OFFICIAL'))
     d['updater'] = substs.get('MOZ_UPDATER') == '1'
+    d['artifact'] = substs.get('MOZ_ARTIFACT_BUILDS') == '1'
 
     def guess_platform():
         if d['buildapp'] in ('browser', 'mulet'):
             p = d['os']
             if p == 'mac':
                 p = 'macosx64'
             elif d['bits'] == 64:
                 p = '{}64'.format(p)
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -514,17 +514,17 @@ class TestRecursiveMakeBackend(BackendTe
         tests_dir = mozpath.join(env.topobjdir, '_tests')
         m_master = mozpath.join(tests_dir, 'testing', 'mochitest', 'tests', 'mochitest.ini')
         x_master = mozpath.join(tests_dir, 'xpcshell', 'xpcshell.ini')
         self.assertTrue(os.path.exists(m_master))
         self.assertTrue(os.path.exists(x_master))
 
         lines = [l.strip() for l in open(x_master, 'rt').readlines()]
         self.assertEqual(lines, [
-            '; THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT MODIFY BY HAND.',
+            '# THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT MODIFY BY HAND.',
             '',
             '[include:dir1/xpcshell.ini]',
             '[include:xpcshell.ini]',
         ])
 
         all_tests_path = mozpath.join(env.topobjdir, 'all-tests.pkl')
         self.assertTrue(os.path.exists(all_tests_path))
 
--- a/python/mozbuild/mozbuild/test/configure/lint.py
+++ b/python/mozbuild/mozbuild/test/configure/lint.py
@@ -52,14 +52,14 @@ class Lint(unittest.TestCase):
     def tearDown(self):
         os.chdir(self._curdir)
 
     def lint(self, project):
         sandbox = LintSandbox({
             'OLD_CONFIGURE': os.path.join(topsrcdir, 'old-configure'),
             'MOZCONFIG': os.path.join(os.path.dirname(test_path), 'data',
                                       'empty_mozconfig'),
-        }, ['--enable-project=%s' % project])
+        }, ['--enable-project=%s' % project, '--help'])
         sandbox.run(os.path.join(topsrcdir, 'moz.configure'))
 
 
 if __name__ == '__main__':
     main()
--- a/python/mozbuild/mozbuild/test/configure/test_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_configure.py
@@ -347,17 +347,17 @@ class TestConfigure(unittest.TestCase):
                 def bar():
                     return sys
                 return bar
             bar = foo()'''),
             sandbox
         )
 
         with self.assertRaises(NameError) as e:
-            sandbox._depends[sandbox['bar']].result
+            sandbox._depends[sandbox['bar']].result()
 
         self.assertEquals(e.exception.message,
                           "global name 'sys' is not defined")
 
     def test_apply_imports(self):
         imports = []
 
         class CountApplyImportsSandbox(ConfigureSandbox):
@@ -867,16 +867,35 @@ class TestConfigure(unittest.TestCase):
         '''):
             with self.assertRaises(ConfigureError) as e:
                 self.get_config()
 
             self.assertEquals(e.exception.message,
                               '@depends function needs the same `when` as '
                               'options it depends on')
 
+        with self.moz_configure('''
+            @depends(when=True)
+            def always():
+                return True
+            @depends(when=True)
+            def always2():
+                return True
+            with only_when(always2):
+                option('--with-foo', help='foo', when=always)
+                # include() triggers resolution of its dependencies, and their
+                # side effects.
+                include(depends('--with-foo', when=always)(lambda x: x))
+                # The sandbox should figure that the `when` here is
+                # appropriate. Bad behavior in CombinedDependsFunction.__eq__
+                # made this fail in the past.
+                set_config('FOO', depends('--with-foo', when=always)(lambda x: x))
+        '''):
+            self.get_config()
+
     def test_include_failures(self):
         with self.assertRaises(ConfigureError) as e:
             with self.moz_configure('include("../foo.configure")'):
                 self.get_config()
 
         self.assertEquals(
             e.exception.message,
             'Cannot include `%s` because it is not in a subdirectory of `%s`'
@@ -1263,11 +1282,45 @@ class TestConfigure(unittest.TestCase):
                               '--foo is not available in this configuration')
 
         # And similarly doesn't fail when the condition is true.
         with self.moz_configure('''
             imply_option('--foo', True)
         ''' + moz_configure):
             self.get_config(['--enable-when'])
 
+    def test_depends_or(self):
+        with self.moz_configure('''
+            option('--foo', nargs=1, help='foo')
+            @depends('--foo')
+            def foo(value):
+                return value or None
+
+            option('--bar', nargs=1, help='bar')
+            @depends('--bar')
+            def bar(value):
+                return value
+
+            set_config('FOOBAR', foo | bar)
+        '''):
+            config = self.get_config()
+            self.assertEqual(config, {
+                'FOOBAR': NegativeOptionValue(),
+            })
+
+            config = self.get_config(['--foo=foo'])
+            self.assertEqual(config, {
+                'FOOBAR': PositiveOptionValue(('foo',)),
+            })
+
+            config = self.get_config(['--bar=bar'])
+            self.assertEqual(config, {
+                'FOOBAR': PositiveOptionValue(('bar',)),
+            })
+
+            config = self.get_config(['--foo=foo', '--bar=bar'])
+            self.assertEqual(config, {
+                'FOOBAR': PositiveOptionValue(('foo',)),
+            })
+
 
 if __name__ == '__main__':
     main()
--- a/python/mozbuild/mozbuild/test/configure/test_lint.py
+++ b/python/mozbuild/mozbuild/test/configure/test_lint.py
@@ -109,16 +109,50 @@ class TestLint(unittest.TestCase):
 
                 include(foo)
             '''):
                 self.lint_test()
 
         self.assertEquals(e.exception.message,
                           "Missing @depends for `foo`: '--help'")
 
+        with self.assertRaises(ConfigureError) as e:
+            with self.moz_configure('''
+                option('--foo', help='foo')
+                @depends('--foo')
+                @imports('os')
+                def foo(value):
+                    return value
+
+                @depends(foo)
+                def bar(value):
+                    return value
+
+                include(bar)
+            '''):
+                self.lint_test()
+
+        self.assertEquals(e.exception.message,
+                          "Missing @depends for `foo`: '--help'")
+
+        with self.assertRaises(ConfigureError) as e:
+            with self.moz_configure('''
+                option('--foo', help='foo')
+                @depends('--foo')
+                @imports('os')
+                def foo(value):
+                    return value
+
+                option('--bar', help='bar', when=foo)
+            '''):
+                self.lint_test()
+
+        self.assertEquals(e.exception.message,
+                          "Missing @depends for `foo`: '--help'")
+
         # There is a default restricted `os` module when there is no explicit
         # @imports, and it's fine to use it without a dependency on --help.
         with self.moz_configure('''
             option('--foo', help='foo')
             @depends('--foo')
             def foo(value):
                 os
                 return value
--- a/testing/marionette/harness/marionette_harness/tests/unit-tests.ini
+++ b/testing/marionette/harness/marionette_harness/tests/unit-tests.ini
@@ -1,11 +1,11 @@
-; marionette unit tests
+# marionette unit tests
 [include:unit/unit-tests.ini]
 
-; layout tests
+# layout tests
 [include:../../../../../layout/base/tests/marionette/manifest.ini]
 
-; microformats tests
+# microformats tests
 [include:../../../../../toolkit/components/microformats/manifest.ini]
 
-; migration tests
+# migration tests
 [include:../../../../../browser/components/migration/tests/marionette/manifest.ini]
--- a/testing/mozbase/docs/manifestparser.rst
+++ b/testing/mozbase/docs/manifestparser.rst
@@ -131,28 +131,35 @@ section, without adding possible include
 
 .. code-block:: text
 
     [parent:../manifest.ini]
 
 Manifests are included relative to the directory of the manifest with
 the `[include:]` directive unless they are absolute paths.
 
-By default you can use both '#' and ';' as comment characters. Comments
-must start on a new line, inline comments are not supported.
+By default you can use '#' as a comment character. Comments can start a
+new line, or be inline.
 
 .. code-block:: text
 
     [roses.js]
     # a valid comment
-    ; another valid comment
-    color = red # not a valid comment
+    color = red # another valid comment
+
+Comment characters must be preceded by a space, or they will not be
+treated as comments.
 
-In the example above, the 'color' property will have the value 'red #
-not a valid comment'.
+.. code-block:: text
+
+    [test1.js]
+    url = https://foo.com/bar#baz
+
+The '#baz' anchor will not be stripped off, as it wasn't preceded by
+a space.
 
 Special variable server-root
 ````````````````````````````
 There is a special variable called `server-root` used for paths on the system.
 This variable is deemed a path and will be expanded into its absolute form.
 
 Because of the inheritant nature of the key/value pairs, if one requires a
 system path, it must be absolute for it to be of any use in any included file.
--- a/testing/mozbase/manifestparser/manifestparser/ini.py
+++ b/testing/mozbase/manifestparser/manifestparser/ini.py
@@ -1,34 +1,36 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import os
+import sys
 
 __all__ = ['read_ini', 'combine_fields']
 
 
 def read_ini(fp, variables=None, default='DEFAULT', defaults_only=False,
-             comments=';#', separators=('=', ':'), strict=True,
-             handle_defaults=True):
+             comments=None, separators=None, strict=True, handle_defaults=True):
     """
     read an .ini file and return a list of [(section, values)]
     - fp : file pointer or path to read
     - variables : default set of variables
     - default : name of the section for the default section
     - defaults_only : if True, return the default section only
     - comments : characters that if they start a line denote a comment
     - separators : strings that denote key, value separation in order
     - strict : whether to be strict about parsing
     - handle_defaults : whether to incorporate defaults into each section
     """
 
     # variables
     variables = variables or {}
+    comments = comments or ('#',)
+    separators = separators or ('=', ':')
     sections = []
     key = value = None
     section_names = set()
     if isinstance(fp, basestring):
         fp = file(fp)
 
     # read the lines
     for (linenum, line) in enumerate(fp.read().splitlines(), start=1):
@@ -37,19 +39,36 @@ def read_ini(fp, variables=None, default
 
         # ignore blank lines
         if not stripped:
             # reset key and value to avoid continuation lines
             key = value = None
             continue
 
         # ignore comment lines
-        if stripped[0] in comments:
+        if any(stripped.startswith(c) for c in comments):
             continue
 
+        # strip inline comments (borrowed from configparser)
+        comment_start = sys.maxsize
+        inline_prefixes = {p: -1 for p in comments}
+        while comment_start == sys.maxsize and inline_prefixes:
+            next_prefixes = {}
+            for prefix, index in inline_prefixes.items():
+                index = line.find(prefix, index+1)
+                if index == -1:
+                    continue
+                next_prefixes[prefix] = index
+                if index == 0 or (index > 0 and line[index-1].isspace()):
+                    comment_start = min(comment_start, index)
+            inline_prefixes = next_prefixes
+
+        if comment_start != sys.maxsize:
+            stripped = stripped[:comment_start].rstrip()
+
         # check for a new section
         if len(stripped) > 2 and stripped[0] == '[' and stripped[-1] == ']':
             section = stripped[1:-1].strip()
             key = value = None
 
             # deal with DEFAULT section
             if section.lower() == default.lower():
                 if strict:
@@ -65,17 +84,18 @@ def read_ini(fp, variables=None, default
 
             section_names.add(section)
             current_section = {}
             sections.append((section, current_section))
             continue
 
         # if there aren't any sections yet, something bad happen
         if not section_names:
-            raise Exception('No sections found')
+            raise Exception("Error parsing manifest file '%s', line %s: No sections found" %
+                            (getattr(fp, 'name', 'unknown'), linenum))
 
         # (key, value) pair
         for separator in separators:
             if separator in stripped:
                 key, value = stripped.split(separator, 1)
                 key = key.strip()
                 value = value.strip()
 
@@ -89,22 +109,18 @@ def read_ini(fp, variables=None, default
                 break
         else:
             # continuation line ?
             if line[0].isspace() and key:
                 value = '%s%s%s' % (value, os.linesep, stripped)
                 current_section[key] = value
             else:
                 # something bad happened!
-                if hasattr(fp, 'name'):
-                    filename = fp.name
-                else:
-                    filename = 'unknown'
                 raise Exception("Error parsing manifest file '%s', line %s" %
-                                (filename, linenum))
+                                (getattr(fp, 'name', 'unknown'), linenum))
 
     # server-root is a special os path declared relative to the manifest file.
     # inheritance demands we expand it as absolute
     if 'server-root' in variables:
         root = os.path.join(os.path.dirname(fp.name),
                             variables['server-root'])
         variables['server-root'] = os.path.abspath(root)
 
@@ -132,11 +148,10 @@ def combine_fields(global_vars, local_va
     }
     final_mapping = global_vars.copy()
     for field_name, value in local_vars.items():
         if field_name not in field_patterns or field_name not in global_vars:
             final_mapping[field_name] = value
             continue
         global_value = global_vars[field_name]
         pattern = field_patterns[field_name]
-        final_mapping[field_name] = pattern % (
-            global_value.split('#')[0], value.split('#')[0])
+        final_mapping[field_name] = pattern % (global_value, value)
     return final_mapping
--- a/testing/mozbase/manifestparser/tests/comment-example.ini
+++ b/testing/mozbase/manifestparser/tests/comment-example.ini
@@ -1,11 +1,11 @@
-; See https://bugzilla.mozilla.org/show_bug.cgi?id=813674
+# See https://bugzilla.mozilla.org/show_bug.cgi?id=813674
 
 [test_0180_fileInUse_xp_win_complete.js]
 [test_0181_fileInUse_xp_win_partial.js]
 [test_0182_rmrfdirFileInUse_xp_win_complete.js]
 [test_0183_rmrfdirFileInUse_xp_win_partial.js]
 [test_0184_fileInUse_xp_win_complete.js]
 [test_0185_fileInUse_xp_win_partial.js]
 [test_0186_rmrfdirFileInUse_xp_win_complete.js]
 [test_0187_rmrfdirFileInUse_xp_win_partial.js]
-; [test_0202_app_launch_apply_update_dirlocked.js] # Test disabled, bug 757632
\ No newline at end of file
+# [test_0202_app_launch_apply_update_dirlocked.js] # Test disabled, bug 757632
--- a/testing/mozbase/manifestparser/tests/test_read_ini.py
+++ b/testing/mozbase/manifestparser/tests/test_read_ini.py
@@ -7,66 +7,32 @@ ensure our .ini parser is doing what we 
 python's standard ConfigParser when 2.7 is reality so OrderedDict
 is the default:
 
 http://docs.python.org/2/library/configparser.html
 """
 
 import unittest
 from manifestparser import read_ini
-from ConfigParser import ConfigParser
 from StringIO import StringIO
 
 import mozunit
 
 
 class IniParserTest(unittest.TestCase):
 
     def test_inline_comments(self):
-        """
-        We have no inline comments; so we're testing to ensure we don't:
-        https://bugzilla.mozilla.org/show_bug.cgi?id=855288
-        """
-
-        # test '#' inline comments (really, the lack thereof)
-        string = """[test_felinicity.py]
+        manifest = """
+[test_felinicity.py]
 kittens = true # This test requires kittens
+cats = false#but not cats
 """
-        buffer = StringIO()
-        buffer.write(string)
-        buffer.seek(0)
-        result = read_ini(buffer)[0][1]['kittens']
-        self.assertEqual(result, "true # This test requires kittens")
-
-        # compare this to ConfigParser
-        # python 2.7 ConfigParser does not support '#' as an
-        # inline comment delimeter (for "backwards compatability"):
-        # http://docs.python.org/2/library/configparser.html
-        buffer.seek(0)
-        parser = ConfigParser()
-        parser.readfp(buffer)
-        control = parser.get('test_felinicity.py', 'kittens')
-        self.assertEqual(result, control)
-
-        # test ';' inline comments (really, the lack thereof)
-        string = string.replace('#', ';')
-        buffer = StringIO()
-        buffer.write(string)
-        buffer.seek(0)
-        result = read_ini(buffer)[0][1]['kittens']
-        self.assertEqual(result, "true ; This test requires kittens")
-
-        # compare this to ConfigParser
-        # python 2.7 ConfigParser *does* support ';' as an
-        # inline comment delimeter (ibid).
-        # Python 3.x configparser, OTOH, does not support
-        # inline-comments by default.  It does support their specification,
-        # though they are weakly discouraged:
-        # http://docs.python.org/dev/library/configparser.html
-        buffer.seek(0)
-        parser = ConfigParser()
-        parser.readfp(buffer)
-        control = parser.get('test_felinicity.py', 'kittens')
-        self.assertNotEqual(result, control)
+        # make sure inline comments get stripped out, but comments without a space in front don't
+        buf = StringIO()
+        buf.write(manifest)
+        buf.seek(0)
+        result = read_ini(buf)[0][1]
+        self.assertEqual(result['kittens'], 'true')
+        self.assertEqual(result['cats'], "false#but not cats")
 
 
 if __name__ == '__main__':
     mozunit.main()
--- a/testing/xpcshell/example/unit/xpcshell.ini
+++ b/testing/xpcshell/example/unit/xpcshell.ini
@@ -1,11 +1,11 @@
-; 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/.
+# 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/.
 
 [DEFAULT]
 head =
 skip-if = toolkit == 'gonk'
 support-files =
   subdir/file.txt
   file.txt
   import_module.jsm
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -36,17 +36,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 const CATEGORY_EXTENSION_SCRIPTS_ADDON = "webextension-scripts-addon";
 const CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS = "webextension-scripts-devtools";
 
 Cu.import("resource://gre/modules/ExtensionCommon.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 const {
   DefaultMap,
-  EventManager,
   SingletonEventManager,
   SpreadArgs,
   defineLazyGetter,
   getInnerWindowID,
   getMessageManager,
   getUniqueId,
   injectAPI,
   promiseEvent,
@@ -114,27 +113,27 @@ class Port {
       disconnect: () => {
         this.disconnect();
       },
 
       postMessage: json => {
         this.postMessage(json);
       },
 
-      onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => {
+      onDisconnect: new SingletonEventManager(this.context, "Port.onDisconnect", fire => {
         return this.registerOnDisconnect(error => {
           portError = error && this.context.normalizeError(error);
-          fire.withoutClone(portObj);
+          fire.asyncWithoutClone(portObj);
         });
       }).api(),
 
-      onMessage: new EventManager(this.context, "Port.onMessage", fire => {
+      onMessage: new SingletonEventManager(this.context, "Port.onMessage", fire => {
         return this.registerOnMessage(msg => {
           msg = Cu.cloneInto(msg, this.context.cloneScope);
-          fire.withoutClone(msg, portObj);
+          fire.asyncWithoutClone(msg, portObj);
         });
       }).api(),
 
       get error() {
         return portError;
       },
     };
 
@@ -325,17 +324,17 @@ class Messenger {
   }
 
   sendNativeMessage(messageManager, msg, recipient, responseCallback) {
     msg = NativeApp.encodeMessage(this.context, msg);
     return this.sendMessage(messageManager, msg, recipient, responseCallback);
   }
 
   onMessage(name) {
-    return new SingletonEventManager(this.context, name, callback => {
+    return new SingletonEventManager(this.context, name, fire => {
       let listener = {
         messageFilterPermissive: this.optionalFilter,
         messageFilterStrict: this.filter,
 
         filterMessage: (sender, recipient) => {
           // Ignore the message if it was sent by this Messenger.
           return sender.contextId !== this.context.contextId;
         },
@@ -355,17 +354,17 @@ class Messenger {
           });
 
           message = Cu.cloneInto(message, this.context.cloneScope);
           sender = Cu.cloneInto(sender, this.context.cloneScope);
           sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
 
           // Note: We intentionally do not use runSafe here so that any
           // errors are propagated to the message sender.
-          let result = callback(message, sender, sendResponse);
+          let result = fire.raw(message, sender, sendResponse);
           if (result instanceof this.context.cloneScope.Promise) {
             return result;
           } else if (result === true) {
             return promise;
           }
           return response;
         },
       };
@@ -407,17 +406,17 @@ class Messenger {
     let portId = getUniqueId();
 
     let port = new NativePort(this.context, messageManager, this.messageManagers, name, portId, null, recipient);
 
     return this._connect(messageManager, port, recipient);
   }
 
   onConnect(name) {
-    return new SingletonEventManager(this.context, name, callback => {
+    return new SingletonEventManager(this.context, name, fire => {
       let listener = {
         messageFilterPermissive: this.optionalFilter,
         messageFilterStrict: this.filter,
 
         filterMessage: (sender, recipient) => {
           // Ignore the port if it was created by this Messenger.
           return sender.contextId !== this.context.contextId;
         },
@@ -426,17 +425,17 @@ class Messenger {
           let {name, portId} = message;
           let mm = getMessageManager(target);
           let recipient = Object.assign({}, sender);
           if (recipient.tab) {
             recipient.tabId = recipient.tab.id;
             delete recipient.tab;
           }
           let port = new Port(this.context, mm, this.messageManagers, name, portId, sender, recipient);
-          this.context.runSafeWithoutClone(callback, port.api());
+          fire.asyncWithoutClone(port.api());
           return true;
         },
       };
 
       MessageChannel.addListener(this.messageManagers, "Extension:Connect", listener);
       return () => {
         MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener);
       };
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -575,157 +575,104 @@ LocaleData.prototype = {
     // Return the browser locale, but convert it to a Chrome-style
     // locale code.
     return Locale.getLocale().replace(/-/g, "_");
   },
 };
 
 // This is a generic class for managing event listeners. Example usage:
 //
-// new EventManager(context, "api.subAPI", fire => {
+// new SingletonEventManager(context, "api.subAPI", fire => {
 //   let listener = (...) => {
 //     // Fire any listeners registered with addListener.
-//     fire(arg1, arg2);
+//     fire.async(arg1, arg2);
 //   };
 //   // Register the listener.
 //   SomehowRegisterListener(listener);
 //   return () => {
 //     // Return a way to unregister the listener.
 //     SomehowUnregisterListener(listener);
 //   };
 // }).api()
 //
 // The result is an object with addListener, removeListener, and
 // hasListener methods. |context| is an add-on scope (either an
 // ExtensionContext in the chrome process or ExtensionContext in a
 // content process). |name| is for debugging. |register| is a function
-// to register the listener. |register| is only called once, even if
-// multiple listeners are registered. |register| should return an
+// to register the listener. |register| should return an
 // unregister function that will unregister the listener.
-function EventManager(context, name, register) {
-  this.context = context;
-  this.name = name;
-  this.register = register;
-  this.unregister = null;
-  this.callbacks = new Set();
-}
-
-EventManager.prototype = {
-  addListener(callback) {
-    if (typeof(callback) != "function") {
-      dump(`Expected function\n${Error().stack}`);
-      return;
-    }
-    if (this.context.unloaded) {
-      dump(`Cannot add listener to ${this.name} after context unloaded`);
-      return;
-    }
-
-    if (!this.callbacks.size) {
-      this.context.callOnClose(this);
-
-      let fireFunc = this.fire.bind(this);
-      let fireWithoutClone = this.fireWithoutClone.bind(this);
-      fireFunc.withoutClone = fireWithoutClone;
-      this.unregister = this.register(fireFunc);
-    }
-    this.callbacks.add(callback);
-  },
-
-  removeListener(callback) {
-    if (!this.callbacks.size) {
-      return;
-    }
-
-    this.callbacks.delete(callback);
-    if (this.callbacks.size == 0) {
-      this.unregister();
-      this.unregister = null;
-
-      this.context.forgetOnClose(this);
-    }
-  },
-
-  hasListener(callback) {
-    return this.callbacks.has(callback);
-  },
-
-  fire(...args) {
-    this._fireCommon("runSafe", args);
-  },
-
-  fireWithoutClone(...args) {
-    this._fireCommon("runSafeWithoutClone", args);
-  },
-
-  _fireCommon(runSafeMethod, args) {
-    for (let callback of this.callbacks) {
-      Promise.resolve(callback).then(callback => {
-        if (this.context.unloaded) {
-          dump(`${this.name} event fired after context unloaded.\n`);
-        } else if (!this.context.active) {
-          dump(`${this.name} event fired while context is inactive.\n`);
-        } else if (this.callbacks.has(callback)) {
-          this.context[runSafeMethod](callback, ...args);
-        }
-      });
-    }
-  },
-
-  close() {
-    if (this.callbacks.size) {
-      this.unregister();
-    }
-    this.callbacks.clear();
-    this.register = null;
-    this.unregister = null;
-  },
-
-  api() {
-    return {
-      addListener: callback => this.addListener(callback),
-      removeListener: callback => this.removeListener(callback),
-      hasListener: callback => this.hasListener(callback),
-    };
-  },
-};
-
-// Similar to EventManager, but it doesn't try to consolidate event
-// notifications. Each addListener call causes us to register once. It
-// allows extra arguments to be passed to addListener.
 function SingletonEventManager(context, name, register) {
   this.context = context;
   this.name = name;
   this.register = register;
   this.unregister = new Map();
 }
 
 SingletonEventManager.prototype = {
   addListener(callback, ...args) {
-    let wrappedCallback = (...args) => {
+    if (this.unregister.has(callback)) {
+      return;
+    }
+
+    let shouldFire = () => {
       if (this.context.unloaded) {
         dump(`${this.name} event fired after context unloaded.\n`);
+      } else if (!this.context.active) {
+        dump(`${this.name} event fired while context is inactive.\n`);
       } else if (this.unregister.has(callback)) {
-        return callback(...args);
+        return true;
       }
+      return false;
     };
 
-    let unregister = this.register(wrappedCallback, ...args);
+    let fire = {
+      sync: (...args) => {
+        if (shouldFire()) {
+          return this.context.runSafe(callback, ...args);
+        }
+      },
+      async: (...args) => {
+        return Promise.resolve().then(() => {
+          if (shouldFire()) {
+            return this.context.runSafe(callback, ...args);
+          }
+        });
+      },
+      raw: (...args) => {
+        if (!shouldFire()) {
+          throw new Error("Called raw() on unloaded/inactive context");
+        }
+        return callback(...args);
+      },
+      asyncWithoutClone: (...args) => {
+        return Promise.resolve().then(() => {
+          if (shouldFire()) {
+            return this.context.runSafeWithoutClone(callback, ...args);
+          }
+        });
+      },
+    };
+
+
+    let unregister = this.register(fire, ...args);
     this.unregister.set(callback, unregister);
     this.context.callOnClose(this);
   },
 
   removeListener(callback) {
     if (!this.unregister.has(callback)) {
       return;
     }
 
     let unregister = this.unregister.get(callback);
     this.unregister.delete(callback);
     unregister();
+    if (this.unregister.size == 0) {
+      this.context.forgetOnClose(this);
+    }
   },
 
   hasListener(callback) {
     return this.unregister.has(callback);
   },
 
   close() {
     for (let unregister of this.unregister.values()) {
@@ -1198,17 +1145,16 @@ this.ExtensionUtils = {
   runSafe,
   runSafeSync,
   runSafeSyncWithoutClone,
   runSafeWithoutClone,
   stylesheetMap,
   DefaultMap,
   DefaultWeakMap,
   EventEmitter,
-  EventManager,
   ExtensionError,
   IconDetails,
   LocaleData,
   MessageManagerProxy,
   PlatformInfo,
   SingletonEventManager,
   SpreadArgs,
 };
--- a/toolkit/components/extensions/ext-alarms.js
+++ b/toolkit/components/extensions/ext-alarms.js
@@ -1,15 +1,15 @@
 "use strict";
 
 var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
-  EventManager,
+  SingletonEventManager,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> Map[name -> Alarm]]
 var alarmsMap = new WeakMap();
 
 // WeakMap[Extension -> Set[callback]]
 var alarmCallbacksMap = new WeakMap();
 
@@ -135,19 +135,19 @@ extensions.registerSchemaAPI("alarms", "
         let cleared = false;
         for (let alarm of alarmsMap.get(extension).values()) {
           alarm.clear();
           cleared = true;
         }
         return Promise.resolve(cleared);
       },
 
-      onAlarm: new EventManager(context, "alarms.onAlarm", fire => {
+      onAlarm: new SingletonEventManager(context, "alarms.onAlarm", fire => {
         let callback = alarm => {
-          fire(alarm.data);
+          fire.sync(alarm.data);
         };
 
         alarmCallbacksMap.get(extension).add(callback);
         return () => {
           alarmCallbacksMap.get(extension).delete(callback);
         };
       }).api(),
     },
--- a/toolkit/components/extensions/ext-c-test.js
+++ b/toolkit/components/extensions/ext-c-test.js
@@ -166,17 +166,17 @@ function makeTestAPI(context) {
           assertTrue(errorMatches(error, expectedError, context),
                      `Function threw, expecting error to match ${toSource(expectedError)}` +
                      `got ${errorMessage}${msg}`);
         }
       },
 
       onMessage: new SingletonEventManager(context, "test.onMessage", fire => {
         let handler = (event, ...args) => {
-          context.runSafe(fire, ...args);
+          fire.async(...args);
         };
 
         extension.on("test-harness-message", handler);
         return () => {
           extension.off("test-harness-message", handler);
         };
       }).api(),
     },
--- a/toolkit/components/extensions/ext-cookies.js
+++ b/toolkit/components/extensions/ext-cookies.js
@@ -4,17 +4,17 @@ const {interfaces: Ci, utils: Cu} = Comp
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                   "resource://gre/modules/ContextualIdentityService.jsm");
 
 var {
-  EventManager,
+  SingletonEventManager,
 } = ExtensionUtils;
 
 var DEFAULT_STORE = "firefox-default";
 var PRIVATE_STORE = "firefox-private";
 var CONTAINER_STORE = "firefox-container-";
 
 global.getCookieStoreIdForTab = function(data, tab) {
   if (data.incognito) {
@@ -433,23 +433,23 @@ extensions.registerSchemaAPI("cookies", 
 
         let result = [];
         for (let key in data) {
           result.push({id: key, tabIds: data[key], incognito: key == PRIVATE_STORE});
         }
         return Promise.resolve(result);
       },
 
-      onChanged: new EventManager(context, "cookies.onChanged", fire => {
+      onChanged: new SingletonEventManager(context, "cookies.onChanged", fire => {
         let observer = (subject, topic, data) => {
           let notify = (removed, cookie, cause) => {
             cookie.QueryInterface(Ci.nsICookie2);
 
             if (extension.whiteListedHosts.matchesCookie(cookie)) {
-              fire({removed, cookie: convert({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
+              fire.async({removed, cookie: convert({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
             }
           };
 
           // We do our best effort here to map the incompatible states.
           switch (data) {
             case "deleted":
               notify(true, subject, "explicit");
               break;
--- a/toolkit/components/extensions/ext-downloads.js
+++ b/toolkit/components/extensions/ext-downloads.js
@@ -16,17 +16,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                   "resource://devtools/shared/event-emitter.js");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 const {
   ignoreEvent,
   normalizeTime,
-  runSafeSync,
   SingletonEventManager,
   PlatformInfo,
 } = ExtensionUtils;
 
 const DOWNLOAD_ITEM_FIELDS = ["id", "url", "referrer", "filename", "incognito",
                               "danger", "mime", "startTime", "endTime",
                               "estimatedEndTime", "state",
                               "paused", "canResume", "error",
@@ -746,47 +745,47 @@ extensions.registerSchemaAPI("downloads"
               changes[fld] = {
                 previous: noundef(item.prechange[fld]),
                 current: noundef(item[fld]),
               };
             }
           });
           if (Object.keys(changes).length > 0) {
             changes.id = item.id;
-            runSafeSync(context, fire, changes);
+            fire.async(changes);
           }
         };
 
         let registerPromise = DownloadMap.getDownloadList().then(() => {
           DownloadMap.on("change", handler);
         });
         return () => {
           registerPromise.then(() => {
             DownloadMap.off("change", handler);
           });
         };
       }).api(),
 
       onCreated: new SingletonEventManager(context, "downloads.onCreated", fire => {
         const handler = (what, item) => {
-          runSafeSync(context, fire, item.serialize());
+          fire.async(item.serialize());
         };
         let registerPromise = DownloadMap.getDownloadList().then(() => {
           DownloadMap.on("create", handler);
         });
         return () => {
           registerPromise.then(() => {
             DownloadMap.off("create", handler);
           });
         };
       }).api(),
 
       onErased: new SingletonEventManager(context, "downloads.onErased", fire => {
         const handler = (what, item) => {
-          runSafeSync(context, fire, item.id);
+          fire.async(item.id);
         };
         let registerPromise = DownloadMap.getDownloadList().then(() => {
           DownloadMap.on("erase", handler);
         });
         return () => {
           registerPromise.then(() => {
             DownloadMap.off("erase", handler);
           });
--- a/toolkit/components/extensions/ext-idle.js
+++ b/toolkit/components/extensions/ext-idle.js
@@ -76,17 +76,17 @@ extensions.registerSchemaAPI("idle", "ad
         }
         return Promise.resolve("idle");
       },
       setDetectionInterval: function(detectionIntervalInSeconds) {
         setDetectionInterval(extension, context, detectionIntervalInSeconds);
       },
       onStateChanged: new SingletonEventManager(context, "idle.onStateChanged", fire => {
         let listener = (event, data) => {
-          context.runSafe(fire, data);
+          fire.sync(data);
         };
 
         getObserver(extension, context).on("stateChanged", listener);
         return () => {
           getObserver(extension, context).off("stateChanged", listener);
         };
       }).api(),
     },
--- a/toolkit/components/extensions/ext-notifications.js
+++ b/toolkit/components/extensions/ext-notifications.js
@@ -3,17 +3,17 @@
 var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                   "resource://devtools/shared/event-emitter.js");
 
 var {
-  EventManager,
+  SingletonEventManager,
   ignoreEvent,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> Map[id -> Notification]]
 var notificationsMap = new WeakMap();
 
 // Manages a notification popup (notifications API) created by the extension.
 function Notification(extension, id, options) {
@@ -126,31 +126,31 @@ extensions.registerSchemaAPI("notificati
       getAll: function() {
         let result = {};
         notificationsMap.get(extension).forEach((value, key) => {
           result[key] = value.options;
         });
         return Promise.resolve(result);
       },
 
-      onClosed: new EventManager(context, "notifications.onClosed", fire => {
+      onClosed: new SingletonEventManager(context, "notifications.onClosed", fire => {
         let listener = (event, notificationId) => {
           // FIXME: Support the byUser argument.
-          fire(notificationId, true);
+          fire.async(notificationId, true);
         };
 
         notificationsMap.get(extension).on("closed", listener);
         return () => {
           notificationsMap.get(extension).off("closed", listener);
         };
       }).api(),
 
-      onClicked: new EventManager(context, "notifications.onClicked", fire => {
+      onClicked: new SingletonEventManager(context, "notifications.onClicked", fire => {
         let listener = (event, notificationId) => {
-          fire(notificationId, true);
+          fire.async(notificationId, true);
         };
 
         notificationsMap.get(extension).on("clicked", listener);
         return () => {
           notificationsMap.get(extension).off("clicked", listener);
         };
       }).api(),
 
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -23,55 +23,55 @@ extensions.registerSchemaAPI("runtime", 
     runtime: {
       onStartup: new SingletonEventManager(context, "runtime.onStartup", fire => {
         if (context.incognito) {
           // This event should not fire if we are operating in a private profile.
           return () => {};
         }
         let listener = () => {
           if (extension.startupReason === "APP_STARTUP") {
-            fire();
+            fire.sync();
           }
         };
         extension.on("startup", listener);
         return () => {
           extension.off("startup", listener);
         };
       }).api(),
 
       onInstalled: new SingletonEventManager(context, "runtime.onInstalled", fire => {
         let listener = () => {
           switch (extension.startupReason) {
             case "APP_STARTUP":
               if (Extension.browserUpdated) {
-                fire({reason: "browser_update"});
+                fire.sync({reason: "browser_update"});
               }
               break;
             case "ADDON_INSTALL":
-              fire({reason: "install"});
+              fire.sync({reason: "install"});
               break;
             case "ADDON_UPGRADE":
-              fire({reason: "update"});
+              fire.sync({reason: "update"});
               break;
           }
         };
         extension.on("startup", listener);
         return () => {
           extension.off("startup", listener);
         };
       }).api(),
 
       onUpdateAvailable: new SingletonEventManager(context, "runtime.onUpdateAvailable", fire => {
         let instanceID = extension.addonData.instanceID;
         AddonManager.addUpgradeListener(instanceID, upgrade => {
           extension.upgrade = upgrade;
           let details = {
             version: upgrade.version,
           };
-          context.runSafe(fire, details);
+          fire.sync(details);
         });
         return () => {
           AddonManager.removeUpgradeListener(instanceID);
         };
       }).api(),
 
       reload: () => {
         if (extension.upgrade) {
--- a/toolkit/components/extensions/ext-storage.js
+++ b/toolkit/components/extensions/ext-storage.js
@@ -6,18 +6,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/ExtensionStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorageSync",
                                   "resource://gre/modules/ExtensionStorageSync.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                   "resource://gre/modules/AddonManager.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
-  EventManager,
   ExtensionError,
+  SingletonEventManager,
 } = ExtensionUtils;
 
 function enforceNoTemporaryAddon(extensionId) {
   const EXCEPTION_MESSAGE =
         "The storage API will not work with a temporary addon ID. " +
         "Please add an explicit addon ID to your manifest. " +
         "For more information see https://bugzil.la/1323228.";
   if (AddonManagerPrivate.isTemporaryInstallID(extensionId)) {
@@ -58,22 +58,22 @@ function storageApiFactory(context) {
           return ExtensionStorageSync.remove(extension, keys, context);
         },
         clear: function() {
           enforceNoTemporaryAddon(extension.id);
           return ExtensionStorageSync.clear(extension, context);
         },
       },
 
-      onChanged: new EventManager(context, "storage.onChanged", fire => {
+      onChanged: new SingletonEventManager(context, "storage.onChanged", fire => {
         let listenerLocal = changes => {
-          fire(changes, "local");
+          fire.async(changes, "local");
         };
         let listenerSync = changes => {
-          fire(changes, "sync");
+          fire.async(changes, "sync");
         };
 
         ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
         ExtensionStorageSync.addOnChangedListener(extension, listenerSync, context);
         return () => {
           ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
           ExtensionStorageSync.removeOnChangedListener(extension, listenerSync);
         };
--- a/toolkit/components/extensions/ext-webNavigation.js
+++ b/toolkit/components/extensions/ext-webNavigation.js
@@ -93,17 +93,17 @@ function fillTransitionProperties(eventN
     dst.transitionType = transitionType;
     dst.transitionQualifiers = transitionQualifiers;
   }
 }
 
 // Similar to WebRequestEventManager but for WebNavigation.
 function WebNavigationEventManager(context, eventName) {
   let name = `webNavigation.${eventName}`;
-  let register = (callback, urlFilters) => {
+  let register = (fire, urlFilters) => {
     // Don't create a MatchURLFilters instance if the listener does not include any filter.
     let filters = urlFilters ?
           new MatchURLFilters(urlFilters.url) : null;
 
     let listener = data => {
       if (!data.browser) {
         return;
       }
@@ -122,17 +122,17 @@ function WebNavigationEventManager(conte
       // Fills in tabId typically.
       extensions.emit("fill-browser-data", data.browser, data2);
       if (data2.tabId < 0) {
         return;
       }
 
       fillTransitionProperties(eventName, data, data2);
 
-      context.runSafe(callback, data2);
+      fire.async(data2);
     };
 
     WebNavigation[eventName].addListener(listener, filters);
     return () => {
       WebNavigation[eventName].removeListener(listener);
     };
   };
 
--- a/toolkit/components/extensions/ext-webRequest.js
+++ b/toolkit/components/extensions/ext-webRequest.js
@@ -15,17 +15,17 @@ var {
   SingletonEventManager,
 } = ExtensionUtils;
 
 // EventManager-like class specifically for WebRequest. Inherits from
 // SingletonEventManager. Takes care of converting |details| parameter
 // when invoking listeners.
 function WebRequestEventManager(context, eventName) {
   let name = `webRequest.${eventName}`;
-  let register = (callback, filter, info) => {
+  let register = (fire, filter, info) => {
     let listener = data => {
       // Prevent listening in on requests originating from system principal to
       // prevent tinkering with OCSP, app and addon updates, etc.
       if (data.isSystemPrincipal) {
         return;
       }
       let browserData = {};
       extensions.emit("fill-browser-data", data.browser, browserData);
@@ -60,17 +60,17 @@ function WebRequestEventManager(context,
       let optional = ["requestHeaders", "responseHeaders", "statusCode", "statusLine", "error", "redirectUrl",
                       "requestBody"];
       for (let opt of optional) {
         if (opt in data) {
           data2[opt] = data[opt];
         }
       }
 
-      return context.runSafe(callback, data2);
+      return fire.sync(data2);
     };
 
     let filter2 = {};
     filter2.urls = new MatchPattern(filter.urls);
     if (filter.types) {
       filter2.types = filter.types;
     }
     if (filter.tabId) {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_alarms.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_alarms.js
@@ -47,20 +47,23 @@ add_task(function* test_alarm_fires() {
     background: `(${backgroundScript})()`,
     manifest: {
       permissions: ["alarms"],
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("alarm-fires");
+
+  // Defer unloading the extension so the asynchronous event listener
+  // reply finishes.
+  yield new Promise(resolve => setTimeout(resolve, 0));
   yield extension.unload();
 });
 
-
 add_task(function* test_alarm_fires_with_when() {
   function backgroundScript() {
     let ALARM_NAME = "test_ext_alarms";
     let timer;
 
     browser.alarms.onAlarm.addListener(alarm => {
       browser.test.assertEq(ALARM_NAME, alarm.name, "alarm has the expected name");
       clearTimeout(timer);
@@ -81,20 +84,23 @@ add_task(function* test_alarm_fires_with
     background: `(${backgroundScript})()`,
     manifest: {
       permissions: ["alarms"],
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("alarm-when");
+
+  // Defer unloading the extension so the asynchronous event listener
+  // reply finishes.
+  yield new Promise(resolve => setTimeout(resolve, 0));
   yield extension.unload();
 });
 
-
 add_task(function* test_alarm_clear_non_matching_name() {
   async function backgroundScript() {
     let ALARM_NAME = "test_ext_alarms";
 
     browser.alarms.create(ALARM_NAME, {when: Date.now() + 2000});
 
     let wasCleared = await browser.alarms.clear(ALARM_NAME + "1");
     browser.test.assertFalse(wasCleared, "alarm was not cleared");
@@ -111,17 +117,16 @@ add_task(function* test_alarm_clear_non_
     },
   });
 
   yield extension.startup();
   yield extension.awaitFinish("alarm-clear");
   yield extension.unload();
 });
 
-
 add_task(function* test_alarm_get_and_clear_single_argument() {
   async function backgroundScript() {
     browser.alarms.create({when: Date.now() + 2000});
 
     let alarm = await browser.alarms.get();
     browser.test.assertEq("", alarm.name, "expected alarm returned");
 
     let wasCleared = await browser.alarms.clear();
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contexts.js
@@ -7,17 +7,16 @@ Cu.import("resource://gre/modules/Timer.
 Cu.import("resource://gre/modules/ExtensionCommon.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   BaseContext,
 } = ExtensionCommon;
 
 var {
-  EventManager,
   SingletonEventManager,
 } = ExtensionUtils;
 
 class StubContext extends BaseContext {
   constructor() {
     let fakeExtension = {id: "test@web.extension"};
     super("testEnv", fakeExtension);
     this.sandbox = Cu.Sandbox(global);
@@ -60,70 +59,54 @@ add_task(function* test_post_unload_prom
   // any micro-tasks that get enqueued by the resolution handlers above.
   yield new Promise(resolve => setTimeout(resolve, 0));
 });
 
 
 add_task(function* test_post_unload_listeners() {
   let context = new StubContext();
 
-  let fireEvent;
-  let onEvent = new EventManager(context, "onEvent", fire => {
-    fireEvent = fire;
-    return () => {};
-  });
-
   let fireSingleton;
-  let onSingleton = new SingletonEventManager(context, "onSingleton", callback => {
+  let onSingleton = new SingletonEventManager(context, "onSingleton", fire => {
     fireSingleton = () => {
-      Promise.resolve().then(callback);
+      fire.async();
     };
     return () => {};
   });
 
   let fail = event => {
     ok(false, `Unexpected event: ${event}`);
   };
 
-  // Check that event listeners aren't called after they've been removed.
-  onEvent.addListener(fail);
+  // Check that event listeners isn't called after it has been removed.
   onSingleton.addListener(fail);
 
-  let promises = [
-    new Promise(resolve => onEvent.addListener(resolve)),
-    new Promise(resolve => onSingleton.addListener(resolve)),
-  ];
+  let promise = new Promise(resolve => onSingleton.addListener(resolve));
 
-  fireEvent("onEvent");
   fireSingleton("onSingleton");
 
-  // Both `fireEvent` calls are dispatched asynchronously, so they won't
-  // have fired by this point. The `fail` listeners that we remove now
-  // should not be called, even though the events have already been
+  // The `fireSingleton` call ia dispatched asynchronously, so it won't
+  // have fired by this point. The `fail` listener that we remove now
+  // should not be called, even though the event has already been
   // enqueued.
-  onEvent.removeListener(fail);
   onSingleton.removeListener(fail);
 
-  // Wait for the remaining listeners to be called, which should always
-  // happen after the `fail` listeners would normally be called.
-  yield Promise.all(promises);
+  // Wait for the remaining listener to be called, which should always
+  // happen after the `fail` listener would normally be called.
+  yield promise;
 
-  // Check that event listeners aren't called after the context has
+  // Check that the event listener isn't called after the context has
   // unloaded.
-  onEvent.addListener(fail);
   onSingleton.addListener(fail);
 
-  // The EventManager `fire` callback always dispatches events
+  // The `fire` callback always dispatches events
   // asynchronously, so we need to test that any pending event callbacks
   // aren't fired after the context unloads. We also need to test that
   // any `fire` calls that happen *after* the context is unloaded also
   // do not trigger callbacks.
-  fireEvent("onEvent");
-  Promise.resolve("onEvent").then(fireEvent);
-
   fireSingleton("onSingleton");
   Promise.resolve("onSingleton").then(fireSingleton);
 
   context.unload();
 
   // The `setTimeout` ensures that we return to the event loop after
   // promise resolution, which means we're guaranteed to return after
   // any micro-tasks that get enqueued by the resolution handlers above.
--- a/toolkit/components/extensions/test/xpcshell/test_ext_idle.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_idle.js
@@ -140,16 +140,19 @@ add_task(function* testSetDetectionInter
   });
 
   idleService._reset();
   yield extension.startup();
   yield extension.awaitMessage("listenerAdded");
   idleService._fireObservers("idle");
   yield extension.awaitMessage("listenerFired");
   checkActivity({expectedAdd: [99], expectedRemove: [], expectedFires: ["idle"]});
+  // Defer unloading the extension so the asynchronous event listener
+  // reply finishes.
+  yield new Promise(resolve => setTimeout(resolve, 0));
   yield extension.unload();
 });
 
 add_task(function* testSetDetectionIntervalAfterAddingListener() {
   function background() {
     browser.idle.onStateChanged.addListener(newState => {
       browser.test.assertEq("idle", newState, "listener fired with the expected state");
       browser.test.sendMessage("listenerFired");
@@ -166,16 +169,20 @@ add_task(function* testSetDetectionInter
   });
 
   idleService._reset();
   yield extension.startup();
   yield extension.awaitMessage("detectionIntervalSet");
   idleService._fireObservers("idle");
   yield extension.awaitMessage("listenerFired");
   checkActivity({expectedAdd: [60, 99], expectedRemove: [60], expectedFires: ["idle"]});
+
+  // Defer unloading the extension so the asynchronous event listener
+  // reply finishes.
+  yield new Promise(resolve => setTimeout(resolve, 0));
   yield extension.unload();
 });
 
 add_task(function* testOnlyAddingListener() {
   function background() {
     browser.idle.onStateChanged.addListener(newState => {
       browser.test.assertEq("active", newState, "listener fired with the expected state");
       browser.test.sendMessage("listenerFired");
@@ -193,10 +200,14 @@ add_task(function* testOnlyAddingListene
   idleService._reset();
   yield extension.startup();
   yield extension.awaitMessage("listenerAdded");
   idleService._fireObservers("active");
   yield extension.awaitMessage("listenerFired");
   // check that "idle-daily" topic does not cause a listener to fire
   idleService._fireObservers("idle-daily");
   checkActivity({expectedAdd: [60], expectedRemove: [], expectedFires: ["active", "idle-daily"]});
+
+  // Defer unloading the extension so the asynchronous event listener
+  // reply finishes.
+  yield new Promise(resolve => setTimeout(resolve, 0));
   yield extension.unload();
 });
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -239,22 +239,17 @@ var LoginManagerContent = {
           recipes: msg.data.recipes,
         });
         break;
       }
 
       case "RemoteLogins:loginsAutoCompleted": {
         let loginsFound =
           LoginHelper.vanillaObjectsToLogins(msg.data.logins);
-        // If we're in the parent process, don't pass a message manager so our
-        // autocomplete result objects know they can remove the login from the
-        // login manager directly.
-        let messageManager =
-          (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) ?
-            msg.target : undefined;
+        let messageManager = msg.target;
         request.promise.resolve({ logins: loginsFound, messageManager });
         break;
       }
     }
   },
 
   /**
    * Get relevant logins and recipes from the parent
@@ -292,33 +287,30 @@ var LoginManagerContent = {
     let form = LoginFormFactory.createFromField(aElement);
     let win = doc.defaultView;
 
     let formOrigin = LoginUtils._getPasswordOrigin(doc.documentURI);
     let actionOrigin = LoginUtils._getActionOrigin(form);
 
     let messageManager = messageManagerFromWindow(win);
 
-    let remote = (Services.appinfo.processType ===
-                  Services.appinfo.PROCESS_TYPE_CONTENT);
-
     let previousResult = aPreviousResult ?
                            { searchString: aPreviousResult.searchString,
                              logins: LoginHelper.loginsToVanillaObjects(aPreviousResult.logins) } :
                            null;
 
     let requestData = {};
     let messageData = { formOrigin,
                         actionOrigin,
                         searchString: aSearchString,
                         previousResult,
                         rect: aRect,
                         isSecure: InsecurePasswordUtils.isFormSecure(form),
                         isPasswordField: aElement.type == "password",
-                        remote };
+                      };
 
     return this._sendRequest(messageManager, requestData,
                              "RemoteLogins:autoCompleteLogins",
                              messageData);
   },
 
   setupProgressListener(window) {
     if (!LoginHelper.formlessCaptureEnabled) {
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -6,18 +6,16 @@
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
 Cu.importGlobalProperties(["URL"]);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "UserAutoCompleteResult",
-                                  "resource://gre/modules/LoginManagerContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AutoCompletePopup",
                                   "resource://gre/modules/AutoCompletePopup.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
                                   "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                   "resource://gre/modules/LoginHelper.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
@@ -221,19 +219,19 @@ var LoginManagerParent = {
     target.sendAsyncMessage("RemoteLogins:loginsFound", {
       requestId,
       logins: jsLogins,
       recipes,
     });
   }),
 
   doAutocompleteSearch({ formOrigin, actionOrigin,
-                                   searchString, previousResult,
-                                   rect, requestId, isSecure, isPasswordField,
-                                   remote }, target) {
+                         searchString, previousResult,
+                         rect, requestId, isSecure, isPasswordField,
+                       }, target) {
     // Note: previousResult is a regular object, not an
     // nsIAutoCompleteResult.
 
     let searchStringLower = searchString.toLowerCase();
     let logins;
     if (previousResult &&
         searchStringLower.startsWith(previousResult.searchString.toLowerCase())) {
       log("Using previous autocomplete result");
@@ -264,26 +262,16 @@ var LoginManagerParent = {
       // Also don't offer empty usernames as possible results except
       // for password field.
       if (isPasswordField) {
         return true;
       }
       return match && match.toLowerCase().startsWith(searchStringLower);
     });
 
-    // XXX In the E10S case, we're responsible for showing our own
-    // autocomplete popup here because the autocomplete protocol hasn't
-    // been e10s-ized yet. In the non-e10s case, our caller is responsible
-    // for showing the autocomplete popup (via the regular
-    // nsAutoCompleteController).
-    if (remote) {
-      let results = new UserAutoCompleteResult(searchString, matchingLogins, {isSecure});
-      AutoCompletePopup.showPopupWithResults({ browser: target.ownerGlobal, rect, results });
-    }
-
     // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
     // doesn't support structured cloning.
     var jsLogins = LoginHelper.loginsToVanillaObjects(matchingLogins);
     target.messageManager.sendAsyncMessage("RemoteLogins:loginsAutoCompleted", {
       requestId,
       logins: jsLogins,
     });
   },
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -311,16 +311,24 @@ nsFormFillController::MarkAsAutofillFiel
   nsCOMPtr<nsINode> node = do_QueryInterface(aInput);
   NS_ENSURE_STATE(node);
   mAutofillInputs.Put(node, true);
   node->AddMutationObserverUnlessExists(this);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsFormFillController::GetFocusedInput(nsIDOMHTMLInputElement** aRetVal) {
+  if (!aRetVal) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+  *aRetVal = mFocusedInput;
+  return NS_OK;
+}
 
 ////////////////////////////////////////////////////////////////////////
 //// nsIAutoCompleteInput
 
 NS_IMETHODIMP
 nsFormFillController::GetPopup(nsIAutoCompletePopup **aPopup)
 {
   *aPopup = mFocusedPopup;
--- a/toolkit/components/satchel/nsIFormFillController.idl
+++ b/toolkit/components/satchel/nsIFormFillController.idl
@@ -46,9 +46,16 @@ interface nsIFormFillController : nsISup
 
   /*
    * Mark the specified <input> element as being managed by a form autofill component.
    * Autocomplete requests will be handed off to the autofill component.
    *
    * @param aInput - The HTML <input> element to mark
    */
   void markAsAutofillField(in nsIDOMHTMLInputElement aInput);
+
+  /**
+   * Return the focused input which is cached in form fill controller.
+   *
+   * @returns The focused input.
+   */
+  nsIDOMHTMLInputElement getFocusedInput();
 };
--- a/toolkit/components/timermanager/tests/unit/xpcshell.ini
+++ b/toolkit/components/timermanager/tests/unit/xpcshell.ini
@@ -1,8 +1,8 @@
-; This Source Code Form is subject to the terms of the Mozilla Public
-; License, v. 2.0. If a copy of the MPL was not distributed with this
-; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+# 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/.
 
 [DEFAULT]
 head = 
 
 [consumerNotifications.js]
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -22,17 +22,16 @@ def Libxul(name):
         GeckoSharedLibrary(name, linkage=None)
         SHARED_LIBRARY_NAME = 'xul'
 
     DELAYLOAD_DLLS += [
         'comdlg32.dll',
         'dbghelp.dll',
         'netapi32.dll',
         'PowrProf.dll',
-        'psapi.dll',
         'rasapi32.dll',
         'rasdlg.dll',
         'secur32.dll',
         'wininet.dll',
         'winspool.drv'
     ]
 
     if CONFIG['ACCESSIBILITY']:
@@ -338,17 +337,16 @@ if CONFIG['OS_ARCH'] == 'WINNT':
         'ole32',
         'version',
         'winspool',
         'comdlg32',
         'imm32',
         'msimg32',
         'netapi32',
         'shlwapi',
-        'psapi',
         'ws2_32',
         'dbghelp',
         'rasapi32',
         'rasdlg',
         'iphlpapi',
         'uxtheme',
         'setupapi',
         'secur32',
--- a/toolkit/mozapps/update/tests/chrome/chrome.ini
+++ b/toolkit/mozapps/update/tests/chrome/chrome.ini
@@ -1,20 +1,20 @@
-; 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/.
+# 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/.
 
 [DEFAULT]
 tags = appupdate
 support-files =
   utils.js
   update.sjs
 
-; mochitest-chrome tests must start with "test_" and are executed in sorted
-; order and not in the order specified in the manifest.
+# mochitest-chrome tests must start with "test_" and are executed in sorted
+# order and not in the order specified in the manifest.
 [test_0010_background_basic.xul]
 [test_0011_check_basic.xul]
 [test_0012_check_basic_staging.xul]
 skip-if = asan
 reason = Bug 1168003
 [test_0013_check_no_updates.xul]
 [test_0014_check_error_xml_malformed.xul]
 [test_0061_check_verifyFailPartial_noComplete.xul]
--- a/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
+++ b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
@@ -1,11 +1,11 @@
-; 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/.
+# 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/.
 
 [DEFAULT]
 tags = appupdate
 head = head_update.js
 
 [canCheckForAndCanApplyUpdates.js]
 [urlConstruction.js]
 [updateManagerXML.js]
--- a/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
+++ b/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
@@ -1,15 +1,15 @@
-; This Source Code Form is subject to the terms of the Mozilla Public
-; License, v. 2.0. If a copy of the MPL was not distributed with this
-; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+# 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/.
 
-; Tests that require the updater binary. These tests should never run on Android
-; which doesn't use the updater binary as other applications do and are excluded
-; from running the tests in the moz.build file.
+# Tests that require the updater binary. These tests should never run on Android
+# which doesn't use the updater binary as other applications do and are excluded
+# from running the tests in the moz.build file.
 
 [DEFAULT]
 tags = appupdate
 head = head_update.js
 
 [marSuccessComplete.js]
 skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
 reason = bug 1291985
--- a/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini
+++ b/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini
@@ -1,13 +1,13 @@
-; 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/.
+# 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/.
 
-; Tests that require the updater binary and the maintenance service.
+# Tests that require the updater binary and the maintenance service.
 
 [DEFAULT]
 tags = appupdate
 head = head_update.js
 
 [bootstrapSvc.js]
 skip-if = os == 'win' && debug && (os_version == '5.1' || os_version == '5.2')
 reason = bug 1291985