Merge autoland to mozilla-central. a=merge
authorBogdan Tara <btara@mozilla.com>
Sat, 17 Mar 2018 12:06:00 +0200
changeset 408665 29dcc9cb77c372c97681a47496488ec6c623915d
parent 408664 c488b8d0e074efb490ebca32db68eb77871bfd2f (current diff)
parent 408662 0665d23d7e8788a07048f1454ea4457c0197b9d0 (diff)
child 408666 97160a734959af73cc97af0bf8d198e301ebedae
push id100996
push userbtara@mozilla.com
push dateSat, 17 Mar 2018 10:37:43 +0000
treeherdermozilla-inbound@97160a734959 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.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 autoland to mozilla-central. a=merge
browser/components/places/content/downloadsViewOverlay.xul
browser/components/places/content/editBookmarkOverlay.js
browser/components/places/content/editBookmarkOverlay.xul
browser/components/places/tests/browser/browser_library_batch_delete.js
browser/components/places/tests/chrome/test_bug427633_no_newfolder_if_noip.xul
browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul
browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul
browser/components/places/tests/chrome/test_editBookmarkOverlay_keywords.xul
browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
browser/themes/linux/places/editBookmarkOverlay.css
browser/themes/osx/places/editBookmarkOverlay.css
browser/themes/windows/places/editBookmarkOverlay.css
gfx/layers/apz/public/IAPZCTreeManager.cpp
toolkit/components/places/tests/unit/test_405497.js
toolkit/components/places/tests/unit/test_async_in_batchmode.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1097,31 +1097,25 @@ pref("dom.ipc.plugins.sandbox-level.flas
 // plugins used in automated tests.
 pref("dom.ipc.plugins.sandbox-level.default", 0);
 #endif
 
 #if defined(XP_LINUX) && defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // This pref is introduced as part of bug 742434, the naming is inspired from
 // its Windows/Mac counterpart, but on Linux it's an integer which means:
 // 0 -> "no sandbox"
-// 1 -> "content sandbox using seccomp-bpf when available"
+// 1 -> "content sandbox using seccomp-bpf when available" + ipc restrictions
 // 2 -> "seccomp-bpf + write file broker"
 // 3 -> "seccomp-bpf + read/write file brokering"
-// 4 -> all of the above + network/socket restrictions
-// Content sandboxing on Linux is currently in the stage of
-// 'just getting it enabled', which includes a very permissive whitelist. We
-// enable seccomp-bpf on nightly to see if everything is running, or if we need
-// to whitelist more system calls.
+// 4 -> all of the above + network/socket restrictions + chroot
 //
-// So the purpose of this setting is to allow nightly users to disable the
-// sandbox while we fix their problems. This way, they won't have to wait for
-// another nightly release which disables seccomp-bpf again.
+// The purpose of this setting is to allow Linux users or distros to disable
+// the sandbox while we fix their problems, or to allow running Firefox with
+// exotic configurations we can't reasonably support out of the box.
 //
-// This setting may not be required anymore once we decide to permanently
-// enable the content sandbox.
 pref("security.sandbox.content.level", 4);
 pref("security.sandbox.content.write_path_whitelist", "");
 pref("security.sandbox.content.read_path_whitelist", "");
 pref("security.sandbox.content.syscall_whitelist", "");
 #endif
 
 #if defined(MOZ_SANDBOX) && defined(MOZ_CONTENT_SANDBOX)
 // ID (a UUID when set by gecko) that is used to form the name of a
@@ -1130,18 +1124,16 @@ pref("security.sandbox.content.syscall_w
 pref("security.sandbox.content.tempDirSuffix", "");
 #endif
 
 #if defined(MOZ_SANDBOX)
 // This pref determines if messages relevant to sandbox violations are
 // logged.
 #if defined(XP_WIN) || defined(XP_MACOSX)
 pref("security.sandbox.logging.enabled", false);
-#else
-pref("security.sandbox.logging.enabled", true);
 #endif
 #endif
 
 // This pref governs whether we attempt to work around problems caused by
 // plugins using OS calls to manipulate the cursor while running out-of-
 // process.  These workarounds all involve intercepting (hooking) certain
 // OS calls in the plugin process, then arranging to make certain OS calls
 // in the browser process.  Eventually plugins will be required to use the
@@ -1547,18 +1539,22 @@ pref("privacy.userContext.longPressBehav
 pref("privacy.userContext.extension", "");
 
 // Start the browser in e10s mode
 pref("browser.tabs.remote.autostart", true);
 pref("browser.tabs.remote.desktopbehavior", true);
 
 // For speculatively warming up tabs to improve perceived
 // performance while using the async tab switcher.
-// Disabled until bug 1397426 is fixed.
+#if defined(NIGHTLY_BUILD)
+pref("browser.tabs.remote.warmup.enabled", true);
+#else
 pref("browser.tabs.remote.warmup.enabled", false);
+#endif
+
 pref("browser.tabs.remote.warmup.maxTabs", 3);
 pref("browser.tabs.remote.warmup.unloadDelayMs", 2000);
 
 // For the about:tabcrashed page
 pref("browser.tabs.crashReporting.sendReport", true);
 pref("browser.tabs.crashReporting.includeURL", false);
 pref("browser.tabs.crashReporting.requestEmail", false);
 pref("browser.tabs.crashReporting.emailMe", false);
--- a/browser/base/content/browser-doctype.inc
+++ b/browser/base/content/browser-doctype.inc
@@ -16,10 +16,12 @@
 <!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
 %safebrowsingDTD;
 <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
 %aboutHomeDTD;
 <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
 %syncBrandDTD;
 <!ENTITY % reportphishDTD SYSTEM "chrome://browser/locale/safebrowsing/report-phishing.dtd">
 %reportphishDTD;
+<!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+%editBookmarkOverlayDTD;
 ]>
 
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -196,55 +196,41 @@ var StarUI = {
             }
           }, delay);
           this._autoCloseTimerEnabled = true;
         }
         break;
     }
   },
 
-  _overlayLoaded: false,
-  _overlayLoading: false,
+  _bookmarkPopupInitialized: false,
   async showEditBookmarkPopup(aNode, aAnchorElement, aPosition, aIsNewBookmark, aUrl) {
     // Slow double-clicks (not true double-clicks) shouldn't
     // cause the panel to flicker.
     if (this.panel.state == "showing" ||
         this.panel.state == "open") {
       return;
     }
 
     this._isNewBookmark = aIsNewBookmark;
     this._itemGuids = null;
 
-    // Performance: load the overlay the first time the panel is opened
-    // (see bug 392443).
-    if (this._overlayLoading)
-      return;
-
-    if (this._overlayLoaded) {
+    if (this._bookmarkPopupInitialized) {
       await this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl);
       return;
     }
+    this._bookmarkPopupInitialized = true;
+    // Move the header (star, title, button) into the grid,
+    // so that it aligns nicely with the other items (bug 484022).
+    let header = this._element("editBookmarkPanelHeader");
+    let rows = this._element("editBookmarkPanelGrid").lastChild;
+    rows.insertBefore(header, rows.firstChild);
+    header.hidden = false;
 
-    this._overlayLoading = true;
-    document.loadOverlay(
-      "chrome://browser/content/places/editBookmarkOverlay.xul",
-      (aSubject, aTopic, aData) => {
-        // Move the header (star, title, button) into the grid,
-        // so that it aligns nicely with the other items (bug 484022).
-        let header = this._element("editBookmarkPanelHeader");
-        let rows = this._element("editBookmarkPanelGrid").lastChild;
-        rows.insertBefore(header, rows.firstChild);
-        header.hidden = false;
-
-        this._overlayLoading = false;
-        this._overlayLoaded = true;
-        this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl);
-      }
-    );
+    await this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl);
   },
 
   async _doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition, aUrl) {
     if (this.panel.state != "closed")
       return;
 
     this._blockCommands(); // un-done in the popuphidden handler
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -112,17 +112,17 @@ XPCOMUtils.defineLazyScriptGetter(this, 
                                          "DownloadsViewController",
                                          "DownloadsSummary", "DownloadsFooter",
                                          "DownloadsBlockedSubview"],
                                   "chrome://browser/content/downloads/downloads.js");
 XPCOMUtils.defineLazyScriptGetter(this, ["DownloadsButton",
                                          "DownloadsIndicatorView"],
                                   "chrome://browser/content/downloads/indicator.js");
 XPCOMUtils.defineLazyScriptGetter(this, "gEditItemOverlay",
-                                  "chrome://browser/content/places/editBookmarkOverlay.js");
+                                  "chrome://browser/content/places/editBookmark.js");
 if (AppConstants.NIGHTLY_BUILD) {
   XPCOMUtils.defineLazyScriptGetter(this, "gWebRender",
                                     "chrome://browser/content/browser-webrender.js");
 }
 
 // lazy service getters
 
 XPCOMUtils.defineLazyServiceGetters(this, {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -8,16 +8,18 @@
 
 <?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
 <?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/usercontext/usercontext.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/controlcenter/panel.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/customizableui/panelUI.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmark.css"?>
 <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/tabbrowser.css" type="text/css"?>
 
 # All DTD information is stored in a separate file so that it can be shared by
 # hiddenWindow.xul.
 #include browser-doctype.inc
 
 <window id="main-window"
@@ -218,17 +220,17 @@
         <vbox align="center">
           <image id="editBookmarkPanelStarIcon"/>
         </vbox>
         <vbox>
           <label id="editBookmarkPanelTitle"/>
           <description id="editBookmarkPanelDescription"/>
         </vbox>
       </row>
-      <vbox id="editBookmarkPanelContent" flex="1" hidden="true"/>
+#include ../../components/places/content/editBookmarkPanel.inc.xul
       <hbox id="editBookmarkPanelBottomButtons" pack="end">
 #ifndef XP_UNIX
         <button id="editBookmarkPanelDoneButton"
                 class="editBookmarkPanelBottomButton"
                 label="&editBookmark.done.label;"
                 default="true"
                 oncommand="StarUI.panel.hidePopup();"/>
         <button id="editBookmarkPanelRemoveButton"
--- a/browser/components/extensions/ext-devtools-network.js
+++ b/browser/components/extensions/ext-devtools-network.js
@@ -10,17 +10,17 @@ var {
 } = ExtensionCommon;
 
 this.devtools_network = class extends ExtensionAPI {
   getAPI(context) {
     return {
       devtools: {
         network: {
           onNavigated: new EventManager(context, "devtools.onNavigated", fire => {
-            let listener = (event, data) => {
+            let listener = data => {
               fire.async(data.url);
             };
 
             let targetPromise = getDevToolsTargetForContext(context);
             targetPromise.then(target => {
               target.on("navigate", listener);
             });
             return () => {
--- a/browser/components/extensions/ext-devtools-panels.js
+++ b/browser/components/extensions/ext-devtools-panels.js
@@ -164,17 +164,17 @@ class ParentDevToolsPanel {
 
         this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsPanelShown", {
           toolboxPanelId: this.id,
         });
       }
     }
   }
 
-  async onToolboxPanelSelect(what, id) {
+  async onToolboxPanelSelect(id) {
     if (!this.waitTopLevelContext || !this.panelAdded) {
       return;
     }
 
     // Wait that the panel is fully loaded and emit show.
     await this.waitTopLevelContext;
 
     if (!this.visible && id === this.id) {
@@ -345,17 +345,17 @@ class DevToolsSelectionObserver extends 
     if (this.initialized) {
       this.toolbox.off("selection-changed", this.onSelected);
     }
 
     this.toolbox = null;
     this.destroyed = true;
   }
 
-  onSelected(event) {
+  onSelected() {
     this.emit("selectionChanged");
   }
 }
 
 /**
  * Represents an addon devtools inspector sidebar in the main process.
  *
  * @param {ExtensionChildProxyContext} context
@@ -418,28 +418,28 @@ class ParentDevToolsInspectorSidebar {
 
     this.toolbox.unregisterInspectorExtensionSidebar(this.id);
     this.extensionSidebar = null;
     this._lazySidebarInit = null;
 
     this.destroyed = true;
   }
 
-  onSidebarCreated(evt, sidebar) {
+  onSidebarCreated(sidebar) {
     this.extensionSidebar = sidebar;
 
     const {_lazySidebarInit} = this;
     this._lazySidebarInit = null;
 
     if (typeof _lazySidebarInit === "function") {
       _lazySidebarInit();
     }
   }
 
-  onSidebarSelect(what, id) {
+  onSidebarSelect(id) {
     if (!this.extensionSidebar) {
       return;
     }
 
     if (!this.visible && id === this.id) {
       // TODO: Wait for top level context if extension page
       this.visible = true;
       this.context.parentMessageManager.sendAsyncMessage("Extension:DevToolsInspectorSidebarShown", {
--- a/browser/components/extensions/ext-devtools.js
+++ b/browser/components/extensions/ext-devtools.js
@@ -232,17 +232,17 @@ class DevToolsPageDefinition {
 
     this.url = url;
     this.extension = extension;
 
     // Map[TabTarget -> DevToolsPage]
     this.devtoolsPageForTarget = new Map();
   }
 
-  onThemeChanged(evt, themeName) {
+  onThemeChanged(themeName) {
     Services.ppmm.broadcastAsyncMessage("Extension:DevToolsThemeChanged", {themeName});
   }
 
   buildForToolbox(toolbox) {
     if (this.devtoolsPageForTarget.has(toolbox.target)) {
       return Promise.reject(new Error("DevtoolsPage has been already created for this toolbox"));
     }
 
@@ -299,17 +299,17 @@ let devToolsInitialized = false;
 initDevTools = function() {
   if (devToolsInitialized) {
     return;
   }
 
   /* eslint-disable mozilla/balanced-listeners */
   // Create a devtools page context for a new opened toolbox,
   // based on the registered devtools_page definitions.
-  DevToolsShim.on("toolbox-created", (evt, toolbox) => {
+  DevToolsShim.on("toolbox-created", toolbox => {
     if (!toolbox.target.isLocalTab) {
       // Only local tabs are currently supported (See Bug 1304378 for additional details
       // related to remote targets support).
       let msg = `Ignoring DevTools Toolbox for target "${toolbox.target.toString()}": ` +
                 `"${toolbox.target.name}" ("${toolbox.target.url}"). ` +
                 "Only local tab are currently supported by the WebExtensions DevTools API.";
       let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
       scriptError.init(msg, null, null, null, null, Ci.nsIScriptError.warningFlag, "content javascript");
@@ -320,17 +320,17 @@ initDevTools = function() {
 
     for (let devtoolsPage of devtoolsPageDefinitionMap.values()) {
       devtoolsPage.buildForToolbox(toolbox);
     }
   });
 
   // Destroy a devtools page context for a destroyed toolbox,
   // based on the registered devtools_page definitions.
-  DevToolsShim.on("toolbox-destroy", (evt, target) => {
+  DevToolsShim.on("toolbox-destroy", target => {
     if (!target.isLocalTab) {
       // Only local tabs are currently supported (See Bug 1304378 for additional details
       // related to remote targets support).
       return;
     }
 
     for (let devtoolsPageDefinition of devtoolsPageDefinitionMap.values()) {
       devtoolsPageDefinition.shutdownForTarget(target);
--- a/browser/components/extensions/test/browser/browser_ext_devtools_inspectedWindow_eval_bindings.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_inspectedWindow_eval_bindings.js
@@ -88,19 +88,17 @@ add_task(async function test_devtools_in
 
   // Test that inspect($0) switch the developer toolbox to the inspector.
 
   await gDevTools.showToolbox(target, "styleeditor");
 
   info("Toolbox switched back to the styleeditor panel");
 
   const inspectorPanelSelectedPromise = (async () => {
-    const toolId = await new Promise(resolve => {
-      toolbox.once("select", (evt, toolId) => resolve(toolId));
-    });
+    const toolId = await toolbox.once("select");
 
     if (toolId === "inspector") {
       const selectedNodeName = toolbox.selection.nodeFront &&
                                toolbox.selection.nodeFront._form.nodeName;
       is(selectedNodeName, "HTML", "The expected DOM node has been selected in the inspector");
     } else {
       throw new Error(`inspector panel expected, ${toolId} has been selected instead`);
     }
--- a/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_panel.js
@@ -15,20 +15,20 @@ const DEVTOOLS_THEME_PREF = "devtools.th
  *
  * - devtools.panels.themeName returns the correct value,
  *   both from a page and a panel.
  * - devtools.panels.onThemeChanged fires for theme changes,
  *   both from a page and a panel.
  * - devtools.panels.create is able to create a devtools panel.
  */
 
-async function switchTheme(theme) {
-  const waitforThemeChanged = new Promise(resolve => gDevTools.once("theme-changed", resolve));
+function switchTheme(theme) {
+  const waitforThemeChanged = gDevTools.once("theme-changed");
   Preferences.set(DEVTOOLS_THEME_PREF, theme);
-  await waitforThemeChanged;
+  return waitforThemeChanged;
 }
 
 async function testThemeSwitching(extension, locations = ["page"]) {
   for (let newTheme of ["dark", "light"]) {
     await switchTheme(newTheme);
     for (let location of locations) {
       is(await extension.awaitMessage(`devtools_theme_changed_${location}`),
          newTheme,
@@ -271,35 +271,31 @@ add_task(async function test_devtools_pa
   const secondCycleResults = await extension.awaitMessage("devtools_panel_hidden");
   info("Addon Devtools Panel hidden - second cycle");
 
   is(secondCycleResults.panelCreated, 1, "devtools.panel.create callback has been called once");
   is(secondCycleResults.panelShown, 2, "panel.onShown listener has been called twice");
   is(secondCycleResults.panelHidden, 2, "panel.onHidden listener has been called twice");
 
   // Turn off the addon devtools panel using the visibilityswitch.
-  const waitToolVisibilityOff = new Promise(resolve => {
-    toolbox.once("tool-unregistered", resolve);
-  });
+  const waitToolVisibilityOff = toolbox.once("tool-unregistered");
 
   Services.prefs.setBoolPref(`devtools.webext-${panelId}.enabled`, false);
   gDevTools.emit("tool-unregistered", panelId);
 
   await waitToolVisibilityOff;
 
   ok(toolbox.hasAdditionalTool(panelId),
      "The tool has not been removed on visibilityswitch set to false");
 
   is(toolbox.visibleAdditionalTools.filter(tool => tool.id == panelId).length, 0,
      "The tool is not visible on visibilityswitch set to false");
 
   // Turn on the addon devtools panel using the visibilityswitch.
-  const waitToolVisibilityOn = new Promise(resolve => {
-    toolbox.once("tool-registered", resolve);
-  });
+  const waitToolVisibilityOn = toolbox.once("tool-registered");
 
   Services.prefs.setBoolPref(`devtools.webext-${panelId}.enabled`, true);
   gDevTools.emit("tool-registered", panelId);
 
   await waitToolVisibilityOn;
 
   ok(toolbox.hasAdditionalTool(panelId),
      "The tool has been added on visibilityswitch set to true");
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -52,17 +52,17 @@
  *     - "tags"
  *     - "loadInSidebar"
  *     - "folderPicker" - hides both the tree and the menu.
  *
  * window.arguments[0].performed is set to true if any transaction has
  * been performed by the dialog.
  */
 
-/* import-globals-from editBookmarkOverlay.js */
+/* import-globals-from editBookmark.js */
 /* import-globals-from controller.js */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 const BOOKMARK_ITEM = 0;
 const BOOKMARK_FOLDER = 1;
--- a/browser/components/places/content/bookmarkProperties.xul
+++ b/browser/components/places/content/bookmarkProperties.xul
@@ -1,21 +1,19 @@
 <?xml version="1.0"?>
 
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin/"?>
-<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmark.css"?>
 <?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
 <?xml-stylesheet href="chrome://browser/content/places/places.css"?>
 
-<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
-
 <!DOCTYPE dialog [
   <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
   %editBookmarkOverlayDTD;
 ]>
 
 <dialog id="bookmarkproperties"
         buttons="accept, cancel"
         buttoniconaccept="save"
@@ -28,17 +26,17 @@
         persist="screenX screenY width">
 
   <stringbundleset id="stringbundleset">
     <stringbundle id="stringBundle"
                   src="chrome://browser/locale/places/bookmarkProperties.properties"/>
   </stringbundleset>
 
   <script type="application/javascript"
-          src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+          src="chrome://browser/content/places/editBookmark.js"/>
   <script type="application/javascript"
           src="chrome://browser/content/places/bookmarkProperties.js"/>
   <script type="application/javascript"
           src="chrome://global/content/globalOverlay.js"/>
   <script type="application/javascript"
           src="chrome://browser/content/utilityOverlay.js"/>
   <script type="application/javascript"><![CDATA[
     ChromeUtils.defineModuleGetter(window,
@@ -51,11 +49,11 @@
     ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
     XPCOMUtils.defineLazyScriptGetter(window, "PlacesTreeView",
       "chrome://browser/content/places/treeView.js");
     XPCOMUtils.defineLazyScriptGetter(window,
       ["PlacesInsertionPoint", "PlacesController", "PlacesControllerDragHelper"],
        "chrome://browser/content/places/controller.js");
   ]]></script>
 
-<vbox id="editBookmarkPanelContent"/>
+#include editBookmarkPanel.inc.xul
 
 </dialog>
deleted file mode 100644
--- a/browser/components/places/content/downloadsViewOverlay.xul
+++ /dev/null
@@ -1,47 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<?xul-overlay href="chrome://browser/content/downloads/allDownloadsViewOverlay.xul"?>
-
-<!DOCTYPE overlay [
-<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
-%downloadsDTD;
-]>
-
-<overlay id="downloadsViewOverlay"
-         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-
-  <script type="application/javascript"><![CDATA[
-    const DOWNLOADS_QUERY = "place:transition=" +
-      Ci.nsINavHistoryService.TRANSITION_DOWNLOAD +
-      "&sort=" +
-      Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
-
-    ContentArea.setContentViewForQueryString(DOWNLOADS_QUERY,
-      () => new DownloadsPlacesView(document.getElementById("downloadsRichListBox"), false),
-      { showDetailsPane: false,
-        toolbarSet: "back-button, forward-button, organizeButton, clearDownloadsButton, libraryToolbarSpacer, searchFilter" });
-  ]]></script>
-
-  <window id="places">
-    <commandset id="downloadCommands"/>
-    <menupopup id="downloadsContextMenu"/>
-  </window>
-
-  <deck id="placesViewsDeck">
-    <richlistbox id="downloadsRichListBox"/>
-  </deck>
-
-  <toolbar id="placesToolbar">
-    <toolbarbutton id="clearDownloadsButton"
-#ifdef XP_MACOSX
-                   class="tabbable"
-#endif
-                   insertbefore="libraryToolbarSpacer"
-                   label="&clearDownloadsButton.label;"
-                   command="downloadsCmd_clearDownloads"
-                   tooltiptext="&clearDownloadsButton.tooltip;"/>
-  </toolbar>
-
-</overlay>
rename from browser/components/places/content/editBookmarkOverlay.js
rename to browser/components/places/content/editBookmark.js
rename from browser/components/places/content/editBookmarkOverlay.xul
rename to browser/components/places/content/editBookmarkPanel.inc.xul
--- a/browser/components/places/content/editBookmarkOverlay.xul
+++ b/browser/components/places/content/editBookmarkPanel.inc.xul
@@ -1,188 +1,172 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!DOCTYPE overlay [
-<!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
-%editBookmarkOverlayDTD;
-]>
-
-<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
-<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
-
-<overlay id="editBookmarkOverlay"
-         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<vbox id="editBookmarkPanelContent" flex="1">
+  <hbox id="editBMPanel_selectionCount" pack="center">
+    <label id="editBMPanel_itemsCountText"/>
+  </hbox>
 
-  <vbox id="editBookmarkPanelContent" flex="1">
-    <hbox id="editBMPanel_selectionCount" pack="center">
-      <label id="editBMPanel_itemsCountText"/>
-    </hbox>
+  <grid id="editBookmarkPanelGrid" flex="1">
+    <columns id="editBMPanel_columns">
+      <column id="editBMPanel_labelColumn" />
+      <column flex="1" id="editBMPanel_editColumn" />
+    </columns>
+    <rows id="editBMPanel_rows">
+      <row id="editBMPanel_nameRow"
+           align="center"
+           collapsed="true">
+        <label value="&editBookmarkOverlay.name.label;"
+               class="editBMPanel_rowLabel"
+               accesskey="&editBookmarkOverlay.name.accesskey;"
+               control="editBMPanel_namePicker"/>
+        <textbox id="editBMPanel_namePicker"
+                 onchange="gEditItemOverlay.onNamePickerChange().catch(Components.utils.reportError);"/>
+      </row>
 
-    <grid id="editBookmarkPanelGrid" flex="1">
-      <columns id="editBMPanel_columns">
-        <column id="editBMPanel_labelColumn" />
-        <column flex="1" id="editBMPanel_editColumn" />
-      </columns>
-      <rows id="editBMPanel_rows">
-        <row id="editBMPanel_nameRow"
-             align="center"
-             collapsed="true">
-          <label value="&editBookmarkOverlay.name.label;"
-                 class="editBMPanel_rowLabel"
-                 accesskey="&editBookmarkOverlay.name.accesskey;"
-                 control="editBMPanel_namePicker"/>
-          <textbox id="editBMPanel_namePicker"
-                   onchange="gEditItemOverlay.onNamePickerChange().catch(Components.utils.reportError);"/>
-        </row>
+      <row id="editBMPanel_locationRow"
+           align="center"
+           collapsed="true">
+        <label value="&editBookmarkOverlay.location.label;"
+               class="editBMPanel_rowLabel"
+               accesskey="&editBookmarkOverlay.location.accesskey;"
+               control="editBMPanel_locationField"/>
+        <textbox id="editBMPanel_locationField"
+                 class="uri-element"
+                 onchange="gEditItemOverlay.onLocationFieldChange();"/>
+      </row>
 
-        <row id="editBMPanel_locationRow"
-             align="center"
-             collapsed="true">
-          <label value="&editBookmarkOverlay.location.label;"
-                 class="editBMPanel_rowLabel"
-                 accesskey="&editBookmarkOverlay.location.accesskey;"
-                 control="editBMPanel_locationField"/>
-          <textbox id="editBMPanel_locationField"
-                   class="uri-element"
-                   onchange="gEditItemOverlay.onLocationFieldChange();"/>
-        </row>
-
-        <row id="editBMPanel_folderRow"
-             align="center"
-             collapsed="true">
-          <label value="&editBookmarkOverlay.folder.label;"
-                 class="editBMPanel_rowLabel"
-                 control="editBMPanel_folderMenuList"/>
-          <hbox flex="1" align="center">
-            <menulist id="editBMPanel_folderMenuList"
-                      class="folder-icon"
-                      flex="1"
-                      oncommand="gEditItemOverlay.onFolderMenuListCommand(event).catch(Components.utils.reportError);">
-              <menupopup>
-                <!-- Static item for special folders -->
-                <menuitem id="editBMPanel_toolbarFolderItem"
-                          class="menuitem-iconic folder-icon"/>
-                <menuitem id="editBMPanel_bmRootItem"
-                          class="menuitem-iconic folder-icon"/>
-                <menuitem id="editBMPanel_unfiledRootItem"
-                          class="menuitem-iconic folder-icon"/>
-                <menuseparator id="editBMPanel_chooseFolderSeparator"/>
-                <menuitem id="editBMPanel_chooseFolderMenuItem"
-                          label="&editBookmarkOverlay.choose.label;"
-                          class="menuitem-iconic folder-icon"/>
-                <menuseparator id="editBMPanel_foldersSeparator" hidden="true"/>
-              </menupopup>
-            </menulist>
-            <button id="editBMPanel_foldersExpander"
-                    class="expander-down"
-                    tooltiptext="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
-                    tooltiptextdown="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
-                    tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
-                    oncommand="gEditItemOverlay.toggleFolderTreeVisibility();"/>
-          </hbox>
-        </row>
+      <row id="editBMPanel_folderRow"
+           align="center"
+           collapsed="true">
+        <label value="&editBookmarkOverlay.folder.label;"
+               class="editBMPanel_rowLabel"
+               control="editBMPanel_folderMenuList"/>
+        <hbox flex="1" align="center">
+          <menulist id="editBMPanel_folderMenuList"
+                    class="folder-icon"
+                    flex="1"
+                    oncommand="gEditItemOverlay.onFolderMenuListCommand(event).catch(Components.utils.reportError);">
+            <menupopup>
+              <!-- Static item for special folders -->
+              <menuitem id="editBMPanel_toolbarFolderItem"
+                        class="menuitem-iconic folder-icon"/>
+              <menuitem id="editBMPanel_bmRootItem"
+                        class="menuitem-iconic folder-icon"/>
+              <menuitem id="editBMPanel_unfiledRootItem"
+                        class="menuitem-iconic folder-icon"/>
+              <menuseparator id="editBMPanel_chooseFolderSeparator"/>
+              <menuitem id="editBMPanel_chooseFolderMenuItem"
+                        label="&editBookmarkOverlay.choose.label;"
+                        class="menuitem-iconic folder-icon"/>
+              <menuseparator id="editBMPanel_foldersSeparator" hidden="true"/>
+            </menupopup>
+          </menulist>
+          <button id="editBMPanel_foldersExpander"
+                  class="expander-down"
+                  tooltiptext="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
+                  tooltiptextdown="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
+                  tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
+                  oncommand="gEditItemOverlay.toggleFolderTreeVisibility();"/>
+        </hbox>
+      </row>
 
-        <row id="editBMPanel_folderTreeRow"
-             collapsed="true"
-             flex="1">
-          <spacer/>
-          <vbox flex="1">
-            <tree id="editBMPanel_folderTree"
-                  flex="1"
-                  class="placesTree"
-                  type="places"
-                  height="150"
-                  minheight="150"
-                  editable="true"
-                  onselect="gEditItemOverlay.onFolderTreeSelect();"
-                  disableUserActions="true"
-                  hidecolumnpicker="true">
-              <treecols>
-                <treecol anonid="title" flex="1" primary="true" hideheader="true"/>
-              </treecols>
-              <treechildren flex="1"/>
-            </tree>
+      <row id="editBMPanel_folderTreeRow"
+           collapsed="true"
+           flex="1">
+        <spacer/>
+        <vbox flex="1">
+          <tree id="editBMPanel_folderTree"
+                flex="1"
+                class="placesTree"
+                type="places"
+                height="150"
+                minheight="150"
+                editable="true"
+                onselect="gEditItemOverlay.onFolderTreeSelect();"
+                disableUserActions="true"
+                hidecolumnpicker="true">
+            <treecols>
+              <treecol anonid="title" flex="1" primary="true" hideheader="true"/>
+            </treecols>
+            <treechildren flex="1"/>
+          </tree>
 
-            <hbox id="editBMPanel_newFolderBox">
-              <button label="&editBookmarkOverlay.newFolderButton.label;"
-                      id="editBMPanel_newFolderButton"
-                      accesskey="&editBookmarkOverlay.newFolderButton.accesskey;"
-                      oncommand="gEditItemOverlay.newFolder().catch(Components.utils.reportError);"/>
-            </hbox>
-          </vbox>
-        </row>
+          <hbox id="editBMPanel_newFolderBox">
+            <button label="&editBookmarkOverlay.newFolderButton.label;"
+                    id="editBMPanel_newFolderButton"
+                    accesskey="&editBookmarkOverlay.newFolderButton.accesskey;"
+                    oncommand="gEditItemOverlay.newFolder().catch(Components.utils.reportError);"/>
+          </hbox>
+        </vbox>
+      </row>
 
-        <row id="editBMPanel_tagsRow"
-             align="center"
-             collapsed="true">
-          <label value="&editBookmarkOverlay.tags.label;"
-                 class="editBMPanel_rowLabel"
-                 accesskey="&editBookmarkOverlay.tags.accesskey;"
-                 control="editBMPanel_tagsField"/>
-          <hbox flex="1" align="center">
-            <textbox id="editBMPanel_tagsField"
-                     type="autocomplete"
-                     flex="1"
-                     autocompletesearch="places-tag-autocomplete"
-                     autocompletepopup="PopupAutoComplete"
-                     completedefaultindex="true"
-                     tabscrolling="true"
-                     placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;"
-                     onchange="gEditItemOverlay.onTagsFieldChange();"/>
-            <button id="editBMPanel_tagsSelectorExpander"
-                    class="expander-down"
-                    tooltiptext="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
-                    tooltiptextdown="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
-                    tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
-                    oncommand="gEditItemOverlay.toggleTagsSelector();"/>
-          </hbox>
-        </row>
+      <row id="editBMPanel_tagsRow"
+           align="center"
+           collapsed="true">
+        <label value="&editBookmarkOverlay.tags.label;"
+               class="editBMPanel_rowLabel"
+               accesskey="&editBookmarkOverlay.tags.accesskey;"
+               control="editBMPanel_tagsField"/>
+        <hbox flex="1" align="center">
+          <textbox id="editBMPanel_tagsField"
+                   type="autocomplete"
+                   flex="1"
+                   autocompletesearch="places-tag-autocomplete"
+                   autocompletepopup="PopupAutoComplete"
+                   completedefaultindex="true"
+                   tabscrolling="true"
+                   placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;"
+                   onchange="gEditItemOverlay.onTagsFieldChange();"/>
+          <button id="editBMPanel_tagsSelectorExpander"
+                  class="expander-down"
+                  tooltiptext="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
+                  tooltiptextdown="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
+                  tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
+                  oncommand="gEditItemOverlay.toggleTagsSelector();"/>
+        </hbox>
+      </row>
 
-        <row id="editBMPanel_tagsSelectorRow"
-             align="center"
-             collapsed="true">
-          <spacer/>
-          <listbox id="editBMPanel_tagsSelector"
-                   height="150"/>
-        </row>
+      <row id="editBMPanel_tagsSelectorRow"
+           align="center"
+           collapsed="true">
+        <spacer/>
+        <listbox id="editBMPanel_tagsSelector"
+                 height="150"/>
+      </row>
 
-        <row id="editBMPanel_keywordRow"
-             align="center"
-             collapsed="true">
-          <observes element="additionalInfoBroadcaster" attribute="hidden"/>
-          <label value="&editBookmarkOverlay.keyword.label;"
-                 class="editBMPanel_rowLabel"
-                 accesskey="&editBookmarkOverlay.keyword.accesskey;"
-                 control="editBMPanel_keywordField"/>
-          <textbox id="editBMPanel_keywordField"
-                   onchange="gEditItemOverlay.onKeywordFieldChange();"/>
-        </row>
+      <row id="editBMPanel_keywordRow"
+           align="center"
+           collapsed="true">
+        <observes element="additionalInfoBroadcaster" attribute="hidden"/>
+        <label value="&editBookmarkOverlay.keyword.label;"
+               class="editBMPanel_rowLabel"
+               accesskey="&editBookmarkOverlay.keyword.accesskey;"
+               control="editBMPanel_keywordField"/>
+        <textbox id="editBMPanel_keywordField"
+                 onchange="gEditItemOverlay.onKeywordFieldChange();"/>
+      </row>
 
-        <row id="editBMPanel_descriptionRow"
-             collapsed="true">
-          <observes element="additionalInfoBroadcaster" attribute="hidden"/>
-          <label value="&editBookmarkOverlay.description.label;"
-                 class="editBMPanel_rowLabel"
-                 accesskey="&editBookmarkOverlay.description.accesskey;"
-                 control="editBMPanel_descriptionField"/>
-          <textbox id="editBMPanel_descriptionField"
-                   multiline="true"
-                   rows="4"
-                   onchange="gEditItemOverlay.onDescriptionFieldChange();"/>
-        </row>
-      </rows>
-    </grid>
+      <row id="editBMPanel_descriptionRow"
+           collapsed="true">
+        <observes element="additionalInfoBroadcaster" attribute="hidden"/>
+        <label value="&editBookmarkOverlay.description.label;"
+               class="editBMPanel_rowLabel"
+               accesskey="&editBookmarkOverlay.description.accesskey;"
+               control="editBMPanel_descriptionField"/>
+        <textbox id="editBMPanel_descriptionField"
+                 multiline="true"
+                 rows="4"
+                 onchange="gEditItemOverlay.onDescriptionFieldChange();"/>
+      </row>
+    </rows>
+  </grid>
 
-    <checkbox id="editBMPanel_loadInSidebarCheckbox"
-              collapsed="true"
-              label="&editBookmarkOverlay.loadInSidebar.label;"
-              accesskey="&editBookmarkOverlay.loadInSidebar.accesskey;"
-              oncommand="gEditItemOverlay.onLoadInSidebarCheckboxCommand();">
-      <observes element="additionalInfoBroadcaster" attribute="hidden"/>
-    </checkbox>
+  <checkbox id="editBMPanel_loadInSidebarCheckbox"
+            collapsed="true"
+            label="&editBookmarkOverlay.loadInSidebar.label;"
+            accesskey="&editBookmarkOverlay.loadInSidebar.accesskey;"
+            oncommand="gEditItemOverlay.onLoadInSidebarCheckboxCommand();">
+    <observes element="additionalInfoBroadcaster" attribute="hidden"/>
+  </checkbox>
 
-    <!-- If the ids are changing or additional fields are being added, be sure
-         to sync the values in places.js -->
-    <broadcaster id="additionalInfoBroadcaster"/>
-  </vbox>
-</overlay>
+  <!-- If the ids are changing or additional fields are being added, be sure
+       to sync the values in places.js -->
+  <broadcaster id="additionalInfoBroadcaster"/>
+</vbox>
\ No newline at end of file
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -1,18 +1,19 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* import-globals-from editBookmarkOverlay.js */
-// Via downloadsViewOverlay.xul -> allDownloadsViewOverlay.xul
+/* import-globals-from editBookmark.js */
+// Via allDownloadsViewOverlay.xul
 /* import-globals-from ../../../../toolkit/content/contentAreaUtils.js */
 /* import-globals-from ../PlacesUIUtils.jsm */
 /* import-globals-from ../../../../toolkit/components/places/PlacesUtils.jsm */
+/* import-globals-from ../../downloads/content/allDownloadsViewOverlay.js */
 
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/TelemetryStopwatch.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "MigrationUtils",
                                "resource:///modules/MigrationUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "BookmarkJSONUtils",
@@ -23,17 +24,17 @@ ChromeUtils.defineModuleGetter(this, "Do
                                "resource://gre/modules/DownloadUtils.jsm");
 
 const RESTORE_FILEPICKER_FILTER_EXT = "*.json;*.jsonlz4";
 const HISTORY_LIBRARY_SEARCH_TELEMETRY = "PLACES_HISTORY_LIBRARY_SEARCH_TIME_MS";
 
 var PlacesOrganizer = {
   _places: null,
 
-  // IDs of fields from editBookmarkOverlay that should be hidden when infoBox
+  // IDs of fields from editBookmark that should be hidden when infoBox
   // is minimal. IDs should be kept in sync with the IDs of the elements
   // observing additionalInfoBroadcaster.
   _additionalInfoFields: [
     "editBMPanel_descriptionRow",
     "editBMPanel_loadInSidebarCheckbox",
     "editBMPanel_keywordRow",
   ],
 
@@ -129,16 +130,27 @@ var PlacesOrganizer = {
       }
     } finally {
       if (!selectWasSuppressed)
         this._places.view.selection.selectEventsSuppressed = false;
     }
   },
 
   init: function PO_init() {
+    // Register the downloads view.
+    const DOWNLOADS_QUERY = "place:transition=" +
+      Ci.nsINavHistoryService.TRANSITION_DOWNLOAD +
+      "&sort=" +
+      Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
+
+    ContentArea.setContentViewForQueryString(DOWNLOADS_QUERY,
+      () => new DownloadsPlacesView(document.getElementById("downloadsRichListBox"), false),
+      { showDetailsPane: false,
+        toolbarSet: "back-button, forward-button, organizeButton, clearDownloadsButton, libraryToolbarSpacer, searchFilter" });
+
     ContentArea.init();
 
     this._places = document.getElementById("placesList");
     this._initFolderTree();
 
     var leftPaneSelection = "AllBookmarks"; // default to all-bookmarks
     if (window.arguments && window.arguments[0])
       leftPaneSelection = window.arguments[0];
--- a/browser/components/places/content/places.xul
+++ b/browser/components/places/content/places.xul
@@ -5,30 +5,34 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 <?xml-stylesheet href="chrome://browser/content/places/places.css"?>
 <?xml-stylesheet href="chrome://browser/content/places/organizer.css"?>
 
 <?xml-stylesheet href="chrome://global/skin/"?>
 <?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
 <?xml-stylesheet href="chrome://browser/skin/places/organizer.css"?>
-
-<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmark.css"?>
 
 #ifdef XP_MACOSX
 <?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
 #endif
+<?xul-overlay href="chrome://browser/content/downloads/allDownloadsViewOverlay.xul"?>
 
 <!DOCTYPE window [
 <!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
 %placesDTD;
+<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+%downloadsDTD;
 <!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
 %editMenuOverlayDTD;
 <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
 %browserDTD;
+<!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+%editBookmarkOverlayDTD;
 #ifdef XP_MACOSX
 <!ENTITY % baseMenuOverlayDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd">
 %baseMenuOverlayDTD;
 #endif
 ]>
 
 <window id="places"
         title="&places.library.title;"
@@ -42,17 +46,17 @@
         toggletoolbar="true"
         persist="width height screenX screenY sizemode">
 
   <script type="application/javascript"
           src="chrome://browser/content/places/places.js"/>
 #ifndef XP_MACOSX
   <!-- On Mac, these are included via macBrowserOverlay.xul -> browser.js -> defineLazyScriptGetter -->
   <script type="application/javascript"
-          src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+          src="chrome://browser/content/places/editBookmark.js"/>
   <script type="application/javascript"
           src="chrome://global/content/globalOverlay.js"/>
   <script type="application/javascript"
           src="chrome://browser/content/utilityOverlay.js"/>
   <script type="application/javascript"><![CDATA[
     ChromeUtils.defineModuleGetter(window,
       "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
     ChromeUtils.defineModuleGetter(window,
@@ -98,17 +102,17 @@
              oncommand="PlacesOrganizer.saveSearch();"/>
     <command id="OrganizerCommand_search:moreCriteria"
              oncommand="PlacesQueryBuilder.addRow();"/>
     <command id="OrganizerCommand:Back"
              oncommand="PlacesOrganizer.back();"/>
     <command id="OrganizerCommand:Forward"
              oncommand="PlacesOrganizer.forward();"/>
   </commandset>
-
+  <commandset id="downloadCommands"/>
 
   <keyset id="placesOrganizerKeyset">
     <!-- Instantiation Keys -->
     <key id="placesKey_close" key="&cmd.close.key;" modifiers="accel"
          oncommand="close();"/>
 
     <!-- Command Keys -->
     <key id="placesKey_find:all"
@@ -155,16 +159,17 @@
   </keyset>
 #endif
 
   <popupset id="placesPopupset">
 #include placesContextMenu.inc.xul
     <menupopup id="placesColumnsContext"
                onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);"
                oncommand="ViewMenu.showHideColumn(event.target); event.stopPropagation();"/>
+    <menupopup id="downloadsContextMenu"/>
   </popupset>
 
   <toolbox id="placesToolbox">
     <toolbar class="chromeclass-toolbar" id="placesToolbar" align="center">
       <toolbarbutton id="back-button"
                      command="OrganizerCommand:Back"
                      tooltiptext="&backButton.tooltip;"
                      disabled="true"/>
@@ -344,16 +349,24 @@
       <spacer id="libraryToolbarSpacer" flex="1"/>
 
       <textbox id="searchFilter"
                type="search"
                aria-controls="placeContent"
                oncommand="PlacesSearchBox.search(this.value);"
                collection="bookmarks">
       </textbox>
+      <toolbarbutton id="clearDownloadsButton"
+#ifdef XP_MACOSX
+                     class="tabbable"
+#endif
+                     insertbefore="libraryToolbarSpacer"
+                     label="&clearDownloadsButton.label;"
+                     command="downloadsCmd_clearDownloads"
+                     tooltiptext="&clearDownloadsButton.tooltip;"/>
     </toolbar>
   </toolbox>
 
   <hbox flex="1" id="placesView">
     <tree id="placesList"
           class="plain placesTree"
           type="places"
           hidecolumnpicker="true" context="placesContext"
@@ -410,29 +423,30 @@
             <treecol label="&col.dateadded.label;" id="placesContentDateAdded" anonid="dateAdded" flex="1" hidden="true"
                       persist="width hidden ordinal sortActive sortDirection"/>
             <splitter class="tree-splitter"/>
             <treecol label="&col.lastmodified.label;" id="placesContentLastModified" anonid="lastModified" flex="1" hidden="true"
                       persist="width hidden ordinal sortActive sortDirection"/>
           </treecols>
           <treechildren flex="1" onclick="ContentTree.onClick(event);"/>
         </tree>
+        <richlistbox id="downloadsRichListBox"/>
       </deck>
       <deck id="detailsDeck" style="height: 11em;">
         <vbox id="itemsCountBox" align="center">
           <spacer flex="3"/>
           <label id="itemsCountText"/>
           <spacer flex="1"/>
           <description id="selectItemDescription">
               &detailsPane.selectAnItemText.description;
           </description>
           <spacer flex="3"/>
         </vbox>
         <vbox id="infoBox" minimal="true">
-          <vbox id="editBookmarkPanelContent" flex="1"/>
+#include editBookmarkPanel.inc.xul
           <hbox id="infoBoxExpanderWrapper" align="center">
 
             <button type="image" id="infoBoxExpander"
                     class="expander-down"
                     oncommand="PlacesOrganizer.toggleAdditionalInfoFields();"
                     observes="paneElementsBroadcaster"/>
 
             <label id="infoBoxExpanderLabel"
--- a/browser/components/places/jar.mn
+++ b/browser/components/places/jar.mn
@@ -1,31 +1,28 @@
 # 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/.
 
 browser.jar:
-% overlay chrome://browser/content/places/places.xul chrome://browser/content/places/downloadsViewOverlay.xul
 # Provide another URI for the bookmarkProperties dialog so we can persist the
 # attributes separately
-    content/browser/places/bookmarkProperties2.xul       (content/bookmarkProperties.xul)
+*   content/browser/places/bookmarkProperties2.xul       (content/bookmarkProperties.xul)
 *   content/browser/places/places.xul                    (content/places.xul)
     content/browser/places/places.js                     (content/places.js)
     content/browser/places/places.css                    (content/places.css)
     content/browser/places/organizer.css                 (content/organizer.css)
-    content/browser/places/bookmarkProperties.xul        (content/bookmarkProperties.xul)
+*   content/browser/places/bookmarkProperties.xul        (content/bookmarkProperties.xul)
     content/browser/places/bookmarkProperties.js         (content/bookmarkProperties.js)
     content/browser/places/menu.xml                      (content/menu.xml)
     content/browser/places/tree.xml                      (content/tree.xml)
     content/browser/places/controller.js                 (content/controller.js)
     content/browser/places/treeView.js                   (content/treeView.js)
     content/browser/places/browserPlacesViews.js         (content/browserPlacesViews.js)
 # keep the Places version of the history sidebar at history/history-panel.xul
 # to prevent having to worry about between versions of the browser
 *   content/browser/history/history-panel.xul            (content/history-panel.xul)
     content/browser/places/history-panel.js              (content/history-panel.js)
 # ditto for the bookmarks sidebar
 *   content/browser/bookmarks/bookmarksPanel.xul         (content/bookmarksPanel.xul)
     content/browser/bookmarks/bookmarksPanel.js          (content/bookmarksPanel.js)
     content/browser/bookmarks/sidebarUtils.js            (content/sidebarUtils.js)
-    content/browser/places/editBookmarkOverlay.xul       (content/editBookmarkOverlay.xul)
-    content/browser/places/editBookmarkOverlay.js        (content/editBookmarkOverlay.js)
-*   content/browser/places/downloadsViewOverlay.xul      (content/downloadsViewOverlay.xul)
+    content/browser/places/editBookmark.js               (content/editBookmark.js)
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -46,22 +46,22 @@ subsuite = clipboard
 [browser_cutting_bookmarks.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 subsuite = clipboard
 [browser_drag_bookmarks_on_toolbar.js]
 [browser_enable_toolbar_sidebar.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_forgetthissite_single.js]
 [browser_history_sidebar_search.js]
-[browser_library_batch_delete.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_library_commands.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_library_delete_bookmarks_in_tags.js]
 [browser_library_delete_tags.js]
+[browser_library_delete.js]
 [browser_library_downloads.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_library_infoBox.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_library_left_pane_middleclick.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_library_left_pane_select_hierarchy.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
@@ -94,15 +94,20 @@ skip-if = (os == "mac" && debug) # Bug 1
 [browser_sort_in_library.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_stayopenmenu.js]
 skip-if = os == "mac" && debug # bug 1400323
 [browser_toolbar_drop_text.js]
 [browser_toolbar_overflow.js]
 [browser_toolbarbutton_menu_context.js]
 [browser_views_iconsupdate.js]
+[browser_bug485100-change-case-loses-tag.js]
+[browser_editBookmark_tags_liveUpdate.js]
+[browser_bug427633_no_newfolder_if_noip.js]
+[browser_editBookmark_keywords.js]
+[browser_bug631374_tags_selector_scroll.js]
 support-files =
   favicon-normal16.png
 [browser_views_liveupdate.js]
 [browser_bookmark_all_tabs.js]
 support-files =
   bookmark_dummy_1.html
   bookmark_dummy_2.html
rename from browser/components/places/tests/chrome/test_bug427633_no_newfolder_if_noip.xul
rename to browser/components/places/tests/browser/browser_bug427633_no_newfolder_if_noip.js
--- a/browser/components/places/tests/chrome/test_bug427633_no_newfolder_if_noip.xul
+++ b/browser/components/places/tests/browser/browser_bug427633_no_newfolder_if_noip.js
@@ -1,92 +1,44 @@
-<?xml version="1.0"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
-                 type="text/css"?>
+"use strict";
 
-<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
-<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
-<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+/**
+ * Bug 427633 - Disable creating a New Folder in the bookmarks dialogs if
+ * insertionPoint is invalid.
+ */
 
-<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
-
-<!DOCTYPE window [
-  <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
-  %editBookmarkOverlayDTD;
-]>
+const TEST_URL = "about:buildconfig";
 
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="Bug 427633 - Disable creating a New Folder in the bookmarks dialogs if insertionPoint is invalid"
-        onload="runTest();">
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
-  <script type="application/javascript"
-          src="chrome://browser/content/places/editBookmarkOverlay.js"/>
-  <script type="application/javascript" src="head.js" />
-
+add_task(async function() {
+  let tab = await BrowserTestUtils.openNewForegroundTab({
+    gBrowser,
+    opening: TEST_URL,
+    waitForStateStop: true
+  });
 
-  <body xmlns="http://www.w3.org/1999/xhtml" />
-
-  <vbox id="editBookmarkPanelContent"/>
-
-  <script type="application/javascript">
-  <![CDATA[
-
-     /**
-      * Bug 427633 - Disable creating a New Folder in the bookmarks dialogs if
-      * insertionPoint is invalid.
-      */
+  registerCleanupFunction(async () => {
+    bookmarkPanel.removeAttribute("animate");
+    await BrowserTestUtils.removeTab(tab);
+    await PlacesUtils.bookmarks.eraseEverything();
+  });
 
-    function runTest() {
-      SimpleTest.waitForExplicitFinish();
+  let bookmarkPanel = document.getElementById("editBookmarkPanel");
+  bookmarkPanel.setAttribute("animate", false);
+
+  let shownPromise = promisePopupShown(bookmarkPanel);
 
-      (async function() {
-        // Add a bookmark.
-        let bm = await PlacesUtils.bookmarks.insert({
-          parentGuid: PlacesUtils.bookmarks.toolbarGuid,
-          url: "http://www.example.com/",
-          index: PlacesUtils.bookmarks.DEFAULT_INDEX,
-          type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
-          title: "mozilla"
-        });
+  let bookmarkStar = BookmarkingUI.star;
+  bookmarkStar.click();
 
-        // Init panel.
-        ok(gEditItemOverlay, "gEditItemOverlay is in context");
-        let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
-        gEditItemOverlay.initPanel({ node });
-        ok(gEditItemOverlay.initialized, "gEditItemOverlay is initialized");
-
-        let tree = gEditItemOverlay._element("folderTree");
-        await openFolderTree(tree);
+  await shownPromise;
 
-        tree.view.selection.clearSelection();
-        ok(document.getElementById("editBMPanel_newFolderButton").disabled,
-           "New folder button is disabled if there's no selection");
+  ok(gEditItemOverlay, "gEditItemOverlay is in context");
+  ok(gEditItemOverlay.initialized, "gEditItemOverlay is initialized");
 
-        // Cleanup.
-        await PlacesUtils.bookmarks.remove(bm.guid);
-      })().then(() => SimpleTest.finish());
-    }
+  window.gEditItemOverlay.toggleFolderTreeVisibility();
 
-    function openFolderTree(tree) {
-      return new Promise(resolve => {
-        tree.addEventListener("DOMAttrModified", function onAttrModified(event) {
-          if (event.attrName == "place") {
-            tree.removeEventListener("DOMAttrModified", onAttrModified);
-            resolve();
-          }
-        });
+  let tree = gEditItemOverlay._element("folderTree");
 
-        // Open the folder tree.
-        document.getElementById("editBMPanel_foldersExpander").doCommand();
-      });
-    }
-  ]]>
-  </script>
+  tree.view.selection.clearSelection();
+  ok(document.getElementById("editBMPanel_newFolderButton").disabled,
+     "New folder button is disabled if there's no selection");
 
-</window>
+});
rename from browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul
rename to browser/components/places/tests/browser/browser_bug485100-change-case-loses-tag.js
--- a/browser/components/places/tests/chrome/test_bug485100-change-case-loses-tag.xul
+++ b/browser/components/places/tests/browser/browser_bug485100-change-case-loses-tag.js
@@ -1,90 +1,48 @@
-<?xml version="1.0"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
-                 type="text/css"?>
+"use strict";
 
-<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
-<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
-<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
-
-<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
-
-<!DOCTYPE window [
-  <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
-  %editBookmarkOverlayDTD;
-]>
+add_task(async function() {
+  let testTag = "foo";
+  let testTagUpper = "Foo";
+  let testURI = Services.io.newURI("http://www.example.com/");
 
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="485100: Exchanging a letter of a tag name with its big/small equivalent removes tag from bookmark"
-        onload="runTest();">
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
-  <script type="application/javascript"
-          src="chrome://browser/content/places/editBookmarkOverlay.js"/>
-  <script type="application/javascript" src="head.js" />
+  // Add a bookmark.
+  let bm = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+    type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+    title: "mozilla",
+    url: testURI
+  });
 
-  <body xmlns="http://www.w3.org/1999/xhtml" />
-
-  <vbox id="editBookmarkPanelContent"/>
-
-  <script type="application/javascript">
-  <![CDATA[
-    function runTest() {
-      SimpleTest.waitForExplicitFinish();
-      (async function() {
-        let testTag = "foo";
-        let testTagUpper = "Foo";
-        let testURI = Services.io.newURI("http://www.example.com/");
+  // Init panel
+  ok(gEditItemOverlay, "gEditItemOverlay is in context");
+  let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
+  gEditItemOverlay.initPanel({ node });
 
-        // Add a bookmark.
-        let bm = await PlacesUtils.bookmarks.insert({
-          parentGuid: PlacesUtils.bookmarks.toolbarGuid,
-          index: PlacesUtils.bookmarks.DEFAULT_INDEX,
-          type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
-          title: "mozilla",
-          url: testURI
-        });
+  // add a tag
+  document.getElementById("editBMPanel_tagsField").value = testTag;
+  let promiseNotification = PlacesTestUtils.waitForNotification(
+    "onItemChanged", (id, property) => property == "tags");
+  gEditItemOverlay.onTagsFieldChange();
+  await promiseNotification;
 
-        // Init panel
-        ok(gEditItemOverlay, "gEditItemOverlay is in context");
-        let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
-        gEditItemOverlay.initPanel({ node });
-
-        // add a tag
-        document.getElementById("editBMPanel_tagsField").value = testTag;
-        let promiseNotification = PlacesTestUtils.waitForNotification(
-          "onItemChanged", (id, property) => property == "tags");
-        gEditItemOverlay.onTagsFieldChange();
-        await promiseNotification;
+  // test that the tag has been added in the backend
+  is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTag, "tags match");
 
-        // test that the tag has been added in the backend
-        is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTag, "tags match");
-
-        // change the tag
-        document.getElementById("editBMPanel_tagsField").value = testTagUpper;
-        // The old sync API doesn't notify a tags change, and fixing it would be
-        // quite complex, so we just wait for a title change until tags are
-        // refactored.
-        promiseNotification = PlacesTestUtils.waitForNotification(
-          "onItemChanged", (id, property) => property == "title");
-        gEditItemOverlay.onTagsFieldChange();
-        await promiseNotification;
+  // change the tag
+  document.getElementById("editBMPanel_tagsField").value = testTagUpper;
+  // The old sync API doesn't notify a tags change, and fixing it would be
+  // quite complex, so we just wait for a title change until tags are
+  // refactored.
+  promiseNotification = PlacesTestUtils.waitForNotification(
+    "onItemChanged", (id, property) => property == "title");
+  gEditItemOverlay.onTagsFieldChange();
+  await promiseNotification;
 
-        // test that the tag has been added in the backend
-        is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTagUpper, "tags match");
+  // test that the tag has been added in the backend
+  is(PlacesUtils.tagging.getTagsForURI(testURI)[0], testTagUpper, "tags match");
 
-        // Cleanup.
-        PlacesUtils.tagging.untagURI(testURI, [testTag]);
-        await PlacesUtils.bookmarks.remove(bm.guid);
-      })().then(() => SimpleTest.finish());
-    }
-  ]]>
-  </script>
-
-</window>
+  // Cleanup.
+  PlacesUtils.tagging.untagURI(testURI, [testTag]);
+  await PlacesUtils.bookmarks.remove(bm.guid);
+});
rename from browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul
rename to browser/components/places/tests/browser/browser_bug631374_tags_selector_scroll.js
--- a/browser/components/places/tests/chrome/test_bug631374_tags_selector_scroll.xul
+++ b/browser/components/places/tests/browser/browser_bug631374_tags_selector_scroll.js
@@ -1,170 +1,150 @@
-<?xml version="1.0"?>
-
-<!-- Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/ -->
-
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
-                 type="text/css"?>
+ /**
+  * This test checks that editing tags doesn't scroll the tags selector
+  * listbox to wrong positions.
+  */
 
-<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
-<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
-<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+const TEST_URL = "about:buildconfig";
 
-<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
-
-<!DOCTYPE window [
-  <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
-  %editBookmarkOverlayDTD;
-]>
+add_task(async function() {
+  await PlacesUtils.bookmarks.eraseEverything();
+  let tags = ["a", "b", "c", "d", "e", "f", "g",
+              "h", "i", "l", "m", "n", "o", "p"];
 
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="Bug 631374 - Editing tags in the selector scrolls up the listbox"
-        onload="runTest();">
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
-  <script type="application/javascript"
-          src="chrome://browser/content/places/editBookmarkOverlay.js"/>
-  <script type="application/javascript" src="head.js" />
+  // Add a bookmark and tag it.
+  let uri1 = Services.io.newURI(TEST_URL);
+  let bm1 = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    title: "mozilla",
+    url: uri1.spec
+  });
+  PlacesUtils.tagging.tagURI(uri1, tags);
 
-  <body xmlns="http://www.w3.org/1999/xhtml" />
-
-  <vbox id="editBookmarkPanelContent"/>
-
-  <script type="application/javascript">
-  <![CDATA[
-     /**
-      * This test checks that editing tags doesn't scroll the tags selector
-      * listbox to wrong positions.
-      */
+  // Add a second bookmark so that tags won't disappear when unchecked.
+  let uri2 = Services.io.newURI("http://www2.mozilla.org/");
+  let bm2 = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    title: "mozilla",
+    url: uri2.spec
+  });
+  PlacesUtils.tagging.tagURI(uri2, tags);
 
-    function runTest() {
-      SimpleTest.waitForExplicitFinish();
-      (async function() {
-        await PlacesUtils.bookmarks.eraseEverything();
-        let tags = ["a", "b", "c", "d", "e", "f", "g",
-                    "h", "i", "l", "m", "n", "o", "p"];
+  let tab = await BrowserTestUtils.openNewForegroundTab({
+    gBrowser,
+    opening: TEST_URL,
+    waitForStateStop: true
+  });
 
-        // Add a bookmark and tag it.
-        let uri1 = Services.io.newURI("http://www1.mozilla.org/");
-        let bm1 = await PlacesUtils.bookmarks.insert({
-          parentGuid: PlacesUtils.bookmarks.toolbarGuid,
-          title: "mozilla",
-          url: uri1.spec
-        });
-        PlacesUtils.tagging.tagURI(uri1, tags);
+  registerCleanupFunction(async () => {
+    bookmarkPanel.removeAttribute("animate");
+    await BrowserTestUtils.removeTab(tab);
+    await PlacesUtils.bookmarks.eraseEverything();
+  });
+
+  let bookmarkPanel = document.getElementById("editBookmarkPanel");
+  bookmarkPanel.setAttribute("animate", false);
+  let shownPromise = promisePopupShown(bookmarkPanel);
+
+  let bookmarkStar = BookmarkingUI.star;
+  bookmarkStar.click();
 
-        // Add a second bookmark so that tags won't disappear when unchecked.
-        let uri2 = Services.io.newURI("http://www2.mozilla.org/");
-        let bm2 = await PlacesUtils.bookmarks.insert({
-          parentGuid: PlacesUtils.bookmarks.toolbarGuid,
-          title: "mozilla",
-          url: uri2.spec
-        });
-        PlacesUtils.tagging.tagURI(uri2, tags);
+  await shownPromise;
+
+  // Init panel.
+  ok(gEditItemOverlay, "gEditItemOverlay is in context");
+  ok(gEditItemOverlay.initialized, "gEditItemOverlay is initialized");
+
+  await openTagSelector();
+  let tagsSelector = document.getElementById("editBMPanel_tagsSelector");
 
-        // Init panel.
-        ok(gEditItemOverlay, "gEditItemOverlay is in context");
-        let node1 = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm1);
-        gEditItemOverlay.initPanel({ node: node1 });
-        ok(gEditItemOverlay.initialized, "gEditItemOverlay is initialized");
-
-        await openTagSelector();
-        let tagsSelector = document.getElementById("editBMPanel_tagsSelector");
+  // Go by two so there is some untouched tag in the middle.
+  for (let i = 8; i < tags.length; i += 2) {
+    tagsSelector.selectedIndex = i;
+    let listItem = tagsSelector.selectedItem;
+    isnot(listItem, null, "Valid listItem found");
 
-        // Go by two so there is some untouched tag in the middle.
-        for (let i = 8; i < tags.length; i += 2) {
-          tagsSelector.selectedIndex = i;
-          let listItem = tagsSelector.selectedItem;
-          isnot(listItem, null, "Valid listItem found");
+    tagsSelector.ensureElementIsVisible(listItem);
+    let visibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
 
-          tagsSelector.ensureElementIsVisible(listItem);
-          let visibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
+    ok(listItem.checked, "Item is checked " + i);
+    let selectedTag = listItem.label;
 
-          ok(listItem.checked, "Item is checked " + i);
-          let selectedTag = listItem.label;
+    // Uncheck the tag.
+    let promiseNotification = PlacesTestUtils.waitForNotification(
+      "onItemChanged", (id, property) => property == "tags");
+    listItem.checked = false;
+    await promiseNotification;
+    is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(),
+       "Scroll position did not change");
 
-          // Uncheck the tag.
-          let promiseNotification = PlacesTestUtils.waitForNotification(
-            "onItemChanged", (id, property) => property == "tags");
-          listItem.checked = false;
-          await promiseNotification;
-          is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(),
-             "Scroll position did not change");
-
-          // The listbox is rebuilt, so we have to get the new element.
-          let newItem = tagsSelector.selectedItem;
-          isnot(newItem, null, "Valid new listItem found");
-          ok(!newItem.checked, "New listItem is unchecked " + i);
-          is(newItem.label, selectedTag, "Correct tag is still selected");
+    // The listbox is rebuilt, so we have to get the new element.
+    let newItem = tagsSelector.selectedItem;
+    isnot(newItem, null, "Valid new listItem found");
+    ok(!newItem.checked, "New listItem is unchecked " + i);
+    is(newItem.label, selectedTag, "Correct tag is still selected");
 
-          // Check the tag.
-          promiseNotification = PlacesTestUtils.waitForNotification(
-            "onItemChanged", (id, property) => property == "tags");
-          newItem.checked = true;
-          await promiseNotification;
-          is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(),
-             "Scroll position did not change");
-        }
-
-        // Remove the second bookmark, then nuke some of the tags.
-        await PlacesUtils.bookmarks.remove(bm2);
+    // Check the tag.
+    promiseNotification = PlacesTestUtils.waitForNotification(
+      "onItemChanged", (id, property) => property == "tags");
+    newItem.checked = true;
+    await promiseNotification;
+    is(visibleIndex, tagsSelector.getIndexOfFirstVisibleRow(),
+       "Scroll position did not change");
+  }
 
-        // Doing this backwords tests more interesting paths.
-        for (let i = tags.length - 1; i >= 0 ; i -= 2) {
-          tagsSelector.selectedIndex = i;
-          let listItem = tagsSelector.selectedItem;
-          isnot(listItem, null, "Valid listItem found");
+  // Remove the second bookmark, then nuke some of the tags.
+  await PlacesUtils.bookmarks.remove(bm2);
 
-          tagsSelector.ensureElementIsVisible(listItem);
-          let firstVisibleTag = tags[tagsSelector.getIndexOfFirstVisibleRow()];
+  // Doing this backwords tests more interesting paths.
+  for (let i = tags.length - 1; i >= 0 ; i -= 2) {
+    tagsSelector.selectedIndex = i;
+    let listItem = tagsSelector.selectedItem;
+    isnot(listItem, null, "Valid listItem found");
 
-          ok(listItem.checked, "Item is checked " + i);
-          let selectedTag = listItem.label;
+    tagsSelector.ensureElementIsVisible(listItem);
+    let firstVisibleTag = tags[tagsSelector.getIndexOfFirstVisibleRow()];
+
+    ok(listItem.checked, "Item is checked " + i);
 
-          // Uncheck the tag.
-          let promiseNotification = PlacesTestUtils.waitForNotification(
-            "onItemChanged", (id, property) => property == "tags");
-          listItem.checked = false;
-          await promiseNotification;
+    // Uncheck the tag.
+    let promiseNotification = PlacesTestUtils.waitForNotification(
+      "onItemChanged", (id, property) => property == "tags");
+    listItem.checked = false;
+    await promiseNotification;
 
-          // Ensure the first visible tag is still visible in the list.
-          let firstVisibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
-          let lastVisibleIndex = firstVisibleIndex + tagsSelector.getNumberOfVisibleRows() -1;
-          let expectedTagIndex = tags.indexOf(firstVisibleTag);
-          ok(expectedTagIndex >= firstVisibleIndex &&
-             expectedTagIndex <= lastVisibleIndex,
-             "Scroll position is correct");
-
-          // The listbox is rebuilt, so we have to get the new element.
-          let newItem = tagsSelector.selectedItem;
-          isnot(newItem, null, "Valid new listItem found");
-          ok(newItem.checked, "New listItem is checked " + i);
-          is(tagsSelector.selectedItem.label,
-             tags[Math.min(i + 1, tags.length - 2)],
-             "The next tag is now selected");
-        }
+    // Ensure the first visible tag is still visible in the list.
+    let firstVisibleIndex = tagsSelector.getIndexOfFirstVisibleRow();
+    let lastVisibleIndex = firstVisibleIndex + tagsSelector.getNumberOfVisibleRows() - 1;
+    let expectedTagIndex = tags.indexOf(firstVisibleTag);
+    ok(expectedTagIndex >= firstVisibleIndex &&
+       expectedTagIndex <= lastVisibleIndex,
+       "Scroll position is correct");
 
-        // Cleanup.
-        await PlacesUtils.bookmarks.remove(bm1);
-      })().catch(ex => ok(false, "test failed: " + ex)).then(SimpleTest.finish);
-    }
+    // The listbox is rebuilt, so we have to get the new element.
+    let newItem = tagsSelector.selectedItem;
+    isnot(newItem, null, "Valid new listItem found");
+    ok(newItem.checked, "New listItem is checked " + i);
+    is(tagsSelector.selectedItem.label,
+       tags[Math.min(i + 1, tags.length - 2)],
+       "The next tag is now selected");
+  }
 
-    function openTagSelector() {
-      // Wait for the tags selector to be open.
-      let promise = new Promise(resolve => {
-        let row = document.getElementById("editBMPanel_tagsSelectorRow");
-        row.addEventListener("DOMAttrModified", function onAttrModified() {
-          row.removeEventListener("DOMAttrModified", onAttrModified);
-          resolve();
-        });
-      });
-      // Open the tags selector.
-      document.getElementById("editBMPanel_tagsSelectorExpander").doCommand();
-      return promise;
-    }
-  ]]>
-  </script>
+  let hiddenPromise = promisePopupHidden(bookmarkPanel);
+  let doneButton = document.getElementById("editBookmarkPanelDoneButton");
+  doneButton.click();
+  await hiddenPromise;
+  // Cleanup.
+  await PlacesUtils.bookmarks.remove(bm1);
+});
 
-</window>
+function openTagSelector() {
+  // Wait for the tags selector to be open.
+  let promise = new Promise(resolve => {
+    let row = document.getElementById("editBMPanel_tagsSelectorRow");
+    row.addEventListener("DOMAttrModified", function onAttrModified() {
+      resolve();
+    }, {once: true});
+  });
+  // Open the tags selector.
+  document.getElementById("editBMPanel_tagsSelectorExpander").doCommand();
+  return promise;
+}
rename from browser/components/places/tests/chrome/test_editBookmarkOverlay_keywords.xul
rename to browser/components/places/tests/browser/browser_editBookmark_keywords.js
--- a/browser/components/places/tests/chrome/test_editBookmarkOverlay_keywords.xul
+++ b/browser/components/places/tests/browser/browser_editBookmark_keywords.js
@@ -1,100 +1,74 @@
-<?xml version="1.0"?>
+"use strict";
 
-<!-- Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/ -->
-
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
-                 type="text/css"?>
+const TEST_URL = "about:blank";
 
-<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
-<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
-<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
-
-<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
-
-<!DOCTYPE window [
-  <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
-  %editBookmarkOverlayDTD;
-]>
+add_task(async function() {
+  function promiseOnItemChanged() {
+    return new Promise(resolve => {
+      PlacesUtils.bookmarks.addObserver({
+        onBeginUpdateBatch() {},
+        onEndUpdateBatch() {},
+        onItemAdded() {},
+        onItemRemoved() {},
+        onItemVisited() {},
+        onItemMoved() {},
+        onItemChanged(id, property, isAnno, value) {
+          PlacesUtils.bookmarks.removeObserver(this);
+          resolve({ property, value });
+        },
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
+      });
+    });
+  }
 
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="Bug 1343256 - Bookmark keywords disappear from one bookmark when adding a keyword to another bookmark"
-        onload="runTest();">
+  let tab = await BrowserTestUtils.openNewForegroundTab({
+    gBrowser,
+    opening: TEST_URL,
+    waitForStateStop: true
+  });
 
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
-  <script type="application/javascript"
-          src="chrome://browser/content/places/editBookmarkOverlay.js"/>
-  <script type="application/javascript" src="head.js" />
+  let library = await promiseLibrary("UnfiledBookmarks");
+  registerCleanupFunction(async () => {
+    await promiseLibraryClosed(library);
+    await PlacesUtils.bookmarks.eraseEverything();
+    await BrowserTestUtils.removeTab(tab);
+  });
 
+  let keywordField = library.document.getElementById("editBMPanel_keywordField");
 
-  <body xmlns="http://www.w3.org/1999/xhtml" />
-
-  <vbox id="editBookmarkPanelContent"/>
+  for (let i = 0; i < 2; ++i) {
+    let bm = await PlacesUtils.bookmarks.insert({
+      url: `http://www.test${i}.me/`,
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    });
 
-  <script type="application/javascript">
-  <![CDATA[
-    function runTest() {
-      SimpleTest.waitForExplicitFinish();
-      (test.bind(this))()
-          .catch(ex => ok(false, ex))
-          .then(() => PlacesUtils.bookmarks.eraseEverything())
-          .then(SimpleTest.finish);
+    let node = library.ContentTree.view.view.nodeForTreeIndex(i);
+    is(node.bookmarkGuid, bm.guid, "Found the expected bookmark");
+    // Select the bookmark.
+    library.ContentTree.view.selectNode(node);
+    synthesizeClickOnSelectedTreeCell(library.ContentTree.view);
+
+    // Expand the additional info for the first bookmark.
+    if (i === 0) {
+      library.document.getElementById("infoBoxExpander").click();
     }
 
-    function promiseOnItemChanged() {
-      return new Promise(resolve => {
-        PlacesUtils.bookmarks.addObserver({
-          onBeginUpdateBatch() {},
-          onEndUpdateBatch() {},
-          onItemAdded() {},
-          onItemRemoved() {},
-          onItemVisited() {},
-          onItemMoved() {},
-          onItemChanged(id, property, isAnno, value) {
-            PlacesUtils.bookmarks.removeObserver(this);
-            resolve({ property, value });
-          },
-          QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver])
-        });
-      });
-    }
-
-    async function test() {
-      ok(gEditItemOverlay, "Sanity check: gEditItemOverlay is in context");
-      let keywordField = document.getElementById("editBMPanel_keywordField");
+    is(library.document.getElementById("editBMPanel_keywordField").value, "",
+      "The keyword field should be empty");
+    info("Add a keyword to the bookmark");
+    let promise = promiseOnItemChanged();
+    keywordField.focus();
+    keywordField.value = "kw";
+    EventUtils.sendString(i.toString(), library);
+    keywordField.blur();
+    let {property, value} = await promise;
+    is(property, "keyword", "The keyword should have been changed");
 
-      for (let i = 0; i < 2; ++i) {
-        let bm = await PlacesUtils.bookmarks.insert({
-          url: `http://www.test${i}.me/`,
-          parentGuid: PlacesUtils.bookmarks.unfiledGuid,
-        });
-        info(`Init panel on bookmark #${i+1}`);
-        let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
-        gEditItemOverlay.initPanel({ node });
-        is(document.getElementById("editBMPanel_keywordField").value, "",
-          "The keyword field should be empty");
-        info("Add a keyword to the bookmark");
-        let promise = promiseOnItemChanged();
-        keywordField.focus();
-        keywordField.value = "kw";
-        sendString(i.toString());
-        synthesizeKey("KEY_Enter");
-        keywordField.blur();
-        let {property, value} = await promise;
-        is(property, "keyword", "The keyword should have been changed");
-        is(value, `kw${i}`, "The new keyword value is correct");
-      }
+    is(value, `kw${i}`, "The new keyword value is correct");
+  }
 
-      for (let i = 0; i < 2; ++i) {
-        let entry = await PlacesUtils.keywords.fetch({ url: `http://www.test${i}.me/` });
-        is(entry.keyword, `kw${i}`, `The keyword for http://www.test${i}.me/ is correct`);
-      }
-    };
-  ]]>
-  </script>
-
-</window>
+  for (let i = 0; i < 2; ++i) {
+    let entry = await PlacesUtils.keywords.fetch({ url: `http://www.test${i}.me/` });
+    is(entry.keyword, `kw${i}`, `The keyword for http://www.test${i}.me/ is correct`);
+  }
+});
rename from browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
rename to browser/components/places/tests/browser/browser_editBookmark_tags_liveUpdate.js
--- a/browser/components/places/tests/chrome/test_editBookmarkOverlay_tags_liveUpdate.xul
+++ b/browser/components/places/tests/browser/browser_editBookmark_tags_liveUpdate.js
@@ -1,216 +1,174 @@
-<?xml version="1.0"?>
-
-<!-- Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/ -->
-
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
-                 type="text/css"?>
+"use strict";
 
-<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
-<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
-<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
-
-<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
-
-<!DOCTYPE window [
-  <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
-  %editBookmarkOverlayDTD;
-]>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="485100: Exchanging a letter of a tag name with its big/small equivalent removes tag from bookmark"
-        onload="runTest();">
+function checkTagsSelector(aAvailableTags, aCheckedTags) {
+  is(PlacesUtils.tagging.allTags.length, aAvailableTags.length,
+     "tagging service is in sync.");
+  let tagsSelector = document.getElementById("editBMPanel_tagsSelector");
+  let children = tagsSelector.childNodes;
+  is(children.length, aAvailableTags.length,
+      "Found expected number of tags in the tags selector");
 
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
-  <script type="application/javascript"
-          src="chrome://browser/content/places/editBookmarkOverlay.js"/>
-  <script type="application/javascript" src="head.js" />
-
-  <body xmlns="http://www.w3.org/1999/xhtml" />
-
-  <vbox id="editBookmarkPanelContent"/>
+  Array.prototype.forEach.call(children, function(aChild) {
+    let tag = aChild.getAttribute("label");
+    ok(true, "Found tag '" + tag + "' in the selector");
+    ok(aAvailableTags.includes(tag), "Found expected tag");
+    let checked = aChild.getAttribute("checked") == "true";
+    is(checked, aCheckedTags.includes(tag),
+       "Tag is correctly marked");
+  });
+}
 
-  <script type="application/javascript">
-  <![CDATA[
-    function checkTagsSelector(aAvailableTags, aCheckedTags) {
-      is(PlacesUtils.tagging.allTags.length, aAvailableTags.length,
-         "tagging service is in sync.");
-      let tagsSelector = document.getElementById("editBMPanel_tagsSelector");
-      let children = tagsSelector.childNodes;
-      is(children.length, aAvailableTags.length,
-          "Found expected number of tags in the tags selector");
+add_task(async function() {
+  const TEST_URI = Services.io.newURI("http://www.test.me/");
+  const TEST_URI2 = Services.io.newURI("http://www.test.again.me/");
+  const TEST_TAG = "test-tag";
+
+  ok(gEditItemOverlay, "Sanity check: gEditItemOverlay is in context");
 
-      Array.prototype.forEach.call(children, function (aChild) {
-        let tag = aChild.getAttribute("label");
-        ok(true, "Found tag '" + tag + "' in the selector");
-        ok(aAvailableTags.includes(tag), "Found expected tag");
-        let checked = aChild.getAttribute("checked") == "true";
-        is(checked, aCheckedTags.includes(tag),
-           "Tag is correctly marked");
-      });
-    }
+  // Open the tags selector.
+  document.getElementById("editBMPanel_tagsSelectorRow").collapsed = false;
+
+  // Add a bookmark.
+  let bm = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+    type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+    url: TEST_URI.spec,
+    title: "test.me"
+  });
 
-    function runTest() {
-      SimpleTest.waitForExplicitFinish();
+  // Init panel.
+  let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
+  gEditItemOverlay.initPanel({ node });
 
-      (async function() {
-        const TEST_URI = Services.io.newURI("http://www.test.me/");
-        const TEST_URI2 = Services.io.newURI("http://www.test.again.me/");
-        const TEST_TAG = "test-tag";
-
-        ok(gEditItemOverlay, "Sanity check: gEditItemOverlay is in context");
-
-        // Open the tags selector.
-        document.getElementById("editBMPanel_tagsSelectorRow").collapsed = false;
+  // Add a tag.
+  PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
 
-        // Add a bookmark.
-        let bm = await PlacesUtils.bookmarks.insert({
-          parentGuid: PlacesUtils.bookmarks.unfiledGuid,
-          index: PlacesUtils.bookmarks.DEFAULT_INDEX,
-          type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
-          url: TEST_URI.spec,
-          title: "test.me"
-        });
-
-        // Init panel.
-        let node = await PlacesUIUtils.promiseNodeLikeFromFetchInfo(bm);
-        gEditItemOverlay.initPanel({ node });
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
+     "Correctly added tag to a single bookmark");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+    "Editing a single bookmark shows the added tag.");
+  checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
-        // Add a tag.
-        PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
-
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
-           "Correctly added tag to a single bookmark");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
-          "Editing a single bookmark shows the added tag.");
-        checkTagsSelector([TEST_TAG], [TEST_TAG]);
+  // Remove tag.
+  PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
+     "The tag has been removed");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == "",
+    "Editing a single bookmark should not show any tag");
+  checkTagsSelector([], []);
 
-        // Remove tag.
-        PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
-           "The tag has been removed");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == "",
-          "Editing a single bookmark should not show any tag");
-        checkTagsSelector([], []);
+  // Add a second bookmark.
+  let bm2 = await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+    type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+    title: "test.again.me",
+    url: TEST_URI2.spec
+  });
 
-        // Add a second bookmark.
-        let bm2 = await PlacesUtils.bookmarks.insert({
-          parentGuid: PlacesUtils.bookmarks.unfiledGuid,
-          index: PlacesUtils.bookmarks.DEFAULT_INDEX,
-          type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
-          title: "test.again.me",
-          url: TEST_URI2.spec
-        });
+  // Init panel with multiple uris.
+  gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
+
+  // Add a tag to the first uri.
+  PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
+     "Correctly added a tag to the first bookmark.");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == "",
+    "Editing multiple bookmarks without matching tags should not show any tag.");
+  checkTagsSelector([TEST_TAG], []);
 
-        // Init panel with multiple uris.
-        gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
+  // Add a tag to the second uri.
+  PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
+     "Correctly added a tag to the second bookmark.");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+    "Editing multiple bookmarks should show matching tags.");
+  checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
-        // Add a tag to the first uri.
-        PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
-           "Correctly added a tag to the first bookmark.");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == "",
-          "Editing multiple bookmarks without matching tags should not show any tag.");
-        checkTagsSelector([TEST_TAG], []);
-
-        // Add a tag to the second uri.
-        PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
-           "Correctly added a tag to the second bookmark.");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
-          "Editing multiple bookmarks should show matching tags.");
-        checkTagsSelector([TEST_TAG], [TEST_TAG]);
+  // Remove tag from the first bookmark.
+  PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
+     "Correctly removed tag from the first bookmark.");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == "",
+    "Editing multiple bookmarks without matching tags should not show any tag.");
+  checkTagsSelector([TEST_TAG], []);
 
-        // Remove tag from the first bookmark.
-        PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
-           "Correctly removed tag from the first bookmark.");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == "",
-          "Editing multiple bookmarks without matching tags should not show any tag.");
-        checkTagsSelector([TEST_TAG], []);
+  // Remove tag from the second bookmark.
+  PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
+     "Correctly removed tag from the second bookmark.");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == "",
+    "Editing multiple bookmarks without matching tags should not show any tag.");
+  checkTagsSelector([], []);
 
-        // Remove tag from the second bookmark.
-        PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
-           "Correctly removed tag from the second bookmark.");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == "",
-          "Editing multiple bookmarks without matching tags should not show any tag.");
-        checkTagsSelector([], []);
+  // Init panel with a nsIURI entry.
+  gEditItemOverlay.initPanel({ uris: [TEST_URI] });
 
-        // Init panel with a nsIURI entry.
-        gEditItemOverlay.initPanel({ uris: [TEST_URI] });
-
-        // Add a tag.
-        PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
-           "Correctly added tag to the first entry.");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
-          "Editing a single nsIURI entry shows the added tag.");
-        checkTagsSelector([TEST_TAG], [TEST_TAG]);
+  // Add a tag.
+  PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
+     "Correctly added tag to the first entry.");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+    "Editing a single nsIURI entry shows the added tag.");
+  checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
-        // Remove tag.
-        PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
-           "Correctly removed tag from the nsIURI entry.");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == "",
-          "Editing a single nsIURI entry should not show any tag.");
-        checkTagsSelector([], []);
+  // Remove tag.
+  PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
+     "Correctly removed tag from the nsIURI entry.");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == "",
+    "Editing a single nsIURI entry should not show any tag.");
+  checkTagsSelector([], []);
 
-        // Init panel with multiple nsIURI entries.
-        gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
+  // Init panel with multiple nsIURI entries.
+  gEditItemOverlay.initPanel({ uris: [TEST_URI, TEST_URI2] });
 
-        // Add a tag to the first entry.
-        PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
-           "Tag correctly added.");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == "",
-          "Editing multiple nsIURIs without matching tags should not show any tag.");
-        checkTagsSelector([TEST_TAG], []);
+  // Add a tag to the first entry.
+  PlacesUtils.tagging.tagURI(TEST_URI, [TEST_TAG]);
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], TEST_TAG,
+     "Tag correctly added.");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == "",
+    "Editing multiple nsIURIs without matching tags should not show any tag.");
+  checkTagsSelector([TEST_TAG], []);
 
-        // Add a tag to the second entry.
-        PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
-           "Tag correctly added.");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
-          "Editing multiple nsIURIs should show matching tags.");
-        checkTagsSelector([TEST_TAG], [TEST_TAG]);
-
-        // Remove tag from the first entry.
-        PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
-           "Correctly removed tag from the first entry.");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == "",
-          "Editing multiple nsIURIs without matching tags should not show any tag.");
-        checkTagsSelector([TEST_TAG], []);
+  // Add a tag to the second entry.
+  PlacesUtils.tagging.tagURI(TEST_URI2, [TEST_TAG]);
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], TEST_TAG,
+     "Tag correctly added.");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == TEST_TAG,
+    "Editing multiple nsIURIs should show matching tags.");
+  checkTagsSelector([TEST_TAG], [TEST_TAG]);
 
-        // Remove tag from the second entry.
-        PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
-        is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
-           "Correctly removed tag from the second entry.");
-        await BrowserTestUtils.waitForCondition(
-          () => document.getElementById("editBMPanel_tagsField").value == "",
-          "Editing multiple nsIURIs without matching tags should not show any tag.");
-        checkTagsSelector([], []);
+  // Remove tag from the first entry.
+  PlacesUtils.tagging.untagURI(TEST_URI, [TEST_TAG]);
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI)[0], undefined,
+     "Correctly removed tag from the first entry.");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == "",
+    "Editing multiple nsIURIs without matching tags should not show any tag.");
+  checkTagsSelector([TEST_TAG], []);
 
-        // Cleanup.
-        await PlacesUtils.bookmarks.remove(bm.guid);
-        await PlacesUtils.bookmarks.remove(bm2.guid);
-      })().then(SimpleTest.finish);
-    }
-  ]]>
-  </script>
+  // Remove tag from the second entry.
+  PlacesUtils.tagging.untagURI(TEST_URI2, [TEST_TAG]);
+  is(PlacesUtils.tagging.getTagsForURI(TEST_URI2)[0], undefined,
+     "Correctly removed tag from the second entry.");
+  await BrowserTestUtils.waitForCondition(
+    () => document.getElementById("editBMPanel_tagsField").value == "",
+    "Editing multiple nsIURIs without matching tags should not show any tag.");
+  checkTagsSelector([], []);
 
-</window>
+  // Cleanup.
+  await PlacesUtils.bookmarks.remove(bm.guid);
+  await PlacesUtils.bookmarks.remove(bm2.guid);
+});
rename from browser/components/places/tests/browser/browser_library_batch_delete.js
rename to browser/components/places/tests/browser/browser_library_delete.js
--- a/browser/components/places/tests/browser/browser_library_batch_delete.js
+++ b/browser/components/places/tests/browser/browser_library_delete.js
@@ -1,91 +1,92 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- *  Tests that Library handles correctly batch deletes.
+ *  Tests that Library correctly handles deletes.
  */
 
 const TEST_URL = "http://www.batch.delete.me/";
 
 var gLibrary;
 
 add_task(async function test_setup() {
   gLibrary = await promiseLibrary();
 
   registerCleanupFunction(async () => {
     await PlacesUtils.bookmarks.eraseEverything();
     // Close Library window.
     gLibrary.close();
   });
 });
 
-add_task(async function test_create_and_batch_remove_bookmarks() {
-  let testURI = makeURI(TEST_URL);
-  PlacesUtils.history.runInBatchMode({
-    runBatched(aUserData) {
-      // Create a folder in unserted and populate it with bookmarks.
-      let folder = PlacesUtils.bookmarks.createFolder(
-        PlacesUtils.unfiledBookmarksFolderId, "deleteme",
-        PlacesUtils.bookmarks.DEFAULT_INDEX
-      );
-      PlacesUtils.bookmarks.createFolder(
-        PlacesUtils.unfiledBookmarksFolderId, "keepme",
-        PlacesUtils.bookmarks.DEFAULT_INDEX
-      );
-      for (let i = 0; i < 10; i++) {
-        PlacesUtils.bookmarks.insertBookmark(folder,
-                                             testURI,
-                                             PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                             "bm" + i);
-      }
-    }
-  }, null);
+add_task(async function test_create_and_remove_bookmarks() {
+  let bmChildren = [];
+  for (let i = 0; i < 10; i++) {
+    bmChildren.push({
+      title: `bm${i}`,
+      url: TEST_URL,
+    });
+  }
+
+  await PlacesUtils.bookmarks.insertTree({
+    guid: PlacesUtils.bookmarks.unfiledGuid,
+    children: [{
+      title: "deleteme",
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+      children: bmChildren
+    }, {
+      title: "keepme",
+      type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    }],
+  });
 
   // Select and open the left pane "History" query.
   let PO = gLibrary.PlacesOrganizer;
   PO.selectLeftPaneBuiltIn("UnfiledBookmarks");
   Assert.notEqual(PO._places.selectedNode, null, "Selected unsorted bookmarks");
 
   let unsortedNode = PlacesUtils.asContainer(PO._places.selectedNode);
-  unsortedNode.containerOpen = true;
   Assert.equal(unsortedNode.childCount, 2, "Unsorted node has 2 children");
   let folderNode = unsortedNode.getChild(0);
   Assert.equal(folderNode.title, "deleteme", "Folder found in unsorted bookmarks");
+
   // Check delete command is available.
   PO._places.selectNode(folderNode);
   Assert.equal(PO._places.selectedNode.title, "deleteme", "Folder node selected");
   Assert.ok(PO._places.controller.isCommandEnabled("cmd_delete"),
      "Delete command is enabled");
   let promiseItemRemovedNotification = PlacesTestUtils.waitForNotification(
     "onItemRemoved", (itemId, parentId, index, type, uri, guid) => guid == folderNode.bookmarkGuid);
+
   // Execute the delete command and check bookmark has been removed.
   PO._places.controller.doCommand("cmd_delete");
 
   await promiseItemRemovedNotification;
 
-  Assert.ok(!(await PlacesUtils.bookmarks.fetch({url: testURI})),
+  Assert.ok(!(await PlacesUtils.bookmarks.fetch({url: TEST_URL})),
     "Bookmark has been correctly removed");
   // Test live update.
   Assert.equal(unsortedNode.childCount, 1, "Unsorted node has 1 child");
   Assert.equal(PO._places.selectedNode.title, "keepme", "Folder node selected");
-  unsortedNode.containerOpen = false;
 });
 
 add_task(async function test_ensure_correct_selection_and_functionality() {
   let PO = gLibrary.PlacesOrganizer;
   let ContentTree = gLibrary.ContentTree;
   // Move selection forth and back.
   PO.selectLeftPaneBuiltIn("History");
   PO.selectLeftPaneBuiltIn("UnfiledBookmarks");
   // Now select the "keepme" folder in the right pane and delete it.
   ContentTree.view.selectNode(ContentTree.view.result.root.getChild(0));
   Assert.equal(ContentTree.view.selectedNode.title, "keepme",
     "Found folder in content pane");
-  // Test live update.
-  PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
-                                       makeURI(TEST_URL),
-                                       PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                       "bm");
+
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    title: "bm",
+    url: TEST_URL,
+  });
+
   Assert.equal(ContentTree.view.result.root.childCount, 2,
     "Right pane was correctly updated");
 });
--- a/browser/components/places/tests/chrome/chrome.ini
+++ b/browser/components/places/tests/chrome/chrome.ini
@@ -1,14 +1,9 @@
 [DEFAULT]
 support-files = head.js
 
 [test_0_bug510634.xul]
 [test_bug1163447_selectItems_through_shortcut.xul]
-[test_bug427633_no_newfolder_if_noip.xul]
-[test_bug485100-change-case-loses-tag.xul]
 [test_bug549192.xul]
 [test_bug549491.xul]
-[test_bug631374_tags_selector_scroll.xul]
-[test_editBookmarkOverlay_keywords.xul]
-[test_editBookmarkOverlay_tags_liveUpdate.xul]
 [test_selectItems_on_nested_tree.xul]
-[test_treeview_date.xul]
+[test_treeview_date.xul]
\ No newline at end of file
--- a/browser/components/preferences/connection.js
+++ b/browser/components/preferences/connection.js
@@ -3,29 +3,32 @@
  * 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-globals-from ../../base/content/utilityOverlay.js */
 /* import-globals-from ../../../toolkit/content/preferencesBindings.js */
 /* import-globals-from in-content/extensionControlled.js */
 
 Preferences.addAll([
+  // Add network.proxy.autoconfig_url before network.proxy.type so they're
+  // both initialized when network.proxy.type initialization triggers a call to
+  // gConnectionsDialog.updateReloadButton().
+  { id: "network.proxy.autoconfig_url", type: "string" },
   { id: "network.proxy.type", type: "int" },
   { id: "network.proxy.http", type: "string" },
   { id: "network.proxy.http_port", type: "int" },
   { id: "network.proxy.ftp", type: "string" },
   { id: "network.proxy.ftp_port", type: "int" },
   { id: "network.proxy.ssl", type: "string" },
   { id: "network.proxy.ssl_port", type: "int" },
   { id: "network.proxy.socks", type: "string" },
   { id: "network.proxy.socks_port", type: "int" },
   { id: "network.proxy.socks_version", type: "int" },
   { id: "network.proxy.socks_remote_dns", type: "bool" },
   { id: "network.proxy.no_proxies_on", type: "string" },
-  { id: "network.proxy.autoconfig_url", type: "string" },
   { id: "network.proxy.share_proxy_settings", type: "bool" },
   { id: "signon.autologin.proxy", type: "bool" },
   { id: "pref.advanced.proxies.disable_button.reload", type: "bool" },
   { id: "network.proxy.backup.ftp", type: "string" },
   { id: "network.proxy.backup.ftp_port", type: "int" },
   { id: "network.proxy.backup.ssl", type: "string" },
   { id: "network.proxy.backup.ssl_port", type: "int" },
   { id: "network.proxy.backup.socks", type: "string" },
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -37,16 +37,17 @@ support-files =
   browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul
 [browser_change_app_handler.js]
 skip-if = os != "win" || (os == "win" && os_version == "6.1")
 # This test tests the windows-specific app selection dialog, so can't run on non-Windows.
 # Skip the test on Window 7, see the detail at Bug 1381706.
 [browser_checkspelling.js]
 [browser_connection.js]
 [browser_connection_bug388287.js]
+[browser_connection_bug1445991.js]
 [browser_cookies_exceptions.js]
 [browser_defaultbrowser_alwayscheck.js]
 [browser_healthreport.js]
 skip-if = true || !healthreport # Bug 1185403 for the "true"
 [browser_homepages_filter_aboutpreferences.js]
 [browser_extension_controlled.js]
 [browser_languages_subdialog.js]
 [browser_layersacceleration.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content/tests/browser_connection_bug1445991.js
@@ -0,0 +1,29 @@
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the disabled status of the autoconfig Reload button when the proxy type
+// is autoconfig (network.proxy.type == 2).
+add_task(async function testAutoconfigReloadButton() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["network.proxy.type", 2],
+      ["network.proxy.autoconfig_url", "file:///nonexistent.pac"],
+    ],
+  });
+
+  await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true });
+  const connectionURL = "chrome://browser/content/preferences/connection.xul";
+  const promiseDialogLoaded = promiseLoadSubDialog(connectionURL);
+  // eslint-disable-next-line mozilla/no-cpows-in-tests
+  gBrowser.contentDocument.getElementById("connectionSettings").click();
+  const dialog = await promiseDialogLoaded;
+
+  ok(!dialog.document.getElementById("autoReload").disabled,
+     "Reload button is enabled when proxy type is autoconfig");
+
+  dialog.close();
+  gBrowser.removeCurrentTab();
+});
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -823,37 +823,67 @@ this.FormAutofillUtils = {
     return {
       "addressLevel1Label": dataset.state_name_type || "province",
       "postalCodeLabel": dataset.zip_name_type || "postalCode",
       "fieldsOrder": this.parseAddressFormat(dataset.fmt || "%N%n%O%n%A%n%C, %S %Z"),
     };
   },
 
   /**
-   * Localize elements with "data-localization" attribute
-   * @param   {string} bundleURI
-   * @param   {DOMElement} root
+   * Localize "data-localization" or "data-localization-region" attributes.
+   * @param {Element} element
+   * @param {string} attributeName
    */
-  localizeMarkup(bundleURI, root) {
-    const bundle = Services.strings.createBundle(bundleURI);
+  localizeAttributeForElement(element, attributeName) {
+    let bundle = null;
+    switch (attributeName) {
+      case "data-localization": {
+        bundle = this.stringBundle;
+        break;
+      }
+      case "data-localization-region": {
+        bundle = this.regionsBundle;
+        break;
+      }
+      default:
+        throw new Error("Unexpected attributeName");
+    }
+
+    element.textContent = bundle.GetStringFromName(element.getAttribute(attributeName));
+    element.removeAttribute(attributeName);
+  },
+
+  /**
+   * Localize elements with "data-localization" or "data-localization-region" attributes.
+   * @param {Element} root
+   */
+  localizeMarkup(root) {
     let elements = root.querySelectorAll("[data-localization]");
     for (let element of elements) {
-      element.textContent = bundle.GetStringFromName(element.getAttribute("data-localization"));
-      element.removeAttribute("data-localization");
+      this.localizeAttributeForElement(element, "data-localization");
+    }
+
+    elements = root.querySelectorAll("[data-localization-region]");
+    for (let element of elements) {
+      this.localizeAttributeForElement(element, "data-localization-region");
     }
   },
 };
 
 this.log = null;
 this.FormAutofillUtils.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]);
 
 XPCOMUtils.defineLazyGetter(FormAutofillUtils, "stringBundle", function() {
   return Services.strings.createBundle("chrome://formautofill/locale/formautofill.properties");
 });
 
+XPCOMUtils.defineLazyGetter(FormAutofillUtils, "regionsBundle", function() {
+  return Services.strings.createBundle("chrome://global/locale/regionNames.properties");
+});
+
 XPCOMUtils.defineLazyGetter(FormAutofillUtils, "brandBundle", function() {
   return Services.strings.createBundle("chrome://branding/locale/brand.properties");
 });
 
 XPCOMUtils.defineLazyPreferenceGetter(this.FormAutofillUtils,
                                       "DEFAULT_REGION", DEFAULT_REGION_PREF, "US");
 XPCOMUtils.defineLazyPreferenceGetter(this.FormAutofillUtils,
                                       "isAutofillAddressesEnabled", ENABLED_AUTOFILL_ADDRESSES_PREF);
copy from browser/extensions/formautofill/content/editDialog.js
copy to browser/extensions/formautofill/content/autofillEditForms.js
--- a/browser/extensions/formautofill/content/editDialog.js
+++ b/browser/extensions/formautofill/content/autofillEditForms.js
@@ -1,51 +1,21 @@
 /* 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/. */
 
 /* exported EditAddress, EditCreditCard */
+/* eslint-disable mozilla/balanced-listeners */ // Not relevant since the document gets unloaded.
 
 "use strict";
 
-const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
-const REGIONS_BUNDLE_URI = "chrome://global/locale/regionNames.properties";
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
-
-ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
-                               "resource://formautofill/FormAutofillStorage.jsm");
-ChromeUtils.defineModuleGetter(this, "MasterPassword",
-                               "resource://formautofill/MasterPassword.jsm");
-
-class EditDialog {
-  constructor(subStorageName, elements, record) {
-    this._storageInitPromise = formAutofillStorage.initialize();
-    this._subStorageName = subStorageName;
+class EditAutofillForm {
+  constructor(elements, record) {
     this._elements = elements;
     this._record = record;
-    this.localizeDocument();
-    window.addEventListener("DOMContentLoaded", this, {once: true});
-  }
-
-  async init() {
-    if (this._record) {
-      await this.loadInitialValues(this._record);
-    }
-    this.attachEventListeners();
-    // For testing only: loadInitialValues for credit card is an async method, and tests
-    // need to wait until the values have been filled before editing the fields.
-    window.dispatchEvent(new CustomEvent("FormReady"));
-  }
-
-  uninit() {
-    this.detachEventListeners();
-    this._elements = null;
   }
 
   /**
    * Fill the form with a record object.
    * @param  {object} record
    */
   loadInitialValues(record) {
     for (let field in record) {
@@ -56,177 +26,98 @@ class EditDialog {
     }
   }
 
   /**
    * Get inputs from the form.
    * @returns {object}
    */
   buildFormObject() {
-    return Array.from(document.forms[0].elements).reduce((obj, input) => {
+    return Array.from(this._elements.form.elements).reduce((obj, input) => {
       if (input.value) {
         obj[input.id] = input.value;
       }
       return obj;
     }, {});
   }
 
   /**
-   * Get storage and ensure it has been initialized.
-   * @returns {object}
-   */
-  async getStorage() {
-    await this._storageInitPromise;
-    return formAutofillStorage[this._subStorageName];
-  }
-
-  /**
-   * Asks FormAutofillParent to save or update an record.
-   * @param  {object} record
-   * @param  {string} guid [optional]
-   */
-  async saveRecord(record, guid) {
-    let storage = await this.getStorage();
-    if (guid) {
-      storage.update(guid, record);
-    } else {
-      storage.add(record);
-    }
-  }
-
-  /**
    * Handle events
    *
    * @param  {DOMEvent} event
    */
   handleEvent(event) {
     switch (event.type) {
-      case "DOMContentLoaded": {
-        this.init();
-        break;
-      }
-      case "unload": {
-        this.uninit();
-        break;
-      }
-      case "click": {
-        this.handleClick(event);
-        break;
-      }
       case "input": {
         this.handleInput(event);
         break;
       }
-      case "keypress": {
-        this.handleKeyPress(event);
-        break;
-      }
       case "change": {
         this.handleChange(event);
         break;
       }
-      case "contextmenu": {
-        if (!(event.target instanceof HTMLInputElement) &&
-            !(event.target instanceof HTMLTextAreaElement)) {
-          event.preventDefault();
-        }
-        break;
-      }
-    }
-  }
-
-  /**
-   * Handle click events
-   *
-   * @param  {DOMEvent} event
-   */
-  handleClick(event) {
-    if (event.target == this._elements.cancel) {
-      window.close();
-    }
-    if (event.target == this._elements.save) {
-      this.handleSubmit();
     }
   }
 
   /**
    * Handle input events
    *
    * @param  {DOMEvent} event
    */
-  handleInput(event) {
-    // Toggle disabled attribute on the save button based on
-    // whether the form is filled or empty.
-    if (Object.keys(this.buildFormObject()).length == 0) {
-      this._elements.save.setAttribute("disabled", true);
-    } else {
-      this._elements.save.removeAttribute("disabled");
-    }
-  }
-
-  /**
-   * Handle key press events
-   *
-   * @param  {DOMEvent} event
-   */
-  handleKeyPress(event) {
-    if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
-      window.close();
-    }
-  }
+  handleInput(event) {}
 
   /**
    * Attach event listener
    */
   attachEventListeners() {
-    window.addEventListener("keypress", this);
-    window.addEventListener("contextmenu", this);
-    this._elements.controlsContainer.addEventListener("click", this);
-    document.addEventListener("input", this);
+    this._elements.form.addEventListener("input", this);
   }
 
-  /**
-   * Remove event listener
-   */
-  detachEventListeners() {
-    window.removeEventListener("keypress", this);
-    window.removeEventListener("contextmenu", this);
-    this._elements.controlsContainer.removeEventListener("click", this);
-    document.removeEventListener("input", this);
-  }
-
-  // An interface to be inherited.
-  localizeDocument() {}
-
-  // An interface to be inherited.
-  handleSubmit(event) {}
-
   // An interface to be inherited.
   handleChange(event) {}
 }
 
-class EditAddress extends EditDialog {
-  constructor(elements, record) {
+class EditAddress extends EditAutofillForm {
+  /**
+   * @param {HTMLElement[]} elements
+   * @param {object} record
+   * @param {object} config
+   * @param {string[]} config.DEFAULT_REGION
+   * @param {function} config.getFormFormat Function to return form layout info for a given country.
+   * @param {string[]} config.supportedCountries
+   */
+  constructor(elements, record, config) {
     let country = record ? record.country :
-                  FormAutofillUtils.supportedCountries.find(supported => supported == FormAutofillUtils.DEFAULT_REGION);
-    super("addresses", elements, record || {country});
+                    config.supportedCountries.find(supported => supported == config.DEFAULT_REGION);
+    super(elements, record || {country});
 
+    Object.assign(this, config);
+    Object.assign(this._elements, {
+      addressLevel1Label: this._elements.form.querySelector("#address-level1-container > span"),
+      postalCodeLabel: this._elements.form.querySelector("#postal-code-container > span"),
+      country: this._elements.form.querySelector("#country"),
+    });
+
+    this.populateCountries();
+    // Need to populate the countries before trying to set the initial country.
+    // Also need to use this._record so it has the default country selected.
+    this.loadInitialValues(this._record);
     this.formatForm(country);
+    this.attachEventListeners();
   }
 
   /**
    * Format the form based on country. The address-level1 and postal-code labels
    * should be specific to the given country.
    * @param  {string} country
    */
   formatForm(country) {
-    const {addressLevel1Label, postalCodeLabel, fieldsOrder} = FormAutofillUtils.getFormFormat(country);
+    const {addressLevel1Label, postalCodeLabel, fieldsOrder} = this.getFormFormat(country);
     this._elements.addressLevel1Label.dataset.localization = addressLevel1Label;
     this._elements.postalCodeLabel.dataset.localization = postalCodeLabel;
-    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
     this.arrangeFields(fieldsOrder);
   }
 
   arrangeFields(fieldsOrder) {
     let fields = [
       "name",
       "organization",
       "street-address",
@@ -251,55 +142,55 @@ class EditAddress extends EditDialog {
     }
     // Hide the remaining fields
     for (let field of fields) {
       let container = document.getElementById(`${field}-container`);
       container.style.display = "none";
     }
   }
 
-  localizeDocument() {
-    if (this._record) {
-      this._elements.title.dataset.localization = "editAddressTitle";
-    }
+  populateCountries() {
     let fragment = document.createDocumentFragment();
-    for (let country of FormAutofillUtils.supportedCountries) {
+    for (let country of this.supportedCountries) {
       let option = new Option();
       option.value = country;
-      option.dataset.localization = country.toLowerCase();
+      option.dataset.localizationRegion = country.toLowerCase();
       fragment.appendChild(option);
     }
     this._elements.country.appendChild(fragment);
-    FormAutofillUtils.localizeMarkup(REGIONS_BUNDLE_URI, this._elements.country);
-  }
-
-  async handleSubmit() {
-    await this.saveRecord(this.buildFormObject(), this._record ? this._record.guid : null);
-    window.close();
   }
 
   handleChange(event) {
     this.formatForm(event.target.value);
   }
 
   attachEventListeners() {
     this._elements.country.addEventListener("change", this);
     super.attachEventListeners();
   }
-
-  detachEventListeners() {
-    this._elements.country.removeEventListener("change", this);
-    super.detachEventListeners();
-  }
 }
 
-class EditCreditCard extends EditDialog {
-  constructor(elements, record) {
-    super("creditCards", elements, record);
+class EditCreditCard extends EditAutofillForm {
+  /**
+   * @param {HTMLElement[]} elements
+   * @param {object} record with a decrypted cc-number
+   * @param {object} config
+   * @param {function} config.isCCNumber Function to determine is a string is a valid CC number.
+   */
+  constructor(elements, record, config) {
+    super(elements, record);
+
+    Object.assign(this, config);
+    Object.assign(this._elements, {
+      ccNumber: this._elements.form.querySelector("#cc-number"),
+      year: this._elements.form.querySelector("#cc-exp-year"),
+    });
     this.generateYears();
+    this.loadInitialValues(this._record);
+    this.attachEventListeners();
   }
 
   generateYears() {
     const count = 11;
     const currentYear = new Date().getFullYear();
     const ccExpYear = this._record && this._record["cc-exp-year"];
 
     if (ccExpYear && ccExpYear < currentYear) {
@@ -312,49 +203,37 @@ class EditCreditCard extends EditDialog 
       this._elements.year.appendChild(option);
     }
 
     if (ccExpYear && ccExpYear > currentYear + count) {
       this._elements.year.appendChild(new Option(ccExpYear));
     }
   }
 
-  localizeDocument() {
-    if (this._record) {
-      this._elements.title.dataset.localization = "editCreditCardTitle";
-    }
-    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
+  attachEventListeners() {
+    this._elements.ccNumber.addEventListener("change", this);
+    super.attachEventListeners();
   }
 
-  /**
-   * Decrypt cc-number first and fill the form.
-   * @param  {object} creditCard
-   */
-  async loadInitialValues(creditCard) {
-    let decryptedCC = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
-    super.loadInitialValues(Object.assign({}, creditCard, {"cc-number": decryptedCC}));
-  }
+  handleChange(event) {
+    super.handleChange(event);
 
-  async handleSubmit() {
-    let creditCard = this.buildFormObject();
-    // Show error on the cc-number field if it's empty or invalid
-    if (!FormAutofillUtils.isCCNumber(creditCard["cc-number"])) {
-      this._elements.ccNumber.setCustomValidity(true);
+    if (event.target != this._elements.ccNumber) {
       return;
     }
 
-    // TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
-    // APIs are refactored to be async functions (bug 1399367).
-    if (await MasterPassword.ensureLoggedIn()) {
-      await this.saveRecord(creditCard, this._record ? this._record.guid : null);
+    let ccNumberField = this._elements.ccNumber;
+
+    // Mark the cc-number field as invalid if the number is empty or invalid.
+    if (!this.isCCNumber(ccNumberField.value)) {
+      ccNumberField.setCustomValidity(true);
     }
-    window.close();
   }
 
   handleInput(event) {
     // Clear the error message if cc-number is valid
     if (event.target == this._elements.ccNumber &&
-        FormAutofillUtils.isCCNumber(this._elements.ccNumber.value)) {
+        this.isCCNumber(this._elements.ccNumber.value)) {
       this._elements.ccNumber.setCustomValidity("");
     }
     super.handleInput(event);
   }
 }
--- a/browser/extensions/formautofill/content/editAddress.xhtml
+++ b/browser/extensions/formautofill/content/editAddress.xhtml
@@ -7,17 +7,19 @@
   %globalDTD;
 ]>
 <html xmlns="http://www.w3.org/1999/xhtml" width="620">
 <head>
   <title data-localization="addNewAddressTitle"/>
   <link rel="stylesheet" href="chrome://formautofill-shared/skin/editDialog.css"/>
   <link rel="stylesheet" href="chrome://formautofill-shared/skin/editAddress.css"/>
   <link rel="stylesheet" href="chrome://formautofill/skin/editDialog.css"/>
+  <script src="chrome://formautofill/content/l10n.js"></script>
   <script src="chrome://formautofill/content/editDialog.js"></script>
+  <script src="chrome://formautofill/content/autofillEditForms.js"></script>
 </head>
 <body dir="&locale.dir;">
   <form id="form" autocomplete="off">
     <div>
       <div id="name-container">
         <label id="given-name-container">
           <span data-localization="givenName"/>
           <input id="given-name" type="text"/>
@@ -69,22 +71,36 @@
     </label>
   </form>
   <div id="controls-container">
     <button id="cancel" data-localization="cancelBtnLabel"/>
     <button id="save" disabled="disabled" data-localization="saveBtnLabel"/>
   </div>
   <script type="application/javascript"><![CDATA[
     "use strict";
-    /* global EditAddress */
-    new EditAddress({
-      title: document.querySelector("title"),
+
+    let {
+      DEFAULT_REGION,
+      getFormFormat,
+      supportedCountries,
+    } = FormAutofillUtils;
+    let record = window.arguments && window.arguments[0];
+
+    /* import-globals-from autofillEditForms.js */
+    let fieldContainer = new EditAddress({
       form: document.getElementById("form"),
-      addressLevel1Label: document.querySelector("#address-level1-container > span"),
-      postalCodeLabel: document.querySelector("#postal-code-container > span"),
-      country: document.getElementById("country"),
+    }, record, {
+      DEFAULT_REGION,
+      getFormFormat: getFormFormat.bind(FormAutofillUtils),
+      supportedCountries,
+    });
+
+    /* import-globals-from editDialog.js */
+    new EditAddressDialog({
+      title: document.querySelector("title"),
+      fieldContainer,
       controlsContainer: document.getElementById("controls-container"),
       cancel: document.getElementById("cancel"),
       save: document.getElementById("save"),
-    }, window.arguments && window.arguments[0]);
+    }, record);
   ]]></script>
 </body>
 </html>
--- a/browser/extensions/formautofill/content/editCreditCard.xhtml
+++ b/browser/extensions/formautofill/content/editCreditCard.xhtml
@@ -7,17 +7,19 @@
   %globalDTD;
 ]>
 <html xmlns="http://www.w3.org/1999/xhtml" width="500" style="width: 500px">
 <head>
   <title data-localization="addNewCreditCardTitle"/>
   <link rel="stylesheet" href="chrome://formautofill-shared/skin/editDialog.css"/>
   <link rel="stylesheet" href="chrome://formautofill-shared/skin/editCreditCard.css"/>
   <link rel="stylesheet" href="chrome://formautofill/skin/editDialog.css"/>
+  <script src="chrome://formautofill/content/l10n.js"></script>
   <script src="chrome://formautofill/content/editDialog.js"></script>
+  <script src="chrome://formautofill/content/autofillEditForms.js"></script>
 </head>
 <body dir="&locale.dir;">
   <form id="form" autocomplete="off">
     <label>
       <span data-localization="cardNumber"/>
       <input id="cc-number" type="text"/>
     </label>
     <label>
@@ -47,21 +49,33 @@
     </div>
   </form>
   <div id="controls-container">
     <button id="cancel" data-localization="cancelBtnLabel"/>
     <button id="save" disabled="disabled" data-localization="saveBtnLabel"/>
   </div>
   <script type="application/javascript"><![CDATA[
     "use strict";
-    /* global EditCreditCard */
-    new EditCreditCard({
+
+    let {
+      isCCNumber,
+    } = FormAutofillUtils;
+    let record = window.arguments && window.arguments[0];
+
+    /* import-globals-from autofillEditForms.js */
+    let fieldContainer = new EditCreditCard({
+      form: document.getElementById("form"),
+    }, record,
+      {
+        isCCNumber: isCCNumber.bind(FormAutofillUtils),
+      });
+
+    /* import-globals-from editDialog.js */
+    new EditCreditCardDialog({
       title: document.querySelector("title"),
-      form: document.getElementById("form"),
-      ccNumber: document.getElementById("cc-number"),
-      year: document.getElementById("cc-exp-year"),
+      fieldContainer,
       controlsContainer: document.getElementById("controls-container"),
       cancel: document.getElementById("cancel"),
       save: document.getElementById("save"),
-    }, window.arguments && window.arguments[0]);
+    }, record);
   ]]></script>
 </body>
 </html>
--- a/browser/extensions/formautofill/content/editDialog.js
+++ b/browser/extensions/formautofill/content/editDialog.js
@@ -1,79 +1,42 @@
 /* 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/. */
 
-/* exported EditAddress, EditCreditCard */
+/* exported EditAddressDialog, EditCreditCardDialog */
+/* eslint-disable mozilla/balanced-listeners */ // Not relevant since the document gets unloaded.
 
 "use strict";
 
-const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
-const REGIONS_BUNDLE_URI = "chrome://global/locale/regionNames.properties";
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
                                "resource://formautofill/FormAutofillStorage.jsm");
 ChromeUtils.defineModuleGetter(this, "MasterPassword",
                                "resource://formautofill/MasterPassword.jsm");
 
-class EditDialog {
+class AutofillEditDialog {
   constructor(subStorageName, elements, record) {
     this._storageInitPromise = formAutofillStorage.initialize();
     this._subStorageName = subStorageName;
     this._elements = elements;
     this._record = record;
     this.localizeDocument();
     window.addEventListener("DOMContentLoaded", this, {once: true});
   }
 
   async init() {
-    if (this._record) {
-      await this.loadInitialValues(this._record);
-    }
     this.attachEventListeners();
-    // For testing only: loadInitialValues for credit card is an async method, and tests
-    // need to wait until the values have been filled before editing the fields.
+    // For testing only: signal to tests that the dialog is ready for testing.
+    // This is likely no longer needed since retrieving from storage is fully
+    // handled in manageDialog.js now.
     window.dispatchEvent(new CustomEvent("FormReady"));
   }
 
-  uninit() {
-    this.detachEventListeners();
-    this._elements = null;
-  }
-
-  /**
-   * Fill the form with a record object.
-   * @param  {object} record
-   */
-  loadInitialValues(record) {
-    for (let field in record) {
-      let input = document.getElementById(field);
-      if (input) {
-        input.value = record[field];
-      }
-    }
-  }
-
-  /**
-   * Get inputs from the form.
-   * @returns {object}
-   */
-  buildFormObject() {
-    return Array.from(document.forms[0].elements).reduce((obj, input) => {
-      if (input.value) {
-        obj[input.id] = input.value;
-      }
-      return obj;
-    }, {});
-  }
-
   /**
    * Get storage and ensure it has been initialized.
    * @returns {object}
    */
   async getStorage() {
     await this._storageInitPromise;
     return formAutofillStorage[this._subStorageName];
   }
@@ -98,36 +61,28 @@ class EditDialog {
    * @param  {DOMEvent} event
    */
   handleEvent(event) {
     switch (event.type) {
       case "DOMContentLoaded": {
         this.init();
         break;
       }
-      case "unload": {
-        this.uninit();
-        break;
-      }
       case "click": {
         this.handleClick(event);
         break;
       }
       case "input": {
         this.handleInput(event);
         break;
       }
       case "keypress": {
         this.handleKeyPress(event);
         break;
       }
-      case "change": {
-        this.handleChange(event);
-        break;
-      }
       case "contextmenu": {
         if (!(event.target instanceof HTMLInputElement) &&
             !(event.target instanceof HTMLTextAreaElement)) {
           event.preventDefault();
         }
         break;
       }
     }
@@ -150,17 +105,17 @@ class EditDialog {
   /**
    * Handle input events
    *
    * @param  {DOMEvent} event
    */
   handleInput(event) {
     // Toggle disabled attribute on the save button based on
     // whether the form is filled or empty.
-    if (Object.keys(this.buildFormObject()).length == 0) {
+    if (Object.keys(this._elements.fieldContainer.buildFormObject()).length == 0) {
       this._elements.save.setAttribute("disabled", true);
     } else {
       this._elements.save.removeAttribute("disabled");
     }
   }
 
   /**
    * Handle key press events
@@ -178,183 +133,54 @@ class EditDialog {
    */
   attachEventListeners() {
     window.addEventListener("keypress", this);
     window.addEventListener("contextmenu", this);
     this._elements.controlsContainer.addEventListener("click", this);
     document.addEventListener("input", this);
   }
 
-  /**
-   * Remove event listener
-   */
-  detachEventListeners() {
-    window.removeEventListener("keypress", this);
-    window.removeEventListener("contextmenu", this);
-    this._elements.controlsContainer.removeEventListener("click", this);
-    document.removeEventListener("input", this);
-  }
-
   // An interface to be inherited.
   localizeDocument() {}
-
-  // An interface to be inherited.
-  handleSubmit(event) {}
-
-  // An interface to be inherited.
-  handleChange(event) {}
 }
 
-class EditAddress extends EditDialog {
+class EditAddressDialog extends AutofillEditDialog {
   constructor(elements, record) {
-    let country = record ? record.country :
-                  FormAutofillUtils.supportedCountries.find(supported => supported == FormAutofillUtils.DEFAULT_REGION);
-    super("addresses", elements, record || {country});
-
-    this.formatForm(country);
-  }
-
-  /**
-   * Format the form based on country. The address-level1 and postal-code labels
-   * should be specific to the given country.
-   * @param  {string} country
-   */
-  formatForm(country) {
-    const {addressLevel1Label, postalCodeLabel, fieldsOrder} = FormAutofillUtils.getFormFormat(country);
-    this._elements.addressLevel1Label.dataset.localization = addressLevel1Label;
-    this._elements.postalCodeLabel.dataset.localization = postalCodeLabel;
-    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
-    this.arrangeFields(fieldsOrder);
-  }
-
-  arrangeFields(fieldsOrder) {
-    let fields = [
-      "name",
-      "organization",
-      "street-address",
-      "address-level2",
-      "address-level1",
-      "postal-code",
-    ];
-    let inputs = [];
-    for (let i = 0; i < fieldsOrder.length; i++) {
-      let {fieldId, newLine} = fieldsOrder[i];
-      let container = document.getElementById(`${fieldId}-container`);
-      inputs.push(...container.querySelectorAll("input, textarea, select"));
-      container.style.display = "flex";
-      container.style.order = i;
-      container.style.pageBreakAfter = newLine ? "always" : "auto";
-      // Remove the field from the list of fields
-      fields.splice(fields.indexOf(fieldId), 1);
-    }
-    for (let i = 0; i < inputs.length; i++) {
-      // Assign tabIndex starting from 1
-      inputs[i].tabIndex = i + 1;
-    }
-    // Hide the remaining fields
-    for (let field of fields) {
-      let container = document.getElementById(`${field}-container`);
-      container.style.display = "none";
-    }
+    super("addresses", elements, record);
   }
 
   localizeDocument() {
     if (this._record) {
       this._elements.title.dataset.localization = "editAddressTitle";
     }
-    let fragment = document.createDocumentFragment();
-    for (let country of FormAutofillUtils.supportedCountries) {
-      let option = new Option();
-      option.value = country;
-      option.dataset.localization = country.toLowerCase();
-      fragment.appendChild(option);
-    }
-    this._elements.country.appendChild(fragment);
-    FormAutofillUtils.localizeMarkup(REGIONS_BUNDLE_URI, this._elements.country);
   }
 
   async handleSubmit() {
-    await this.saveRecord(this.buildFormObject(), this._record ? this._record.guid : null);
+    await this.saveRecord(this._elements.fieldContainer.buildFormObject(), this._record ? this._record.guid : null);
     window.close();
   }
-
-  handleChange(event) {
-    this.formatForm(event.target.value);
-  }
-
-  attachEventListeners() {
-    this._elements.country.addEventListener("change", this);
-    super.attachEventListeners();
-  }
-
-  detachEventListeners() {
-    this._elements.country.removeEventListener("change", this);
-    super.detachEventListeners();
-  }
 }
 
-class EditCreditCard extends EditDialog {
+class EditCreditCardDialog extends AutofillEditDialog {
   constructor(elements, record) {
     super("creditCards", elements, record);
-    this.generateYears();
-  }
-
-  generateYears() {
-    const count = 11;
-    const currentYear = new Date().getFullYear();
-    const ccExpYear = this._record && this._record["cc-exp-year"];
-
-    if (ccExpYear && ccExpYear < currentYear) {
-      this._elements.year.appendChild(new Option(ccExpYear));
-    }
-
-    for (let i = 0; i < count; i++) {
-      let year = currentYear + i;
-      let option = new Option(year);
-      this._elements.year.appendChild(option);
-    }
-
-    if (ccExpYear && ccExpYear > currentYear + count) {
-      this._elements.year.appendChild(new Option(ccExpYear));
-    }
   }
 
   localizeDocument() {
     if (this._record) {
       this._elements.title.dataset.localization = "editCreditCardTitle";
     }
-    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
-  }
-
-  /**
-   * Decrypt cc-number first and fill the form.
-   * @param  {object} creditCard
-   */
-  async loadInitialValues(creditCard) {
-    let decryptedCC = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
-    super.loadInitialValues(Object.assign({}, creditCard, {"cc-number": decryptedCC}));
   }
 
   async handleSubmit() {
-    let creditCard = this.buildFormObject();
-    // Show error on the cc-number field if it's empty or invalid
-    if (!FormAutofillUtils.isCCNumber(creditCard["cc-number"])) {
-      this._elements.ccNumber.setCustomValidity(true);
+    let creditCard = this._elements.fieldContainer.buildFormObject();
+    if (!this._elements.fieldContainer._elements.form.checkValidity()) {
       return;
     }
 
     // TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
     // APIs are refactored to be async functions (bug 1399367).
     if (await MasterPassword.ensureLoggedIn()) {
       await this.saveRecord(creditCard, this._record ? this._record.guid : null);
     }
     window.close();
   }
-
-  handleInput(event) {
-    // Clear the error message if cc-number is valid
-    if (event.target == this._elements.ccNumber &&
-        FormAutofillUtils.isCCNumber(this._elements.ccNumber.value)) {
-      this._elements.ccNumber.setCustomValidity("");
-    }
-    super.handleInput(event);
-  }
 }
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/l10n.js
@@ -0,0 +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/. */
+
+"use strict";
+
+/**
+ * This file will be replaced by Fluent but it's a middle ground so we can share
+ * the edit dialog code with the unprivileged PaymentRequest dialog before the
+ * Fluent conversion
+ */
+
+ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+
+const L10N_ATTRIBUTES = ["data-localization", "data-localization-region"];
+
+let mutationObserver = new MutationObserver(function onMutation(mutations) {
+  for (let mutation of mutations) {
+    if (!mutation.target.hasAttribute(mutation.attributeName)) {
+      // The attribute was removed in the meantime.
+      continue;
+    }
+    FormAutofillUtils.localizeAttributeForElement(mutation.target, mutation.attributeName);
+  }
+});
+
+document.addEventListener("DOMContentLoaded", function onDCL() {
+  FormAutofillUtils.localizeMarkup(document);
+  mutationObserver.observe(document, {
+    attributes: true,
+    attributeFilter: L10N_ATTRIBUTES,
+    subtree: true,
+  });
+}, {
+  once: true,
+});
--- a/browser/extensions/formautofill/content/manageDialog.js
+++ b/browser/extensions/formautofill/content/manageDialog.js
@@ -3,17 +3,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* exported ManageAddresses, ManageCreditCards */
 
 "use strict";
 
 const EDIT_ADDRESS_URL = "chrome://formautofill/content/editAddress.xhtml";
 const EDIT_CREDIT_CARD_URL = "chrome://formautofill/content/editCreditCard.xhtml";
-const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
                                "resource://formautofill/FormAutofillStorage.jsm");
 ChromeUtils.defineModuleGetter(this, "MasterPassword",
@@ -44,17 +43,17 @@ class ManageRecords {
   uninit() {
     log.debug("uninit");
     this.detachEventListeners();
     this._elements = null;
   }
 
   localizeDocument() {
     document.documentElement.style.minWidth = FormAutofillUtils.stringBundle.GetStringFromName("manageDialogsWidth");
-    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
+    FormAutofillUtils.localizeMarkup(document);
   }
 
   /**
    * Get the selected options on the addresses element.
    *
    * @returns {array<DOMElement>}
    */
   get _selectedOptions() {
@@ -313,17 +312,22 @@ class ManageCreditCards extends ManageRe
    * Open the edit address dialog to create/edit a credit card.
    *
    * @param  {object} creditCard [optional]
    */
   async openEditDialog(creditCard) {
     // If master password is set, ask for password if user is trying to edit an
     // existing credit card.
     if (!creditCard || !this._hasMasterPassword || await MasterPassword.ensureLoggedIn(true)) {
-      this.prefWin.gSubDialog.open(EDIT_CREDIT_CARD_URL, "resizable=no", creditCard);
+      let decryptedCCNumObj = {};
+      if (creditCard) {
+        decryptedCCNumObj["cc-number"] = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
+      }
+      let decryptedCreditCard = Object.assign({}, creditCard, decryptedCCNumObj);
+      this.prefWin.gSubDialog.open(EDIT_CREDIT_CARD_URL, "resizable=no", decryptedCreditCard);
     }
   }
 
   /**
    * Get credit card display label. It should display masked numbers and the
    * cardholder's name, separated by a comma. If `showCreditCards` is set to
    * true, decrypted credit card numbers are shown instead.
    *
--- a/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
@@ -106,21 +106,25 @@ add_task(async function test_editAddress
   is(addresses[0]["given-name"], TEST_ADDRESS_1["given-name"] + "test", "given-name changed");
   await removeAddresses([addresses[0].guid]);
 
   addresses = await getAddresses();
   is(addresses.length, 0, "Address storage is empty");
 });
 
 add_task(async function test_saveAddressCA() {
-  await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+  await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
     let doc = win.document;
     // Change country to verify labels
     doc.querySelector("#country").focus();
     EventUtils.synthesizeKey("Canada", {}, win);
+
+    await TestUtils.waitForCondition(() => {
+      return doc.querySelector("#address-level1-container > span").textContent == "Province";
+    }, "Wait for the mutation observer to change the labels");
     is(doc.querySelector("#address-level1-container > span").textContent, "Province",
                          "CA address-level1 label should be 'Province'");
     is(doc.querySelector("#postal-code-container > span").textContent, "Postal Code",
                          "CA postal-code label should be 'Postal Code'");
     // Input address info and verify move through form with tab keys
     doc.querySelector("#given-name").focus();
     const keyInputs = [
       TEST_ADDRESS_CA_1["given-name"],
@@ -153,21 +157,24 @@ add_task(async function test_saveAddress
   let addresses = await getAddresses();
   for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS_CA_1)) {
     is(addresses[0][fieldName], fieldValue, "check " + fieldName);
   }
   await removeAllRecords();
 });
 
 add_task(async function test_saveAddressDE() {
-  await testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+  await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
     let doc = win.document;
     // Change country to verify labels
     doc.querySelector("#country").focus();
     EventUtils.synthesizeKey("Germany", {}, win);
+    await TestUtils.waitForCondition(() => {
+      return doc.querySelector("#postal-code-container > span").textContent == "Postal Code";
+    }, "Wait for the mutation observer to change the labels");
     is(doc.querySelector("#postal-code-container > span").textContent, "Postal Code",
                          "DE postal-code label should be 'Postal Code'");
     is(doc.querySelector("#address-level1-container").style.display, "none",
                          "DE address-level1 should be hidden");
     // Input address info and verify move through form with tab keys
     doc.querySelector("#given-name").focus();
     const keyInputs = [
       TEST_ADDRESS_DE_1["given-name"],
--- a/browser/extensions/formautofill/test/browser/browser_editCreditCardDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editCreditCardDialog.js
@@ -124,17 +124,17 @@ add_task(async function test_addInvalidC
   await new Promise(resolve => {
     let win = window.openDialog(EDIT_CREDIT_CARD_DIALOG_URL);
     waitForFocus(() => {
       const unloadHandler = () => ok(false, "Edit credit card dialog shouldn't be closed");
       win.addEventListener("unload", unloadHandler);
 
       EventUtils.synthesizeKey("VK_TAB", {}, win);
       EventUtils.synthesizeKey("test", {}, win);
-      win.document.querySelector("#save").click();
+      EventUtils.synthesizeMouseAtCenter(win.document.querySelector("#save"), {}, win);
 
       is(win.document.querySelector("form").checkValidity(), false, "cc-number is invalid");
       SimpleTest.requestFlakyTimeout("Ensure the window remains open after save attempt");
       setTimeout(() => {
         win.removeEventListener("unload", unloadHandler);
         win.close();
         resolve();
       }, 500);
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -334,16 +334,16 @@ async function removeAllRecords() {
 async function waitForFocusAndFormReady(win) {
   return Promise.all([
     new Promise(resolve => waitForFocus(resolve, win)),
     BrowserTestUtils.waitForEvent(win, "FormReady"),
   ]);
 }
 
 async function testDialog(url, testFn, arg) {
-  let win = window.openDialog(url, null, null, arg);
+  let win = window.openDialog(url, null, "width=600,height=600", arg);
   await waitForFocusAndFormReady(win);
   let unloadPromise = BrowserTestUtils.waitForEvent(win, "unload");
-  testFn(win);
+  await testFn(win);
   return unloadPromise;
 }
 
 registerCleanupFunction(removeAllRecords);
--- a/browser/modules/AsyncTabSwitcher.jsm
+++ b/browser/modules/AsyncTabSwitcher.jsm
@@ -249,17 +249,19 @@ class AsyncTabSwitcher {
 
     this.setTabStateNoAction(tab, state);
 
     let browser = tab.linkedBrowser;
     let { tabParent } = browser.frameLoader;
     if (state == this.STATE_LOADING) {
       this.assert(!this.minimizedOrFullyOccluded);
 
-      if (!this.tabbrowser.tabWarmingEnabled) {
+      // If we're not in the process of warming this tab, we
+      // don't need to delay activating its DocShell.
+      if (!this.warmingTabs.has(tab)) {
         browser.docShellIsActive = true;
       }
 
       if (tabParent) {
         browser.renderLayers = true;
       } else {
         this.onLayersReady(browser);
       }
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -29,17 +29,17 @@ browser.jar:
   skin/classic/browser/feeds/subscribe.css            (feeds/subscribe.css)
   skin/classic/browser/notification-icons/geo-blocked.svg  (notification-icons/geo-blocked.svg)
   skin/classic/browser/notification-icons/geo-detailed.svg (notification-icons/geo-detailed.svg)
   skin/classic/browser/notification-icons/geo.svg          (notification-icons/geo.svg)
   skin/classic/browser/places/allBookmarks.png        (places/allBookmarks.png)
   skin/classic/browser/places/bookmarksMenu.png       (places/bookmarksMenu.png)
   skin/classic/browser/places/bookmarksToolbar.png    (places/bookmarksToolbar.png)
   skin/classic/browser/places/bookmarks-menu-arrow.png           (places/bookmarks-menu-arrow.png)
-* skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
+* skin/classic/browser/places/editBookmark.css (places/editBookmark.css)
   skin/classic/browser/places/livemark-item.png       (places/livemark-item.png)
 * skin/classic/browser/places/places.css              (places/places.css)
   skin/classic/browser/places/organizer.css           (places/organizer.css)
   skin/classic/browser/places/organizer.xml           (places/organizer.xml)
   skin/classic/browser/places/tag.png                 (places/tag.png)
   skin/classic/browser/places/toolbarDropMarker.png   (places/toolbarDropMarker.png)
   skin/classic/browser/places/unsortedBookmarks.png   (places/unsortedBookmarks.png)
   skin/classic/browser/places/downloads.png           (places/downloads.png)
rename from browser/themes/linux/places/editBookmarkOverlay.css
rename to browser/themes/linux/places/editBookmark.css
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -447,20 +447,20 @@ html|input.urlbar-input {
 #editBookmarkPanelHeader {
   margin-bottom: 6px;
 }
 
 .editBookmarkPanelBottomButton:last-child {
   margin-inline-start: 8px;
 }
 
-/* The following elements come from editBookmarkOverlay.xul. Styling that's
+/* The following elements come from editBookmarkPanel.inc.xul. Styling that's
    specific to the editBookmarkPanel should be in browser.css. Styling that
-   should be shared by all editBookmarkOverlay.xul consumers should be in
-   editBookmarkOverlay.css. */
+   should be shared by all editBookmarkPanel.inc.xul consumers should be in
+   editBookmark.css. */
 
 #editBMPanel_newFolderBox {
   background: linear-gradient(#fff, #f2f2f2);
   background-origin: padding-box;
   background-clip: padding-box;
   border-radius: 0 0 3px 3px;
   border: 1px solid #a5a5a5;
   box-shadow: inset 0 1px rgba(255,255,255,.8),
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -38,17 +38,17 @@ browser.jar:
   skin/classic/browser/places/allBookmarks.png              (places/allBookmarks.png)
 * skin/classic/browser/places/places.css                    (places/places.css)
   skin/classic/browser/places/organizer.css                 (places/organizer.css)
   skin/classic/browser/places/bookmarksMenu.png             (places/bookmarksMenu.png)
   skin/classic/browser/places/bookmarksToolbar.png          (places/bookmarksToolbar.png)
   skin/classic/browser/places/bookmarksToolbar@2x.png       (places/bookmarksToolbar@2x.png)
   skin/classic/browser/places/toolbar.png                   (places/toolbar.png)
   skin/classic/browser/places/toolbarDropMarker.png         (places/toolbarDropMarker.png)
-  skin/classic/browser/places/editBookmarkOverlay.css       (places/editBookmarkOverlay.css)
+  skin/classic/browser/places/editBookmark.css              (places/editBookmark.css)
   skin/classic/browser/places/unfiledBookmarks.png          (places/unfiledBookmarks.png)
   skin/classic/browser/places/unfiledBookmarks@2x.png       (places/unfiledBookmarks@2x.png)
   skin/classic/browser/places/tag.png                       (places/tag.png)
   skin/classic/browser/places/tag@2x.png                    (places/tag@2x.png)
   skin/classic/browser/places/downloads.png                 (places/downloads.png)
   skin/classic/browser/places/livemark-item.png             (places/livemark-item.png)
   skin/classic/browser/preferences/alwaysAsk.png            (preferences/alwaysAsk.png)
   skin/classic/browser/preferences/application.png          (preferences/application.png)
rename from browser/themes/osx/places/editBookmarkOverlay.css
rename to browser/themes/osx/places/editBookmark.css
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -31,17 +31,17 @@ browser.jar:
   skin/classic/browser/notification-icons/geo-blocked.svg      (notification-icons/geo-blocked.svg)
   skin/classic/browser/notification-icons/geo-detailed.svg     (notification-icons/geo-detailed.svg)
   skin/classic/browser/notification-icons/geo.svg              (notification-icons/geo.svg)
 * skin/classic/browser/places/places.css                       (places/places.css)
 * skin/classic/browser/places/organizer.css                    (places/organizer.css)
   skin/classic/browser/places/bookmarksMenu.png                (places/bookmarksMenu.png)
   skin/classic/browser/places/bookmarksToolbar.png             (places/bookmarksToolbar.png)
   skin/classic/browser/places/toolbarDropMarker.png            (places/toolbarDropMarker.png)
-  skin/classic/browser/places/editBookmarkOverlay.css          (places/editBookmarkOverlay.css)
+  skin/classic/browser/places/editBookmark.css                 (places/editBookmark.css)
   skin/classic/browser/places/libraryToolbar.png               (places/libraryToolbar.png)
   skin/classic/browser/places/tag.png                          (places/tag.png)
   skin/classic/browser/places/allBookmarks.png                 (places/allBookmarks.png)
   skin/classic/browser/places/unsortedBookmarks.png            (places/unsortedBookmarks.png)
   skin/classic/browser/places/downloads.png                    (places/downloads.png)
   skin/classic/browser/places/livemark-item.png                (places/livemark-item.png)
   skin/classic/browser/preferences/alwaysAsk.png               (preferences/alwaysAsk.png)
   skin/classic/browser/preferences/application.png             (preferences/application.png)
rename from browser/themes/windows/places/editBookmarkOverlay.css
rename to browser/themes/windows/places/editBookmark.css
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
@@ -92,17 +92,17 @@ add_task(async function testWebExtension
 
     toolbox.selectTool("webconsole")
       .then(async (console) => {
         dump(`Clicking the noautohide button\n`);
         toolbox.doc.getElementById("command-button-noautohide").click();
         dump(`Clicked the noautohide button\n`);
 
         popupFramePromise = new Promise(resolve => {
-          let listener = (event, data) => {
+          let listener = data => {
             if (data.frames.some(({url}) => url && url.endsWith("popup.html"))) {
               toolbox.target.off("frame-update", listener);
               resolve();
             }
           };
           toolbox.target.on("frame-update", listener);
         });
 
--- a/devtools/client/canvasdebugger/canvasdebugger.js
+++ b/devtools/client/canvasdebugger/canvasdebugger.js
@@ -129,37 +129,31 @@ var EventsHandler = {
    */
   initialize: function () {
     // Make sure the backend is prepared to handle <canvas> contexts.
     // Since actors are created lazily on the first request to them, we need to send an
     // early request to ensure the CallWatcherActor is running and watching for new window
     // globals.
     gFront.setup({ reload: false });
 
-    this._onTabNavigated = this._onTabNavigated.bind(this);
-    gTarget.on("will-navigate", this._onTabNavigated);
-    gTarget.on("navigate", this._onTabNavigated);
+    this._onTabWillNavigate = this._onTabWillNavigate.bind(this);
+    gTarget.on("will-navigate", this._onTabWillNavigate);
   },
 
   /**
    * Remove events emitted by the current tab target.
    */
   destroy: function () {
-    gTarget.off("will-navigate", this._onTabNavigated);
-    gTarget.off("navigate", this._onTabNavigated);
+    gTarget.off("will-navigate", this._onTabWillNavigate);
   },
 
   /**
    * Called for each location change in the debugged tab.
    */
-  _onTabNavigated: function (event) {
-    if (event != "will-navigate") {
-      return;
-    }
-
+  _onTabWillNavigate: function () {
     // Reset UI.
     SnapshotsListView.empty();
     CallsListView.empty();
 
     $("#record-snapshot").removeAttribute("checked");
     $("#record-snapshot").removeAttribute("disabled");
     $("#record-snapshot").hidden = false;
 
--- a/devtools/client/debugger/new/debugger.js
+++ b/devtools/client/debugger/new/debugger.js
@@ -23950,17 +23950,17 @@ function _interopRequireDefault(obj) { r
 /**
  * @memberof actions/navigation
  * @static
  */
 /* 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/>. */
 
-function willNavigate(_, event) {
+function willNavigate(event) {
   return async function ({ dispatch, getState, client, sourceMaps }) {
     await sourceMaps.clearSourceMaps();
     (0, _wasm.clearWasmStates)();
     (0, _editor.clearDocuments)();
     (0, _parser.clearSymbols)();
     (0, _parser.clearASTs)();
     (0, _parser.clearScopes)();
     (0, _parser.clearSources)();
@@ -51332,9 +51332,9 @@ module.exports = "<!-- This Source Code 
 /***/ 999:
 /***/ (function(module, exports) {
 
 module.exports = "<!-- 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/. --><svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 32 32\"><path fill=\"#444444\" d=\"M16.232 24.047c-0.15-0.034-0.295-0.081-0.441-0.124-0.037-0.011-0.074-0.022-0.11-0.033-0.143-0.044-0.284-0.090-0.425-0.139-0.019-0.007-0.039-0.014-0.058-0.021-0.126-0.045-0.251-0.091-0.375-0.139-0.035-0.014-0.070-0.027-0.105-0.041-0.136-0.054-0.271-0.11-0.405-0.168-0.027-0.012-0.054-0.024-0.081-0.036-0.115-0.052-0.228-0.105-0.341-0.159-0.033-0.016-0.065-0.031-0.099-0.047-0.089-0.043-0.177-0.090-0.264-0.134-0.059-0.031-0.118-0.060-0.176-0.092-0.107-0.058-0.212-0.117-0.317-0.178-0.035-0.020-0.071-0.038-0.107-0.059-0.139-0.081-0.277-0.166-0.412-0.252-0.037-0.024-0.074-0.050-0.111-0.074-0.099-0.063-0.197-0.128-0.293-0.195-0.032-0.021-0.063-0.045-0.094-0.066-0.093-0.066-0.186-0.132-0.277-0.2-0.042-0.031-0.082-0.062-0.123-0.093-0.084-0.064-0.168-0.129-0.25-0.196-0.037-0.030-0.075-0.060-0.112-0.090-0.105-0.087-0.209-0.173-0.312-0.263-0.011-0.009-0.023-0.018-0.034-0.028-0.111-0.097-0.22-0.197-0.328-0.298-0.031-0.030-0.062-0.059-0.092-0.088-0.080-0.076-0.158-0.153-0.235-0.231-0.031-0.031-0.062-0.061-0.092-0.092-0.098-0.101-0.194-0.203-0.289-0.306-0.005-0.005-0.010-0.010-0.014-0.015-0.1-0.109-0.197-0.221-0.293-0.334-0.026-0.031-0.051-0.060-0.077-0.091-0.071-0.086-0.142-0.173-0.211-0.261-0.026-0.031-0.052-0.064-0.077-0.096-0.083-0.108-0.164-0.215-0.243-0.324-2.197-2.996-2.986-7.129-1.23-10.523l-1.556 1.974c-1.994 2.866-1.746 6.595-0.223 9.64 0.036 0.073 0.074 0.145 0.112 0.217 0.024 0.045 0.046 0.092 0.071 0.137 0.014 0.027 0.030 0.053 0.044 0.079 0.026 0.049 0.053 0.095 0.079 0.142 0.047 0.083 0.096 0.166 0.145 0.249 0.027 0.045 0.055 0.091 0.083 0.136 0.055 0.089 0.111 0.176 0.169 0.264 0.024 0.037 0.047 0.075 0.072 0.111 0.080 0.118 0.161 0.236 0.244 0.353 0.002 0.003 0.005 0.006 0.007 0.009 0.013 0.018 0.028 0.037 0.041 0.056 0.072 0.1 0.147 0.199 0.223 0.296 0.028 0.036 0.056 0.072 0.084 0.107 0.067 0.085 0.136 0.169 0.206 0.253 0.026 0.031 0.052 0.063 0.079 0.094 0.094 0.11 0.189 0.22 0.287 0.328 0.002 0.002 0.004 0.004 0.006 0.005 0.004 0.005 0.008 0.008 0.011 0.013 0.095 0.104 0.193 0.206 0.291 0.307 0.031 0.032 0.062 0.063 0.093 0.094 0.076 0.077 0.154 0.153 0.233 0.228 0.032 0.030 0.063 0.061 0.095 0.091 0.105 0.099 0.211 0.196 0.319 0.291 0.002 0.001 0.003 0.003 0.005 0.004 0.018 0.016 0.038 0.032 0.056 0.047 0.095 0.082 0.192 0.164 0.29 0.245 0.040 0.032 0.080 0.064 0.12 0.096 0.080 0.064 0.16 0.127 0.241 0.189 0.043 0.033 0.086 0.066 0.129 0.098 0.089 0.066 0.18 0.131 0.271 0.194 0.033 0.024 0.065 0.047 0.099 0.070 0.009 0.006 0.018 0.013 0.027 0.019 0.086 0.060 0.175 0.116 0.263 0.174 0.038 0.025 0.075 0.051 0.114 0.076 0.136 0.086 0.273 0.171 0.412 0.253 0.038 0.022 0.076 0.043 0.114 0.064 0.102 0.059 0.205 0.117 0.309 0.174 0.056 0.030 0.114 0.059 0.171 0.088 0.073 0.038 0.147 0.078 0.221 0.115 0.017 0.009 0.035 0.017 0.051 0.025 0.030 0.014 0.060 0.028 0.091 0.044 0.116 0.055 0.233 0.11 0.351 0.163 0.025 0.011 0.049 0.022 0.074 0.033 0.135 0.059 0.271 0.116 0.409 0.17 0.033 0.014 0.066 0.026 0.1 0.039 0.127 0.049 0.256 0.098 0.386 0.143 0.016 0.006 0.032 0.012 0.049 0.017 0.142 0.050 0.286 0.096 0.43 0.141 0.034 0.010 0.069 0.021 0.104 0.031 0.147 0.044 0.293 0.097 0.445 0.125 9.643 1.759 12.444-5.795 12.444-5.795-2.352 3.065-6.528 3.873-10.485 2.974zM12.758 16.231c0.216 0.31 0.456 0.678 0.742 0.927 0.104 0.114 0.213 0.226 0.324 0.336 0.028 0.029 0.057 0.056 0.085 0.084 0.108 0.105 0.217 0.207 0.33 0.307 0.005 0.003 0.009 0.008 0.014 0.012 0.001 0.001 0.002 0.002 0.003 0.003 0.125 0.11 0.255 0.216 0.386 0.319 0.029 0.022 0.058 0.046 0.088 0.069 0.132 0.101 0.266 0.2 0.404 0.295 0.004 0.003 0.008 0.006 0.012 0.009 0.061 0.042 0.123 0.081 0.184 0.122 0.030 0.019 0.058 0.040 0.088 0.058 0.098 0.063 0.198 0.125 0.299 0.183 0.014 0.009 0.028 0.016 0.042 0.024 0.087 0.051 0.176 0.1 0.265 0.148 0.031 0.018 0.063 0.033 0.094 0.049 0.061 0.032 0.123 0.064 0.185 0.096 0.009 0.004 0.019 0.009 0.028 0.012 0.127 0.063 0.255 0.123 0.386 0.18 0.028 0.012 0.057 0.023 0.085 0.035 0.105 0.045 0.21 0.088 0.316 0.129 0.045 0.017 0.091 0.033 0.135 0.050 0.097 0.036 0.193 0.069 0.291 0.101 0.044 0.014 0.087 0.028 0.131 0.042 0.139 0.043 0.276 0.098 0.42 0.122 7.445 1.233 9.164-4.499 9.164-4.499-1.549 2.232-4.55 3.296-7.752 2.465-0.142-0.038-0.282-0.078-0.422-0.122-0.043-0.013-0.084-0.027-0.127-0.041-0.099-0.032-0.197-0.066-0.295-0.102-0.045-0.017-0.089-0.033-0.133-0.050-0.107-0.041-0.213-0.084-0.317-0.128-0.029-0.013-0.058-0.024-0.086-0.036-0.131-0.057-0.261-0.117-0.389-0.18-0.066-0.032-0.13-0.066-0.195-0.099-0.037-0.019-0.075-0.038-0.112-0.058-0.083-0.045-0.165-0.092-0.246-0.139-0.019-0.011-0.040-0.022-0.059-0.033-0.101-0.059-0.2-0.12-0.299-0.182-0.030-0.019-0.060-0.040-0.090-0.060-0.065-0.042-0.13-0.085-0.193-0.128-0.137-0.095-0.271-0.194-0.402-0.294-0.030-0.024-0.061-0.047-0.091-0.071-1.401-1.107-2.512-2.619-3.041-4.334-0.554-1.778-0.434-3.775 0.525-5.395l-1.178 1.663c-1.442 2.075-1.364 4.853-0.239 7.048 0.189 0.368 0.401 0.725 0.638 1.065zM20.606 13.664c0.061 0.023 0.123 0.043 0.185 0.064 0.027 0.008 0.054 0.018 0.082 0.026 0.088 0.027 0.175 0.060 0.265 0.076 4.111 0.794 5.226-2.11 5.523-2.537-0.977 1.406-2.618 1.744-4.632 1.255-0.159-0.039-0.334-0.096-0.488-0.151-0.197-0.070-0.39-0.15-0.579-0.24-0.358-0.172-0.699-0.38-1.015-0.619-1.802-1.367-2.922-3.976-1.746-6.101l-0.637 0.877c-0.85 1.251-0.933 2.805-0.344 4.186 0.622 1.467 1.897 2.617 3.384 3.163z\"></path></svg>"
 
 /***/ })
 
 /******/ });
-});
\ No newline at end of file
+});
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-chrome-create.js
@@ -17,17 +17,17 @@ const { BrowserToolboxProcess } = Chrome
   "resource://devtools/client/framework/ToolboxProcess.jsm",
   {}
 );
 let gProcess = undefined;
 
 function initChromeDebugger() {
   info("Initializing a chrome debugger process.");
   return new Promise(resolve => {
-    BrowserToolboxProcess.init(onClose, (event, _process) => {
+    BrowserToolboxProcess.init(onClose, _process => {
       info("Browser toolbox process started successfully.");
       resolve(_process);
     });
   });
 }
 
 function onClose() {
   is(
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -769,17 +769,17 @@ AddonDebugger.prototype = {
 };
 
 function initChromeDebugger(aOnClose) {
   info("Initializing a chrome debugger process.");
 
   let deferred = promise.defer();
 
   // Wait for the toolbox process to start...
-  BrowserToolboxProcess.init(aOnClose, (aEvent, aProcess) => {
+  BrowserToolboxProcess.init(aOnClose, aProcess => {
     info("Browser toolbox process started successfully.");
 
     prepareDebugger(aProcess);
     deferred.resolve(aProcess);
   });
 
   return deferred.promise;
 }
--- a/devtools/client/framework/ToolboxProcess.jsm
+++ b/devtools/client/framework/ToolboxProcess.jsm
@@ -12,17 +12,17 @@ const CHROME_DEBUGGER_PROFILE_NAME = "ch
 const { require, DevToolsLoader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "Subprocess", "resource://gre/modules/Subprocess.jsm");
 XPCOMUtils.defineLazyGetter(this, "Telemetry", function() {
   return require("devtools/client/shared/telemetry");
 });
 XPCOMUtils.defineLazyGetter(this, "EventEmitter", function() {
-  return require("devtools/shared/old-event-emitter");
+  return require("devtools/shared/event-emitter");
 });
 XPCOMUtils.defineLazyGetter(this, "system", function() {
   return require("devtools/shared/system");
 });
 const promise = require("promise");
 const Services = require("Services");
 
 this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"];
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -737,26 +737,26 @@ var gDevToolsBrowser = exports.gDevTools
     gDevTools.destroy({ shuttingDown });
   },
 };
 
 // Handle all already registered tools,
 gDevTools.getToolDefinitionArray()
          .forEach(def => gDevToolsBrowser._addToolToWindows(def));
 // and the new ones.
-gDevTools.on("tool-registered", function(ev, toolId) {
+gDevTools.on("tool-registered", function(toolId) {
   let toolDefinition = gDevTools._tools.get(toolId);
   // If the tool has been registered globally, add to all the
   // available windows.
   if (toolDefinition) {
     gDevToolsBrowser._addToolToWindows(toolDefinition);
   }
 });
 
-gDevTools.on("tool-unregistered", function(ev, toolId) {
+gDevTools.on("tool-unregistered", function(toolId) {
   gDevToolsBrowser._removeToolFromWindows(toolId);
 });
 
 gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox);
 gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox);
 
 Services.obs.addObserver(gDevToolsBrowser, "quit-application");
 Services.obs.addObserver(gDevToolsBrowser, "browser-delayed-startup-finished");
--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -19,17 +19,17 @@ loader.lazyRequireGetter(this, "HUDServi
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
 
 loader.lazyRequireGetter(this, "WebExtensionInspectedWindowFront",
       "devtools/shared/fronts/webextension-inspected-window", true);
 
 const {defaultTools: DefaultTools, defaultThemes: DefaultThemes} =
   require("devtools/client/definitions");
-const EventEmitter = require("devtools/shared/old-event-emitter");
+const EventEmitter = require("devtools/shared/event-emitter");
 const {getTheme, setTheme, addThemeObserver, removeThemeObserver} =
   require("devtools/client/shared/theme");
 
 const FORBIDDEN_IDS = new Set(["toolbox", ""]);
 const MAX_ORDINAL = 99;
 
 /**
  * DevTools is a class that represents a set of developer tools, it holds a
--- a/devtools/client/framework/menu.js
+++ b/devtools/client/framework/menu.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const EventEmitter = require("devtools/shared/old-event-emitter");
+const EventEmitter = require("devtools/shared/event-emitter");
 
 /**
  * A partial implementation of the Menu API provided by electron:
  * https://github.com/electron/electron/blob/master/docs/api/menu.md.
  *
  * Extra features:
  *  - Emits an 'open' and 'close' event when the menu is opened/closed
 
--- a/devtools/client/framework/selection.js
+++ b/devtools/client/framework/selection.js
@@ -2,17 +2,17 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const nodeConstants = require("devtools/shared/dom-node-constants");
-var EventEmitter = require("devtools/shared/old-event-emitter");
+var EventEmitter = require("devtools/shared/event-emitter");
 
 /**
  * API
  *
  *   new Selection(walker=null)
  *   destroy()
  *   node (readonly)
  *   setNode(node, origin="unknown")
--- a/devtools/client/framework/source-map-url-service.js
+++ b/devtools/client/framework/source-map-url-service.js
@@ -65,17 +65,17 @@ SourceMapURLService.prototype._getLoadin
           });
     }
 
     // Start fetching the sources now.
     let loadingPromise = this._toolbox.threadClient.getSources().then(({sources}) => {
       // Ignore errors.  Register the sources we got; we can't rely on
       // an event to arrive if the source actor already existed.
       for (let source of sources) {
-        this._onSourceUpdated(null, {source});
+        this._onSourceUpdated({source});
       }
     }, e => {
       // Also ignore any protocol-based errors.
     });
 
     this._loadingPromise = Promise.all([styleSheetsLoadingPromise, loadingPromise]);
   }
   return this._loadingPromise;
@@ -105,17 +105,17 @@ SourceMapURLService.prototype.destroy = 
   }
   Services.prefs.removeObserver(SOURCE_MAP_PREF, this._onPrefChanged);
   this._target = this._urls = this._subscriptions = this._idMap = null;
 };
 
 /**
  * A helper function that is called when a new source is available.
  */
-SourceMapURLService.prototype._onSourceUpdated = function (_, sourceEvent) {
+SourceMapURLService.prototype._onSourceUpdated = function (sourceEvent) {
   // Maybe we were shut down while waiting.
   if (!this._urls) {
     return;
   }
 
   let { source } = sourceEvent;
   let { generatedUrl, url, actor: id, sourceMapURL } = source;
 
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Ci } = require("chrome");
 const defer = require("devtools/shared/defer");
-const EventEmitter = require("devtools/shared/old-event-emitter");
+const EventEmitter = require("devtools/shared/event-emitter");
 const Services = require("Services");
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "DebuggerClient",
   "devtools/shared/client/debugger-client", true);
 loader.lazyRequireGetter(this, "gDevTools",
   "devtools/client/framework/devtools", true);
@@ -617,17 +617,17 @@ TabTarget.prototype = {
     // remotenesschange events. But we should ignore them as at the end
     // the content doesn't change its remoteness.
     if (this._tab.isResponsiveDesignMode) {
       return;
     }
 
     // Save a reference to the tab as it will be nullified on destroy
     let tab = this._tab;
-    let onToolboxDestroyed = (event, target) => {
+    let onToolboxDestroyed = target => {
       if (target != this) {
         return;
       }
       gDevTools.off("toolbox-destroyed", target);
 
       // Recreate a fresh target instance as the current one is now destroyed
       let newTarget = TargetFactory.forTab(tab);
       gDevTools.showToolbox(newTarget);
--- a/devtools/client/framework/test/browser_devtools_api.js
+++ b/devtools/client/framework/test/browser_devtools_api.js
@@ -36,29 +36,29 @@ function runTests1(tab) {
   ok(gDevTools.getToolDefinitionMap().has(toolId1),
     "The tool is registered");
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
   let events = {};
 
   // Check events on the gDevTools and toolbox objects.
-  gDevTools.once(toolId1 + "-init", (event, toolbox, iframe) => {
+  gDevTools.once(toolId1 + "-init", (toolbox, iframe) => {
     ok(iframe, "iframe argument available");
 
-    toolbox.once(toolId1 + "-init", (innerEvent, innerIframe) => {
+    toolbox.once(toolId1 + "-init", innerIframe => {
       ok(innerIframe, "innerIframe argument available");
       events.init = true;
     });
   });
 
-  gDevTools.once(toolId1 + "-ready", (event, toolbox, panel) => {
+  gDevTools.once(toolId1 + "-ready", (toolbox, panel) => {
     ok(panel, "panel argument available");
 
-    toolbox.once(toolId1 + "-ready", (innerEvent, innerPanel) => {
+    toolbox.once(toolId1 + "-ready", innerPanel => {
       ok(innerPanel, "innerPanel argument available");
       events.ready = true;
     });
   });
 
   gDevTools.showToolbox(target, toolId1).then(function (toolbox) {
     is(toolbox.target, target, "toolbox target is correct");
     is(toolbox.target.tab, gBrowser.selectedTab, "targeted tab is correct");
@@ -95,38 +95,38 @@ function runTests2() {
   ok(gDevTools.getToolDefinitionMap().has(toolId2),
     "The tool is registered");
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
   let events = {};
 
   // Check events on the gDevTools and toolbox objects.
-  gDevTools.once(toolId2 + "-init", (event, toolbox, iframe) => {
+  gDevTools.once(toolId2 + "-init", (toolbox, iframe) => {
     ok(iframe, "iframe argument available");
 
-    toolbox.once(toolId2 + "-init", (innerEvent, innerIframe) => {
+    toolbox.once(toolId2 + "-init", innerIframe => {
       ok(innerIframe, "innerIframe argument available");
       events.init = true;
     });
   });
 
-  gDevTools.once(toolId2 + "-build", (event, toolbox, panel, iframe) => {
+  gDevTools.once(toolId2 + "-build", (toolbox, panel, iframe) => {
     ok(panel, "panel argument available");
 
-    toolbox.once(toolId2 + "-build", (innerEvent, innerPanel, innerIframe) => {
+    toolbox.once(toolId2 + "-build", (innerPanel, innerIframe) => {
       ok(innerPanel, "innerPanel argument available");
       events.build = true;
     });
   });
 
-  gDevTools.once(toolId2 + "-ready", (event, toolbox, panel) => {
+  gDevTools.once(toolId2 + "-ready", (toolbox, panel) => {
     ok(panel, "panel argument available");
 
-    toolbox.once(toolId2 + "-ready", (innerEvent, innerPanel) => {
+    toolbox.once(toolId2 + "-ready", innerPanel => {
       ok(innerPanel, "innerPanel argument available");
       events.ready = true;
     });
   });
 
   gDevTools.showToolbox(target, toolId2).then(function (toolbox) {
     is(toolbox.target, target, "toolbox target is correct");
     is(toolbox.target.tab, gBrowser.selectedTab, "targeted tab is correct");
@@ -147,30 +147,30 @@ var continueTests = async function (tool
   ok(toolDefinitions.has(toolId2), "The tool is in gDevTools");
 
   let toolDefinition = toolDefinitions.get(toolId2);
   is(toolDefinition.id, toolId2, "toolDefinition id is correct");
 
   info("Testing toolbox tool-unregistered event");
   let toolSelected = toolbox.once("select");
   let unregisteredTool = await new Promise(resolve => {
-    toolbox.once("tool-unregistered", (e, id) => resolve(id));
+    toolbox.once("tool-unregistered", id => resolve(id));
     gDevTools.unregisterTool(toolId2);
   });
   await toolSelected;
 
   is(unregisteredTool, toolId2, "Event returns correct id");
   ok(!toolbox.isToolRegistered(toolId2),
     "Toolbox: The tool is not registered");
   ok(!gDevTools.getToolDefinitionMap().has(toolId2),
     "The tool is no longer registered");
 
   info("Testing toolbox tool-registered event");
   let registeredTool = await new Promise(resolve => {
-    toolbox.once("tool-registered", (e, id) => resolve(id));
+    toolbox.once("tool-registered", id => resolve(id));
     gDevTools.registerTool(toolDefinition);
   });
 
   is(registeredTool, toolId2, "Event returns correct id");
   ok(toolbox.isToolRegistered(toolId2),
     "Toolbox: The tool is registered");
   ok(gDevTools.getToolDefinitionMap().has(toolId2),
     "The tool is registered");
--- a/devtools/client/framework/test/browser_devtools_api_destroy.js
+++ b/devtools/client/framework/test/browser_devtools_api_destroy.js
@@ -34,30 +34,30 @@ function runTests(aTab) {
 
   let collectedEvents = [];
 
   let target = TargetFactory.forTab(aTab);
   gDevTools.showToolbox(target, toolDefinition.id).then(function (toolbox) {
     let panel = toolbox.getPanel(toolDefinition.id);
     ok(panel, "Tool open");
 
-    gDevTools.once("toolbox-destroy", (event, toolbox, iframe) => {
-      collectedEvents.push(event);
+    gDevTools.once("toolbox-destroy", (toolbox, iframe) => {
+      collectedEvents.push("toolbox-destroy");
     });
 
-    gDevTools.once(toolDefinition.id + "-destroy", (event, toolbox, iframe) => {
-      collectedEvents.push("gDevTools-" + event);
+    gDevTools.once(toolDefinition.id + "-destroy", (toolbox, iframe) => {
+      collectedEvents.push("gDevTools-" + toolDefinition.id + "-destroy");
     });
 
-    toolbox.once("destroy", (event) => {
-      collectedEvents.push(event);
+    toolbox.once("destroy", () => {
+      collectedEvents.push("destroy");
     });
 
-    toolbox.once(toolDefinition.id + "-destroy", (event) => {
-      collectedEvents.push("toolbox-" + event);
+    toolbox.once(toolDefinition.id + "-destroy", () => {
+      collectedEvents.push("toolbox-" + toolDefinition.id + "-destroy");
     });
 
     toolbox.destroy().then(function () {
       is(collectedEvents.join(":"),
         "toolbox-destroy:destroy:gDevTools-testTool-destroy:toolbox-testTool-destroy",
         "Found the right amount of collected events.");
 
       gDevTools.unregisterTool(toolDefinition.id);
--- a/devtools/client/framework/test/browser_target_events.js
+++ b/devtools/client/framework/test/browser_target_events.js
@@ -26,29 +26,30 @@ function onHidden() {
   ok(true, "Hidden event received");
   target.once("visible", onVisible);
   gBrowser.removeCurrentTab();
 }
 
 function onVisible() {
   ok(true, "Visible event received");
   target.once("will-navigate", onWillNavigate);
-  let mm = loadFrameScriptUtils();
-  mm.sendAsyncMessage("devtools:test:navigate", { location: "data:text/html,<meta charset='utf8'/>test navigation" });
+
+  ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    content.location = "data:text/html,<meta charset='utf8'/>test navigation";
+  });
 }
 
-function onWillNavigate(event, request) {
+async function onWillNavigate() {
   ok(true, "will-navigate event received");
-  // Wait for navigation handling to complete before removing the tab, in order
-  // to avoid triggering assertions.
-  target.once("navigate", executeSoon.bind(null, onNavigate));
+  target.on("navigate", onNavigate);
 }
 
 function onNavigate() {
   ok(true, "navigate event received");
+  target.off("navigate", onNavigate);
   target.once("close", onClose);
   gBrowser.removeCurrentTab();
 }
 
 function onClose() {
   ok(true, "close event received");
 
   target = null;
--- a/devtools/client/framework/test/browser_toolbox_dynamic_registration.js
+++ b/devtools/client/framework/test/browser_toolbox_dynamic_registration.js
@@ -24,17 +24,17 @@ function testRegister(aToolbox)
     id: "test-tool",
     label: "Test Tool",
     inMenu: true,
     isTargetSupported: () => true,
     build: function () {},
   });
 }
 
-function toolRegistered(event, toolId)
+function toolRegistered(toolId)
 {
   is(toolId, "test-tool", "tool-registered event handler sent tool id");
 
   ok(gDevTools.getToolDefinitionMap().has(toolId), "tool added to map");
 
   // test that it appeared in the UI
   let doc = toolbox.doc;
   let tab = doc.getElementById("toolbox-tab-" + toolId);
@@ -63,17 +63,17 @@ function getAllBrowserWindows() {
 
 function testUnregister()
 {
   gDevTools.once("tool-unregistered", toolUnregistered);
 
   gDevTools.unregisterTool("test-tool");
 }
 
-function toolUnregistered(event, toolId)
+function toolUnregistered(toolId)
 {
   is(toolId, "test-tool", "tool-unregistered event handler sent tool id");
 
   ok(!gDevTools.getToolDefinitionMap().has(toolId), "tool removed from map");
 
   // test that it disappeared from the UI
   let doc = toolbox.doc;
   let tab = doc.getElementById("toolbox-tab-" + toolId);
--- a/devtools/client/framework/test/browser_toolbox_options.js
+++ b/devtools/client/framework/test/browser_toolbox_options.js
@@ -263,29 +263,29 @@ async function toggleTool(node) {
       checkRegistered.bind(null, toolId, deferred));
   }
   node.scrollIntoView();
   EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
 
   await deferred.promise;
 }
 
-function checkUnregistered(toolId, deferred, event, data) {
+function checkUnregistered(toolId, deferred, data) {
   if (data == toolId) {
     ok(true, "Correct tool removed");
     // checking tab on the toolbox
     ok(!doc.getElementById("toolbox-tab-" + toolId),
       "Tab removed for " + toolId);
   } else {
     ok(false, "Something went wrong, " + toolId + " was not unregistered");
   }
   deferred.resolve();
 }
 
-function checkRegistered(toolId, deferred, event, data) {
+function checkRegistered(toolId, deferred, data) {
   if (data == toolId) {
     ok(true, "Correct tool added back");
     // checking tab on the toolbox
     let button = doc.getElementById("toolbox-tab-" + toolId);
     ok(button, "Tab added back for " + toolId);
   } else {
     ok(false, "Something went wrong, " + toolId + " was not registered");
   }
--- a/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js
+++ b/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js
@@ -41,17 +41,17 @@ function testPrefsAreRespectedWhenReopen
 }
 
 function testSelectTool(devtoolsToolbox) {
   let deferred = defer();
   info("Selecting the options panel");
 
   toolbox = devtoolsToolbox;
   doc = toolbox.doc;
-  toolbox.once("options-selected", (event, tool) => {
+  toolbox.once("options-selected", tool => {
     ok(true, "Options panel selected via selectTool method");
     panelWin = tool.panelWin;
     deferred.resolve();
   });
   toolbox.selectTool("options");
 
   return deferred.promise;
 }
--- a/devtools/client/framework/test/browser_toolbox_theme_registration.js
+++ b/devtools/client/framework/test/browser_toolbox_theme_registration.js
@@ -14,17 +14,17 @@ const LIGHT_THEME_NAME = "light";
 var toolbox;
 
 add_task(async function themeRegistration() {
   let tab = await addTab("data:text/html,test");
   let target = TargetFactory.forTab(tab);
   toolbox = await gDevTools.showToolbox(target, "options");
 
   let themeId = await new Promise(resolve => {
-    gDevTools.once("theme-registered", (e, registeredThemeId) => {
+    gDevTools.once("theme-registered", registeredThemeId => {
       resolve(registeredThemeId);
     });
 
     gDevTools.registerTheme({
       id: TEST_THEME_NAME,
       label: "Test theme",
       stylesheets: [CHROME_URL + "doc_theme.css"],
       classList: ["theme-test"],
@@ -39,17 +39,17 @@ add_task(async function themeRegistratio
 add_task(async function themeInOptionsPanel() {
   let panelWin = toolbox.getCurrentPanel().panelWin;
   let doc = panelWin.frameElement.contentDocument;
   let themeBox = doc.getElementById("devtools-theme-box");
   let testThemeOption = themeBox.querySelector(
     `input[type=radio][value=${TEST_THEME_NAME}]`);
   let eventsRecorded = [];
 
-  function onThemeChanged(event, theme) {
+  function onThemeChanged(theme) {
     eventsRecorded.push(theme);
   }
   gDevTools.on("theme-changed", onThemeChanged);
 
   ok(testThemeOption, "new theme exists in the Options panel");
 
   let lightThemeOption = themeBox.querySelector(
     `input[type=radio][value=${LIGHT_THEME_NAME}]`);
@@ -96,17 +96,17 @@ add_task(async function themeInOptionsPa
 });
 
 add_task(async function themeUnregistration() {
   let panelWin = toolbox.getCurrentPanel().panelWin;
   let onUnRegisteredTheme = once(gDevTools, "theme-unregistered");
   let onThemeSwitchComplete = once(panelWin, "theme-switch-complete");
   let eventsRecorded = [];
 
-  function onThemeChanged(event, theme) {
+  function onThemeChanged(theme) {
     eventsRecorded.push(theme);
   }
   gDevTools.on("theme-changed", onThemeChanged);
 
   gDevTools.unregisterTheme(TEST_THEME_NAME);
   await onUnRegisteredTheme;
   await onThemeSwitchComplete;
 
--- a/devtools/client/framework/test/browser_toolbox_toggle.js
+++ b/devtools/client/framework/test/browser_toolbox_toggle.js
@@ -44,23 +44,23 @@ async function testToggle(key, modifiers
 
 async function testToggleDockedToolbox(tab, key, modifiers) {
   let toolbox = getToolboxForTab(tab);
 
   isnot(toolbox.hostType, Toolbox.HostType.WINDOW,
     "Toolbox is docked in the main window");
 
   info("verify docked toolbox is destroyed when using toggle key");
-  let onToolboxDestroyed = once(gDevTools, "toolbox-destroyed");
+  let onToolboxDestroyed = gDevTools.once("toolbox-destroyed");
   EventUtils.synthesizeKey(key, modifiers);
   await onToolboxDestroyed;
   ok(true, "Docked toolbox is destroyed when using a toggle key");
 
   info("verify new toolbox is created when using toggle key");
-  let onToolboxReady = once(gDevTools, "toolbox-ready");
+  let onToolboxReady = gDevTools.once("toolbox-ready");
   EventUtils.synthesizeKey(key, modifiers);
   await onToolboxReady;
   ok(true, "Toolbox is created by using when toggle key");
 }
 
 async function testToggleDetachedToolbox(tab, key, modifiers) {
   let toolbox = getToolboxForTab(tab);
 
@@ -86,23 +86,22 @@ async function testToggleDetachedToolbox
   let onToolboxWindowFocus = once(toolboxWindow, "focus", true);
   EventUtils.synthesizeKey(key, modifiers);
   await onToolboxWindowFocus;
   ok(true, "Toolbox focused and not destroyed");
 
   info("Verify windowed toolbox is destroyed when using toggle key from its " +
     "own window");
 
-  let onToolboxDestroyed = once(gDevTools, "toolbox-destroyed");
+  let onToolboxDestroyed = gDevTools.once("toolbox-destroyed");
   EventUtils.synthesizeKey(key, modifiers, toolboxWindow);
   await onToolboxDestroyed;
   ok(true, "Toolbox destroyed");
 }
 
 function getToolboxForTab(tab) {
   return gDevTools.getToolbox(TargetFactory.forTab(tab));
 }
 
 function cleanup() {
-  Services.prefs.setCharPref("devtools.toolbox.host",
-    Toolbox.HostType.BOTTOM);
+  Services.prefs.setCharPref("devtools.toolbox.host", Toolbox.HostType.BOTTOM);
   gBrowser.removeCurrentTab();
 }
--- a/devtools/client/framework/test/browser_toolbox_transport_events.js
+++ b/devtools/client/framework/test/browser_toolbox_transport_events.js
@@ -58,53 +58,53 @@ function testPackets(sent, received) {
     "The first sent packet is for list of tabs");
 }
 
 // Listen to the transport object that is associated with the
 // default Toolbox debugger client
 var sent1 = [];
 var received1 = [];
 
-function send1(eventId, packet) {
+function send1(packet) {
   sent1.push(packet);
 }
 
-function onPacket1(eventId, packet) {
+function onPacket1(packet) {
   received1.push(packet);
 }
 
-function onToolboxCreated(eventId, toolbox) {
+function onToolboxCreated(toolbox) {
   toolbox.target.makeRemote();
   let client = toolbox.target.client;
   let transport = client._transport;
 
   transport.on("send", send1);
   transport.on("packet", onPacket1);
 
-  client.addOneTimeListener("closed", event => {
+  client.addOneTimeListener("closed", () => {
     transport.off("send", send1);
     transport.off("packet", onPacket1);
   });
 }
 
 // Listen to all debugger client object protocols.
 var sent2 = [];
 var received2 = [];
 
-function send2(eventId, packet) {
+function send2(packet) {
   sent2.push(packet);
 }
 
-function onPacket2(eventId, packet) {
+function onPacket2(packet) {
   received2.push(packet);
 }
 
 function onDebuggerClientConnect(client) {
   let transport = client._transport;
 
   transport.on("send", send2);
   transport.on("packet", onPacket2);
 
-  client.addOneTimeListener("closed", event => {
+  client.addOneTimeListener("closed", () => {
     transport.off("send", send2);
     transport.off("packet", onPacket2);
   });
 }
--- a/devtools/client/framework/test/browser_toolbox_window_shortcuts.js
+++ b/devtools/client/framework/test/browser_toolbox_window_shortcuts.js
@@ -63,17 +63,17 @@ function testShortcuts(aToolbox, aIndex)
     shiftKey: toolModifiers.includes("shift"),
   };
   idIndex = aIndex;
   info("Testing shortcut for tool " + aIndex + ":" + toolIDs[aIndex] +
        " using key " + key);
   EventUtils.synthesizeKey(key, modifiers, toolbox.win.parent);
 }
 
-function selectCB(event, id) {
+function selectCB(id) {
   info("toolbox-select event from " + id);
 
   is(toolIDs.indexOf(id), idIndex,
      "Correct tool is selected on pressing the shortcut for " + id);
 
   testShortcuts(toolbox, idIndex + 1);
 }
 
--- a/devtools/client/framework/test/head.js
+++ b/devtools/client/framework/test/head.js
@@ -3,17 +3,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* import-globals-from ../../shared/test/shared-head.js */
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", this);
 
-const EventEmitter = require("devtools/shared/old-event-emitter");
+const EventEmitter = require("devtools/shared/event-emitter");
 
 function toggleAllTools(state) {
   for (let [, tool] of gDevTools._tools) {
     if (!tool.visibilityswitch) {
       continue;
     }
     if (state) {
       Services.prefs.setBoolPref(tool.visibilityswitch, true);
@@ -175,17 +175,17 @@ function createScript(url) {
  *        the url to wait for
  * @return {Promise} a promise that is resolved when the source is loaded
  */
 function waitForSourceLoad(toolbox, url) {
   info(`Waiting for source ${url} to be available...`);
   return new Promise(resolve => {
     let target = toolbox.target;
 
-    function sourceHandler(_, sourceEvent) {
+    function sourceHandler(sourceEvent) {
       if (sourceEvent && sourceEvent.source && sourceEvent.source.url === url) {
         resolve();
         target.off("source-updated", sourceHandler);
       }
     }
 
     target.on("source-updated", sourceHandler);
   });
--- a/devtools/client/framework/toolbox-hosts.js
+++ b/devtools/client/framework/toolbox-hosts.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const EventEmitter = require("devtools/shared/old-event-emitter");
+const EventEmitter = require("devtools/shared/event-emitter");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 const Services = require("Services");
 const {DOMHelpers} = require("resource://devtools/client/shared/DOMHelpers.jsm");
 
 loader.lazyRequireGetter(this, "system", "devtools/shared/system");
 loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
 
--- a/devtools/client/framework/toolbox-options.js
+++ b/devtools/client/framework/toolbox-options.js
@@ -65,17 +65,17 @@ function OptionsPanel(iframeWindow, tool
   this._themeRegistered = this._themeRegistered.bind(this);
   this._themeUnregistered = this._themeUnregistered.bind(this);
   this._disableJSClicked = this._disableJSClicked.bind(this);
 
   this.disableJSNode = this.panelDoc.getElementById("devtools-disable-javascript");
 
   this._addListeners();
 
-  const EventEmitter = require("devtools/shared/old-event-emitter");
+  const EventEmitter = require("devtools/shared/event-emitter");
   EventEmitter.decorate(this);
 }
 
 OptionsPanel.prototype = {
 
   get target() {
     return this.toolbox.target;
   },
@@ -121,21 +121,21 @@ OptionsPanel.prototype = {
       cbx.checked = cacheDisabled;
     } else if (prefName === "devtools.theme") {
       this.updateCurrentTheme();
     } else if (prefName === "devtools.source-map.client-service.enabled") {
       this.updateSourceMapPref();
     }
   },
 
-  _themeRegistered: function(event, themeId) {
+  _themeRegistered: function(themeId) {
     this.setupThemeList();
   },
 
-  _themeUnregistered: function(event, theme) {
+  _themeUnregistered: function(theme) {
     let themeBox = this.panelDoc.getElementById("devtools-theme-box");
     let themeInput = themeBox.querySelector(`[value=${theme.id}]`);
 
     if (themeInput) {
       themeInput.parentNode.remove();
     }
   },
 
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -16,17 +16,17 @@ const CURRENT_THEME_SCALAR = "devtools.c
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 var {Ci, Cc} = require("chrome");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var Services = require("Services");
 var ChromeUtils = require("ChromeUtils");
 var {gDevTools} = require("devtools/client/framework/devtools");
-var EventEmitter = require("devtools/shared/old-event-emitter");
+var EventEmitter = require("devtools/shared/event-emitter");
 var Telemetry = require("devtools/client/shared/telemetry");
 var { attachThread, detachThread } = require("./attach-thread");
 var Menu = require("devtools/client/framework/menu");
 var MenuItem = require("devtools/client/framework/menu-item");
 var { DOMHelpers } = require("resource://devtools/client/shared/DOMHelpers.jsm");
 const { KeyCodes } = require("devtools/client/shared/keycodes");
 var Startup = Cc["@mozilla.org/devtools/startup-clh;1"].getService(Ci.nsISupports)
   .wrappedJSObject;
@@ -303,17 +303,17 @@ Toolbox.prototype = {
    *          A promise that resolves once the panel is ready.
    */
   getPanelWhenReady: function(id) {
     let deferred = defer();
     let panel = this.getPanel(id);
     if (panel) {
       deferred.resolve(panel);
     } else {
-      this.on(id + "-ready", (e, initializedPanel) => {
+      this.on(id + "-ready", initializedPanel => {
         deferred.resolve(initializedPanel);
       });
     }
 
     return deferred.promise;
   },
 
   /**
@@ -2178,17 +2178,17 @@ Toolbox.prototype = {
       // it can be either an addon or browser toolbox actor
       return promise.resolve();
     }
     let packet = {
       to: this._target.form.actor,
       type: "listFrames"
     };
     return this._target.client.request(packet, resp => {
-      this._updateFrames(null, { frames: resp.frames });
+      this._updateFrames({ frames: resp.frames });
     });
   },
 
   /**
    * Show a drop down menu that allows the user to switch frames.
    */
   showFramesMenu: async function(event) {
     let menu = new Menu();
@@ -2303,17 +2303,17 @@ Toolbox.prototype = {
    * frames {Array}: list of frames. Every frame can have:
    *                 id {Number}: frame ID
    *                 url {String}: frame URL
    *                 title {String}: frame title
    *                 destroy {Boolean}: Set to true if destroyed
    *                 parentID {Number}: ID of the parent frame (not set
    *                                    for top level window)
    */
-  _updateFrames: function(event, data) {
+  _updateFrames: function(data) {
     if (!Services.prefs.getBoolPref("devtools.command-button-frames.enabled")) {
       return;
     }
 
     // We may receive this event before the toolbox is ready.
     if (!this.isReady) {
       return;
     }
@@ -2497,22 +2497,20 @@ Toolbox.prototype = {
       if (key) {
         key.remove();
       }
     }
   },
 
   /**
    * Handler for the tool-registered event.
-   * @param  {string} event
-   *         Name of the event ("tool-registered")
    * @param  {string} toolId
    *         Id of the tool that was registered
    */
-  _toolRegistered: function(event, toolId) {
+  _toolRegistered: function(toolId) {
     // Tools can either be in the global devtools, or added to this specific toolbox
     // as an additional tool.
     let definition = gDevTools.getToolDefinition(toolId);
     let isAdditionalTool = false;
     if (!definition) {
       definition = this.additionalToolDefinitions.get(toolId);
       isAdditionalTool = true;
     }
@@ -2529,22 +2527,20 @@ Toolbox.prototype = {
       // Emit the event so tools can listen to it from the toolbox level
       // instead of gDevTools.
       this.emit("tool-registered", toolId);
     }
   },
 
   /**
    * Handler for the tool-unregistered event.
-   * @param  {string} event
-   *         Name of the event ("tool-unregistered")
    * @param  {string} toolId
    *         id of the tool that was unregistered
    */
-  _toolUnregistered: function(event, toolId) {
+  _toolUnregistered: function(toolId) {
     this.unloadTool(toolId);
     // Emit the event so tools can listen to it from the toolbox level
     // instead of gDevTools
     this.emit("tool-unregistered", toolId);
   },
 
   /**
    * Initialize the inspector/walker/selection/highlighter fronts.
@@ -2567,24 +2563,24 @@ Toolbox.prototype = {
           let autohide = !flags.testing;
           this._highlighter = await this._inspector.getHighlighter(autohide);
         }
       }.bind(this))();
     }
     return this._initInspector;
   },
 
-  _onNewSelectedNodeFront: function(evt) {
+  _onNewSelectedNodeFront: function() {
     // Emit a "selection-changed" event when the toolbox.selection has been set
     // to a new node (or cleared). Currently used in the WebExtensions APIs (to
     // provide the `devtools.panels.elements.onSelectionChanged` event).
     this.emit("selection-changed");
   },
 
-  _onInspectObject: function(evt, packet) {
+  _onInspectObject: function(packet) {
     this.inspectObjectActor(packet.objectActor, packet.inspectFromAnnotation);
   },
 
   inspectObjectActor: async function(objectActor, inspectFromAnnotation) {
     if (objectActor.preview &&
         objectActor.preview.nodeType === domNodeConstants.ELEMENT_NODE) {
       // Open the inspector and select the DOM Element.
       await this.loadTool("inspector");
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -283,20 +283,20 @@ class AnimationInspector {
   }
 
   onElementPickerStopped() {
     this.inspector.store.dispatch(updateElementPickerEnabled(false));
   }
 
   onSidebarSelect() {
     this.update();
-    this.onSidebarResized(null, this.inspector.getSidebarSize());
+    this.onSidebarResized(this.inspector.getSidebarSize());
   }
 
-  onSidebarResized(type, size) {
+  onSidebarResized(size) {
     if (!this.isPanelVisible()) {
       return;
     }
 
     this.inspector.store.dispatch(updateSidebarSize(size));
   }
 
   removeAnimationsCurrentTimeListener(listener) {
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -1412,17 +1412,17 @@ function ComputedViewTool(inspector, win
     this.inspector.pageStyle);
 
   this.onSelected = this.onSelected.bind(this);
   this.refresh = this.refresh.bind(this);
   this.onPanelSelected = this.onPanelSelected.bind(this);
   this.onMutations = this.onMutations.bind(this);
   this.onResized = this.onResized.bind(this);
 
-  this.inspector.selection.on("detached-front", this.onSelected);
+  this.inspector.selection.on("detached-front", this.onDetachedFront);
   this.inspector.selection.on("new-node-front", this.onSelected);
   this.inspector.selection.on("pseudoclass", this.refresh);
   this.inspector.sidebar.on("computedview-selected", this.onPanelSelected);
   this.inspector.pageStyle.on("stylesheet-updated", this.refresh);
   this.inspector.walker.on("mutations", this.onMutations);
   this.inspector.walker.on("resize", this.onResized);
 
   this.computedView.selectElement(null);
@@ -1433,17 +1433,21 @@ function ComputedViewTool(inspector, win
 ComputedViewTool.prototype = {
   isSidebarActive: function() {
     if (!this.computedView) {
       return false;
     }
     return this.inspector.sidebar.getCurrentTabID() == "computedview";
   },
 
-  onSelected: function(event) {
+  onDetachedFront: function() {
+    this.onSelected(false);
+  },
+
+  onSelected: function(selectElement = true) {
     // Ignore the event if the view has been destroyed, or if it's inactive.
     // But only if the current selection isn't null. If it's been set to null,
     // let the update go through as this is needed to empty the view on
     // navigation.
     if (!this.computedView) {
       return;
     }
 
@@ -1456,17 +1460,17 @@ ComputedViewTool.prototype = {
     this.computedView.setPageStyle(this.inspector.pageStyle);
 
     if (!this.inspector.selection.isConnected() ||
         !this.inspector.selection.isElementNode()) {
       this.computedView.selectElement(null);
       return;
     }
 
-    if (!event || event == "new-node-front") {
+    if (selectElement) {
       let done = this.inspector.updating("computed-view");
       this.computedView.selectElement(this.inspector.selection.nodeFront).then(() => {
         done();
       });
     }
   },
 
   refresh: function() {
@@ -1506,17 +1510,17 @@ ComputedViewTool.prototype = {
   },
 
   destroy: function() {
     this.inspector.walker.off("mutations", this.onMutations);
     this.inspector.walker.off("resize", this.onResized);
     this.inspector.sidebar.off("computedview-selected", this.refresh);
     this.inspector.selection.off("pseudoclass", this.refresh);
     this.inspector.selection.off("new-node-front", this.onSelected);
-    this.inspector.selection.off("detached-front", this.onSelected);
+    this.inspector.selection.off("detached-front", this.onDetachedFront);
     this.inspector.sidebar.off("computedview-selected", this.onPanelSelected);
     if (this.inspector.pageStyle) {
       this.inspector.pageStyle.off("stylesheet-updated", this.refresh);
     }
 
     this.computedView.destroy();
 
     this.computedView = this.document = this.inspector = null;
--- a/devtools/client/inspector/fonts/fonts.js
+++ b/devtools/client/inspector/fonts/fonts.js
@@ -148,17 +148,17 @@ class FontInspector {
   onPreviewFonts(value) {
     this.store.dispatch(updatePreviewText(value));
     this.update();
   }
 
   /**
    * Handler for the "theme-switched" event.
    */
-  onThemeChanged(event, frame) {
+  onThemeChanged(frame) {
     if (frame === this.document.defaultView) {
       this.update();
     }
   }
 
   async update() {
     // Stop refreshing if the inspector or store is already destroyed.
     if (!this.inspector || !this.store) {
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1153,17 +1153,17 @@ Inspector.prototype = {
            !selection.isAnonymousNode() &&
            !invalidTagNames.includes(
             selection.nodeFront.nodeName.toLowerCase());
   },
 
   /**
    * When a new node is selected.
    */
-  onNewSelection: function(event, value, reason) {
+  onNewSelection: function(value, reason) {
     if (reason === "selection-destroy") {
       return;
     }
 
     // Wait for all the known tools to finish updating and then let the
     // client know.
     let selection = this.selection.nodeFront;
 
@@ -1248,17 +1248,17 @@ Inspector.prototype = {
     this._updateProgress = null;
   },
 
   /**
    * When a node is deleted, select its parent node or the defaultNode if no
    * parent is found (may happen when deleting an iframe inside which the
    * node was selected).
    */
-  onDetached: function(event, parentNode) {
+  onDetached: function(parentNode) {
     this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode));
     this.selection.setNodeFront(parentNode ? parentNode : this._defaultNode, "detached");
   },
 
   /**
    * Destroy the inspector.
    */
   destroy: function() {
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -176,17 +176,17 @@ MarkupView.prototype = {
     this.imagePreviewTooltip.startTogglingOnHover(this._elt,
       this._isImagePreviewTarget);
   },
 
   _disableImagePreviewTooltip: function() {
     this.imagePreviewTooltip.stopTogglingOnHover();
   },
 
-  _onToolboxPickerHover: function(event, nodeFront) {
+  _onToolboxPickerHover: function(nodeFront) {
     this.showNode(nodeFront).then(() => {
       this._showContainerAsHovered(nodeFront);
     }, console.error);
   },
 
   /**
    * If the element picker gets canceled, make sure and re-center the view on the
    * current selected element.
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -1630,17 +1630,17 @@ function RuleViewTool(inspector, window)
   this.refresh = this.refresh.bind(this);
   this.onMutations = this.onMutations.bind(this);
   this.onPanelSelected = this.onPanelSelected.bind(this);
   this.onResized = this.onResized.bind(this);
   this.onSelected = this.onSelected.bind(this);
   this.onViewRefreshed = this.onViewRefreshed.bind(this);
 
   this.view.on("ruleview-refreshed", this.onViewRefreshed);
-  this.inspector.selection.on("detached-front", this.onSelected);
+  this.inspector.selection.on("detached-front", this.onDetachedFront);
   this.inspector.selection.on("new-node-front", this.onSelected);
   this.inspector.selection.on("pseudoclass", this.refresh);
   this.inspector.target.on("navigate", this.clearUserProperties);
   this.inspector.ruleViewSideBar.on("ruleview-selected", this.onPanelSelected);
   this.inspector.sidebar.on("ruleview-selected", this.onPanelSelected);
   this.inspector.pageStyle.on("stylesheet-updated", this.refresh);
   this.inspector.walker.on("mutations", this.onMutations);
   this.inspector.walker.on("resize", this.onResized);
@@ -1653,17 +1653,21 @@ RuleViewTool.prototype = {
     if (!this.view) {
       return false;
     }
 
     return this.inspector.isSplitRuleViewEnabled ?
       true : this.inspector.sidebar.getCurrentTabID() == "ruleview";
   },
 
-  onSelected: function(event) {
+  onDetachedFront: function() {
+    this.onSelected(false);
+  },
+
+  onSelected: function(selectElement = true) {
     // Ignore the event if the view has been destroyed, or if it's inactive.
     // But only if the current selection isn't null. If it's been set to null,
     // let the update go through as this is needed to empty the view on
     // navigation.
     if (!this.view) {
       return;
     }
 
@@ -1676,17 +1680,17 @@ RuleViewTool.prototype = {
     this.view.setPageStyle(this.inspector.pageStyle);
 
     if (!this.inspector.selection.isConnected() ||
         !this.inspector.selection.isElementNode()) {
       this.view.selectElement(null);
       return;
     }
 
-    if (!event || event == "new-node-front") {
+    if (selectElement) {
       let done = this.inspector.updating("rule-view");
       this.view.selectElement(this.inspector.selection.nodeFront)
         .then(done, done);
     }
   },
 
   refresh: function() {
     if (this.isSidebarActive()) {
@@ -1732,17 +1736,17 @@ RuleViewTool.prototype = {
    */
   onResized: function() {
     this.refresh();
   },
 
   destroy: function() {
     this.inspector.walker.off("mutations", this.onMutations);
     this.inspector.walker.off("resize", this.onResized);
-    this.inspector.selection.off("detached-front", this.onSelected);
+    this.inspector.selection.off("detached-front", this.onDetachedFront);
     this.inspector.selection.off("pseudoclass", this.refresh);
     this.inspector.selection.off("new-node-front", this.onSelected);
     this.inspector.target.off("navigate", this.clearUserProperties);
     this.inspector.sidebar.off("ruleview-selected", this.onPanelSelected);
     if (this.inspector.pageStyle) {
       this.inspector.pageStyle.off("stylesheet-updated", this.refresh);
     }
 
--- a/devtools/client/shadereditor/shadereditor.js
+++ b/devtools/client/shadereditor/shadereditor.js
@@ -86,32 +86,33 @@ function shutdownShaderEditor() {
  */
 var EventsHandler = {
   /**
    * Listen for events emitted by the current tab target.
    */
   initialize: function () {
     this._onHostChanged = this._onHostChanged.bind(this);
     this._onTabNavigated = this._onTabNavigated.bind(this);
+    this._onTabWillNavigate = this._onTabWillNavigate.bind(this);
     this._onProgramLinked = this._onProgramLinked.bind(this);
     this._onProgramsAdded = this._onProgramsAdded.bind(this);
     gToolbox.on("host-changed", this._onHostChanged);
-    gTarget.on("will-navigate", this._onTabNavigated);
+    gTarget.on("will-navigate", this._onTabWillNavigate);
     gTarget.on("navigate", this._onTabNavigated);
     gFront.on("program-linked", this._onProgramLinked);
     this.reloadButton = $("#requests-menu-reload-notice-button");
     this.reloadButton.addEventListener("command", this._onReloadCommand);
   },
 
   /**
    * Remove events emitted by the current tab target.
    */
   destroy: function () {
     gToolbox.off("host-changed", this._onHostChanged);
-    gTarget.off("will-navigate", this._onTabNavigated);
+    gTarget.off("will-navigate", this._onTabWillNavigate);
     gTarget.off("navigate", this._onTabNavigated);
     gFront.off("program-linked", this._onProgramLinked);
     this.reloadButton.removeEventListener("command", this._onReloadCommand);
   },
 
   /**
    * Handles a command event on reload button
    */
@@ -123,53 +124,47 @@ var EventsHandler = {
    * Handles a host change event on the parent toolbox.
    */
   _onHostChanged: function () {
     if (gToolbox.hostType == "side") {
       $("#shaders-pane").removeAttribute("height");
     }
   },
 
+  _onTabWillNavigate: function({isFrameSwitching}) {
+    // Make sure the backend is prepared to handle WebGL contexts.
+    if (!isFrameSwitching) {
+      gFront.setup({ reload: false });
+    }
+
+    // Reset UI.
+    ShadersListView.empty();
+    // When switching to an iframe, ensure displaying the reload button.
+    // As the document has already been loaded without being hooked.
+    if (isFrameSwitching) {
+      $("#reload-notice").hidden = false;
+      $("#waiting-notice").hidden = true;
+    } else {
+      $("#reload-notice").hidden = true;
+      $("#waiting-notice").hidden = false;
+    }
+
+    $("#content").hidden = true;
+    window.emit(EVENTS.UI_RESET);
+  },
+
   /**
    * Called for each location change in the debugged tab.
    */
-  _onTabNavigated: function (event, {isFrameSwitching}) {
-    switch (event) {
-      case "will-navigate": {
-        // Make sure the backend is prepared to handle WebGL contexts.
-        if (!isFrameSwitching) {
-          gFront.setup({ reload: false });
-        }
-
-        // Reset UI.
-        ShadersListView.empty();
-        // When switching to an iframe, ensure displaying the reload button.
-        // As the document has already been loaded without being hooked.
-        if (isFrameSwitching) {
-          $("#reload-notice").hidden = false;
-          $("#waiting-notice").hidden = true;
-        } else {
-          $("#reload-notice").hidden = true;
-          $("#waiting-notice").hidden = false;
-        }
-
-        $("#content").hidden = true;
-        window.emit(EVENTS.UI_RESET);
-
-        break;
-      }
-      case "navigate": {
-        // Manually retrieve the list of program actors known to the server,
-        // because the backend won't emit "program-linked" notifications
-        // in the case of a bfcache navigation (since no new programs are
-        // actually linked).
-        gFront.getPrograms().then(this._onProgramsAdded);
-        break;
-      }
-    }
+  _onTabNavigated: function () {
+    // Manually retrieve the list of program actors known to the server,
+    // because the backend won't emit "program-linked" notifications
+    // in the case of a bfcache navigation (since no new programs are
+    // actually linked).
+    gFront.getPrograms().then(this._onProgramsAdded);
   },
 
   /**
    * Called every time a program was linked in the debugged tab.
    */
   _onProgramLinked: function (programActor) {
     this._addProgram(programActor);
     window.emit(EVENTS.NEW_PROGRAM);
--- a/devtools/client/shared/test/shared-head.js
+++ b/devtools/client/shared/test/shared-head.js
@@ -261,17 +261,20 @@ function waitForNEvents(target, eventNam
 
   for (let [add, remove] of [
     ["on", "off"],
     ["addEventListener", "removeEventListener"],
     ["addListener", "removeListener"],
   ]) {
     if ((add in target) && (remove in target)) {
       target[add](eventName, function onEvent(...aArgs) {
-        info("Got event: '" + eventName + "' on " + target + ".");
+        if (typeof info === "function") {
+          info("Got event: '" + eventName + "' on " + target + ".");
+        }
+
         if (++count == numTimes) {
           target[remove](eventName, onEvent, useCapture);
           deferred.resolve.apply(deferred, aArgs);
         }
       }, useCapture);
       break;
     }
   }
--- a/devtools/client/webaudioeditor/controller.js
+++ b/devtools/client/webaudioeditor/controller.js
@@ -39,21 +39,20 @@ function shutdownWebAudioEditor() {
 /**
  * Functions handling target-related lifetime events.
  */
 var WebAudioEditorController = {
   /**
    * Listen for events emitted by the current tab target.
    */
   async initialize() {
-    this._onTabNavigated = this._onTabNavigated.bind(this);
+    this._onTabWillNavigate = this._onTabWillNavigate.bind(this);
     this._onThemeChange = this._onThemeChange.bind(this);
 
-    gTarget.on("will-navigate", this._onTabNavigated);
-    gTarget.on("navigate", this._onTabNavigated);
+    gTarget.on("will-navigate", this._onTabWillNavigate);
     gFront.on("start-context", this._onStartContext);
     gFront.on("create-node", this._onCreateNode);
     gFront.on("connect-node", this._onConnectNode);
     gFront.on("connect-param", this._onConnectParam);
     gFront.on("disconnect-node", this._onDisconnectNode);
     gFront.on("change-param", this._onChangeParam);
     gFront.on("destroy-node", this._onDestroyNode);
 
@@ -80,18 +79,17 @@ var WebAudioEditorController = {
     // globals.
     gFront.setup({ reload: false });
   },
 
   /**
    * Remove events emitted by the current tab target.
    */
   destroy: function () {
-    gTarget.off("will-navigate", this._onTabNavigated);
-    gTarget.off("navigate", this._onTabNavigated);
+    gTarget.off("will-navigate", this._onTabWillNavigate);
     gFront.off("start-context", this._onStartContext);
     gFront.off("create-node", this._onCreateNode);
     gFront.off("connect-node", this._onConnectNode);
     gFront.off("connect-param", this._onConnectParam);
     gFront.off("disconnect-node", this._onDisconnectNode);
     gFront.off("change-param", this._onChangeParam);
     gFront.off("destroy-node", this._onDestroyNode);
     this._prefObserver.off("devtools.theme", this._onThemeChange);
@@ -137,47 +135,37 @@ var WebAudioEditorController = {
   _onThemeChange: function () {
     let newValue = Services.prefs.getCharPref("devtools.theme");
     window.emit(EVENTS.THEME_CHANGE, newValue);
   },
 
   /**
    * Called for each location change in the debugged tab.
    */
-  async _onTabNavigated(event, {isFrameSwitching}) {
-    switch (event) {
-      case "will-navigate": {
-        // Clear out current UI.
-        this.reset();
+  _onTabWillNavigate: function({isFrameSwitching}) {
+    // Clear out current UI.
+    this.reset();
 
-        // When switching to an iframe, ensure displaying the reload button.
-        // As the document has already been loaded without being hooked.
-        if (isFrameSwitching) {
-          $("#reload-notice").hidden = false;
-          $("#waiting-notice").hidden = true;
-        } else {
-          // Otherwise, we are loading a new top level document,
-          // so we don't need to reload anymore and should receive
-          // new node events.
-          $("#reload-notice").hidden = true;
-          $("#waiting-notice").hidden = false;
-        }
+    // When switching to an iframe, ensure displaying the reload button.
+    // As the document has already been loaded without being hooked.
+    if (isFrameSwitching) {
+      $("#reload-notice").hidden = false;
+      $("#waiting-notice").hidden = true;
+    } else {
+      // Otherwise, we are loading a new top level document,
+      // so we don't need to reload anymore and should receive
+      // new node events.
+      $("#reload-notice").hidden = true;
+      $("#waiting-notice").hidden = false;
+    }
 
-        // Clear out stored audio nodes
-        gAudioNodes.reset();
+    // Clear out stored audio nodes
+    gAudioNodes.reset();
 
-        window.emit(EVENTS.UI_RESET);
-        break;
-      }
-      case "navigate": {
-        // TODO Case of bfcache, needs investigating
-        // bug 994250
-        break;
-      }
-    }
+    window.emit(EVENTS.UI_RESET);
   },
 
   /**
    * Called after the first audio node is created in an audio context,
    * signaling that the audio context is being used.
    */
   _onStartContext: function () {
     $("#reload-notice").hidden = true;
--- a/devtools/client/webconsole/console-commands.js
+++ b/devtools/client/webconsole/console-commands.js
@@ -24,22 +24,21 @@ exports.items = [
       isChecked: function(target) {
         let toolbox = gDevTools.getToolbox(target);
         return !!(toolbox && toolbox.splitConsole);
       },
       onChange: function(target, changeHandler) {
         // Register handlers for when a change event should be fired
         // (which resets the checked state of the button).
         let toolbox = gDevTools.getToolbox(target);
-        let callback = changeHandler.bind(null, "changed", { target: target });
-
         if (!toolbox) {
           return;
         }
 
+        let callback = changeHandler.bind(null, { target });
         toolbox.on("split-console", callback);
         toolbox.once("destroyed", () => {
           toolbox.off("split-console", callback);
         });
       }
     },
     exec: function(args, context) {
       let target = context.environment.target;
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_location_styleeditor_link.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_location_styleeditor_link.js
@@ -23,21 +23,17 @@ add_task(async function() {
 });
 
 async function testViewSource(hud, toolbox, text) {
   info(`Testing message with text "${text}"`);
   let messageNode = await waitFor(() => findMessage(hud, text));
   let frameLinkNode = messageNode.querySelector(".message-location .frame-link");
   ok(frameLinkNode, "The message does have a location link");
 
-  let onStyleEditorSelected = new Promise((resolve) => {
-    toolbox.once("styleeditor-selected", (event, panel) => {
-      resolve(panel);
-    });
-  });
+  let onStyleEditorSelected = toolbox.once("styleeditor-selected");
 
   EventUtils.sendMouseEvent({ type: "click" },
     messageNode.querySelector(".frame-link-filename"));
 
   let panel = await onStyleEditorSelected;
   ok(true, "The style editor is selected when clicking on the location element");
 
   await onStyleEditorReady(panel);
--- a/devtools/client/webconsole/new-webconsole.js
+++ b/devtools/client/webconsole/new-webconsole.js
@@ -310,37 +310,39 @@ NewWebConsoleFrame.prototype = {
   /**
    * Handler for the tabNavigated notification.
    *
    * @param string event
    *        Event name.
    * @param object packet
    *        Notification packet received from the server.
    */
-  handleTabNavigated: async function(event, packet) {
-    if (event == "will-navigate") {
-      if (this.persistLog) {
-        // Add a _type to hit convertCachedPacket.
-        packet._type = true;
-        this.newConsoleOutput.dispatchMessageAdd(packet);
-      } else {
-        this.jsterm.clearOutput(false);
-      }
+  handleTabNavigated: async function(packet) {
+    if (packet.url) {
+      this.onLocationChange(packet.url, packet.title);
+    }
+
+    if (!packet.nativeConsoleAPI) {
+      this.logWarningAboutReplacedAPI();
+    }
+
+    // Wait for completion of any async dispatch before notifying that the console
+    // is fully updated after a page reload
+    await this.newConsoleOutput.waitAsyncDispatches();
+    this.emit("reloaded");
+  },
+
+  handleTabWillNavigate: function(packet) {
+    if (this.persistLog) {
+      // Add a _type to hit convertCachedPacket.
+      packet._type = true;
+      this.newConsoleOutput.dispatchMessageAdd(packet);
+    } else {
+      this.jsterm.clearOutput(false);
     }
 
     if (packet.url) {
       this.onLocationChange(packet.url, packet.title);
     }
-
-    if (event == "navigate" && !packet.nativeConsoleAPI) {
-      this.logWarningAboutReplacedAPI();
-    }
-
-    if (event == "navigate") {
-      // Wait for completion of any async dispatch before notifying that the console
-      // is fully updated after a page reload
-      await this.newConsoleOutput.waitAsyncDispatches();
-      this.emit("reloaded");
-    }
-  },
+  }
 };
 
 exports.NewWebConsoleFrame = NewWebConsoleFrame;
--- a/devtools/client/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
+++ b/devtools/client/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
@@ -41,17 +41,17 @@ function testViewSource() {
     let error2Msg = [...error2Rule.matched][0];
     nodes = [error1Msg.querySelector(".message-location .frame-link"),
              error2Msg.querySelector(".message-location .frame-link")];
     ok(nodes[0], ".frame-link node for the first error");
     ok(nodes[1], ".frame-link node for the second error");
 
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     let toolbox = gDevTools.getToolbox(target);
-    toolbox.once("styleeditor-selected", (event, panel) => {
+    toolbox.once("styleeditor-selected", panel => {
       StyleEditorUI = panel.UI;
       deferred.resolve(panel);
     });
 
     EventUtils.sendMouseEvent({ type: "click" }, nodes[0].querySelector(".frame-link-filename"));
   });
 
   return deferred.promise;
@@ -60,19 +60,17 @@ function testViewSource() {
 function onStyleEditorReady(panel) {
   let deferred = defer();
 
   let win = panel.panelWindow;
   ok(win, "Style Editor Window is defined");
   ok(StyleEditorUI, "Style Editor UI is defined");
 
   function fireEvent(toolbox, href, line) {
-    toolbox.once("styleeditor-selected", function (evt) {
-      info(evt + " event fired");
-
+    toolbox.once("styleeditor-selected", function () {
       checkStyleEditorForSheetAndLine(href, line - 1).then(deferred.resolve);
     });
 
     EventUtils.sendMouseEvent({ type: "click" }, nodes[1].querySelector(".frame-link-filename"));
   }
 
   waitForFocus(function () {
     info("style editor window focused");
--- a/devtools/client/webconsole/webconsole-connection-proxy.js
+++ b/devtools/client/webconsole/webconsole-connection-proxy.js
@@ -30,16 +30,17 @@ function WebConsoleConnectionProxy(webCo
   this._onLogMessage = this._onLogMessage.bind(this);
   this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
   this._onNetworkEvent = this._onNetworkEvent.bind(this);
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
   this._onFileActivity = this._onFileActivity.bind(this);
   this._onReflowActivity = this._onReflowActivity.bind(this);
   this._onServerLogCall = this._onServerLogCall.bind(this);
   this._onTabNavigated = this._onTabNavigated.bind(this);
+  this._onTabWillNavigate = this._onTabWillNavigate.bind(this);
   this._onAttachConsole = this._onAttachConsole.bind(this);
   this._onCachedMessages = this._onCachedMessages.bind(this);
   this._connectionTimeout = this._connectionTimeout.bind(this);
   this._onLastPrivateContextExited =
     this._onLastPrivateContextExited.bind(this);
 }
 
 WebConsoleConnectionProxy.prototype = {
@@ -137,17 +138,17 @@ WebConsoleConnectionProxy.prototype = {
     client.addListener("pageError", this._onPageError);
     client.addListener("consoleAPICall", this._onConsoleAPICall);
     client.addListener("fileActivity", this._onFileActivity);
     client.addListener("reflowActivity", this._onReflowActivity);
     client.addListener("serverLogCall", this._onServerLogCall);
     client.addListener("lastPrivateContextExited",
                        this._onLastPrivateContextExited);
 
-    this.target.on("will-navigate", this._onTabNavigated);
+    this.target.on("will-navigate", this._onTabWillNavigate);
     this.target.on("navigate", this._onTabNavigated);
 
     this._consoleActor = this.target.form.consoleActor;
     if (this.target.isTabActor) {
       let tab = this.target.form;
       this.webConsoleFrame.onLocationChange(tab.url, tab.title);
     }
     this._attachConsole();
@@ -445,31 +446,43 @@ WebConsoleConnectionProxy.prototype = {
    */
   _onLastPrivateContextExited: function(type, packet) {
     if (this.webConsoleFrame && packet.from == this._consoleActor) {
       this.webConsoleFrame.jsterm.clearPrivateMessages();
     }
   },
 
   /**
-   * The "will-navigate" and "navigate" event handlers. We redirect any message
-   * to the UI for displaying.
+   * The "navigate" event handlers. We redirect any message to the UI for displaying.
    *
    * @private
-   * @param string event
-   *        Event type.
    * @param object packet
    *        The message received from the server.
    */
-  _onTabNavigated: function(event, packet) {
+  _onTabNavigated: function(packet) {
     if (!this.webConsoleFrame) {
       return;
     }
 
-    this.webConsoleFrame.handleTabNavigated(event, packet);
+    this.webConsoleFrame.handleTabNavigated(packet);
+  },
+
+  /**
+   * The "will-navigate" event handlers. We redirect any message to the UI for displaying.
+   *
+   * @private
+   * @param object packet
+   *        The message received from the server.
+   */
+  _onTabWillNavigate: function(packet) {
+    if (!this.webConsoleFrame) {
+      return;
+    }
+
+    this.webConsoleFrame.handleTabWillNavigate(packet);
   },
 
   /**
    * Release an object actor.
    *
    * @param string actor
    *        The actor ID to send the request to.
    */
@@ -502,17 +515,17 @@ WebConsoleConnectionProxy.prototype = {
     this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
     this.client.removeListener("fileActivity", this._onFileActivity);
     this.client.removeListener("reflowActivity", this._onReflowActivity);
     this.client.removeListener("serverLogCall", this._onServerLogCall);
     this.client.removeListener("lastPrivateContextExited",
                                this._onLastPrivateContextExited);
     this.webConsoleClient.off("networkEvent", this._onNetworkEvent);
     this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate);
-    this.target.off("will-navigate", this._onTabNavigated);
+    this.target.off("will-navigate", this._onTabWillNavigate);
     this.target.off("navigate", this._onTabNavigated);
 
     this.client = null;
     this.webConsoleClient = null;
     this.target = null;
     this.connected = false;
     this.webConsoleFrame = null;
     this._disconnecter.resolve(null);
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -1868,40 +1868,49 @@ WebConsoleFrame.prototype = {
     if (this.owner.onLocationChange) {
       this.owner.onLocationChange(uri, title);
     }
   },
 
   /**
    * Handler for the tabNavigated notification.
    *
-   * @param string event
-   *        Event name.
    * @param object packet
    *        Notification packet received from the server.
    */
-  handleTabNavigated: function (event, packet) {
-    if (event == "will-navigate") {
-      if (this.persistLog) {
-        let marker = new Messages.NavigationMarker(packet, Date.now());
-        this.output.addMessage(marker);
-      } else {
-        this.jsterm.clearOutput();
-      }
-    }
+  handleTabNavigated: function (packet) {
     if (packet.url) {
       this.onLocationChange(packet.url, packet.title);
     }
 
-    if (event == "navigate" && !packet.nativeConsoleAPI) {
+    if (!packet.nativeConsoleAPI) {
       this.logWarningAboutReplacedAPI();
     }
   },
 
   /**
+   * Handler for the tabNavigated notification.
+   *
+   * @param object packet
+   *        Notification packet received from the server.
+   */
+  handleTabWillNavigate: function (packet) {
+    if (this.persistLog) {
+      let marker = new Messages.NavigationMarker(packet, Date.now());
+      this.output.addMessage(marker);
+    } else {
+      this.jsterm.clearOutput();
+    }
+
+    if (packet.url) {
+      this.onLocationChange(packet.url, packet.title);
+    }
+  },
+
+  /**
    * Output a message node. This filters a node appropriately, then sends it to
    * the output, regrouping and pruning output as necessary.
    *
    * Note: this call is async - the given message node may not be displayed when
    * you call this method.
    *
    * @param integer category
    *        The category of the message you want to output. See the CATEGORY_*
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -90,20 +90,17 @@ skip-if = true # Needs to be updated for
 [browser_perf-recording-actor-01.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-recording-actor-02.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-samples-01.js]
 [browser_perf-samples-02.js]
 [browser_storage_cookies-duplicate-names.js]
 [browser_storage_dynamic_windows.js]
-uses-unsafe-cpows = true
 [browser_storage_listings.js]
-uses-unsafe-cpows = true
 [browser_storage_updates.js]
-uses-unsafe-cpows = true
 [browser_stylesheets_getTextEmpty.js]
 [browser_stylesheets_nested-iframes.js]
 [browser_timeline.js]
 [browser_timeline_actors.js]
 [browser_timeline_iframes.js]
 [browser_register_actor.js]
 [browser_webextension_inspected_window.js]
--- a/devtools/server/tests/browser/browser_storage_dynamic_windows.js
+++ b/devtools/server/tests/browser/browser_storage_dynamic_windows.js
@@ -1,263 +1,190 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const {StorageFront} = require("devtools/shared/fronts/storage");
-Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js", this);
+const { StorageFront } = require("devtools/shared/fronts/storage");
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js",
+  this);
 
+// beforeReload references an object representing the initialized state of the
+// storage actor.
 const beforeReload = {
   cookies: {
     "http://test1.example.org": ["c1", "cs2", "c3", "uc1"],
     "http://sectest1.example.org": ["uc1", "cs2"]
   },
-  localStorage: {
-    "http://test1.example.org": ["ls1", "ls2"],
-    "http://sectest1.example.org": ["iframe-u-ls1"]
-  },
-  sessionStorage: {
-    "http://test1.example.org": ["ss1"],
-    "http://sectest1.example.org": ["iframe-u-ss1", "iframe-u-ss2"]
-  },
   indexedDB: {
     "http://test1.example.org": [
       JSON.stringify(["idb1", "obj1"]),
       JSON.stringify(["idb1", "obj2"]),
       JSON.stringify(["idb2", "obj3"]),
     ],
     "http://sectest1.example.org": []
+  },
+  localStorage: {
+    "http://test1.example.org": ["ls1", "ls2"],
+    "http://sectest1.example.org": ["iframe-u-ls1"]
+  },
+  sessionStorage: {
+    "http://test1.example.org": ["ss1"],
+    "http://sectest1.example.org": ["iframe-u-ss1", "iframe-u-ss2"]
   }
 };
 
-async function testStores(data, front) {
-  testWindowsBeforeReload(data);
-
-  // FIXME: Bug 1183581 - browser_storage_dynamic_windows.js IsSafeToRunScript
-  //                      errors when testing reload in E10S mode
-  // yield testReload(front);
-  await testAddIframe(front);
-  await testRemoveIframe(front);
-}
-
-function testWindowsBeforeReload(data) {
-  for (let storageType in beforeReload) {
-    ok(data[storageType], storageType + " storage actor is present");
-    is(Object.keys(data[storageType].hosts).length,
-       Object.keys(beforeReload[storageType]).length,
-       "Number of hosts for " + storageType + "match");
-    for (let host in beforeReload[storageType]) {
-      ok(data[storageType].hosts[host], "Host " + host + " is present");
-    }
-  }
-}
-
-function markOutMatched(toBeEmptied, data, deleted) {
-  if (!Object.keys(toBeEmptied).length) {
-    info("Object empty");
-    return;
+// afterIframeAdded references the items added when an iframe containing storage
+// items is added to the page.
+const afterIframeAdded = {
+  cookies: {
+    "https://sectest1.example.org": [
+      getCookieId("cs2", ".example.org", "/"),
+      getCookieId("sc1", "sectest1.example.org",
+                  "/browser/devtools/server/tests/browser/")
+    ],
+    "http://sectest1.example.org": [
+      getCookieId("sc1", "sectest1.example.org",
+                  "/browser/devtools/server/tests/browser/")
+    ]
+  },
+  indexedDB: {
+    // empty because indexed db creation happens after the page load, so at
+    // the time of window-ready, there was no indexed db present.
+    "https://sectest1.example.org": []
+  },
+  localStorage: {
+    "https://sectest1.example.org": ["iframe-s-ls1"]
+  },
+  sessionStorage: {
+    "https://sectest1.example.org": ["iframe-s-ss1"]
   }
-  ok(Object.keys(data).length,
-     "At least one storage type should be present");
-  for (let storageType in toBeEmptied) {
-    if (!data[storageType]) {
-      continue;
-    }
-    info("Testing for " + storageType);
-    for (let host in data[storageType]) {
-      ok(toBeEmptied[storageType][host], "Host " + host + " found");
-
-      if (!deleted) {
-        for (let item of data[storageType][host]) {
-          let index = toBeEmptied[storageType][host].indexOf(item);
-          ok(index > -1, "Item found - " + item);
-          if (index > -1) {
-            toBeEmptied[storageType][host].splice(index, 1);
-          }
-        }
-        if (!toBeEmptied[storageType][host].length) {
-          delete toBeEmptied[storageType][host];
-        }
-      } else {
-        delete toBeEmptied[storageType][host];
-      }
-    }
-    if (!Object.keys(toBeEmptied[storageType]).length) {
-      delete toBeEmptied[storageType];
-    }
-  }
-}
-
-function testAddIframe(front) {
-  info("Testing if new iframe addition works properly");
-  return new Promise(resolve => {
-    let shouldBeEmpty = {
-      localStorage: {
-        "https://sectest1.example.org": ["iframe-s-ls1"]
-      },
-      sessionStorage: {
-        "https://sectest1.example.org": ["iframe-s-ss1"]
-      },
-      cookies: {
-        "https://sectest1.example.org": [
-          getCookieId("cs2", ".example.org", "/"),
-          getCookieId("sc1", "sectest1.example.org",
-                      "/browser/devtools/server/tests/browser/")
-        ],
-        "http://sectest1.example.org": [
-          getCookieId("sc1", "sectest1.example.org",
-                      "/browser/devtools/server/tests/browser/")
-        ]
-      },
-      indexedDB: {
-        // empty because indexed db creation happens after the page load, so at
-        // the time of window-ready, there was no indexed db present.
-        "https://sectest1.example.org": []
-      },
-      Cache: {
-        "https://sectest1.example.org": []
-      }
-    };
-
-    let onStoresUpdate = data => {
-      info("checking if the hosts list is correct for this iframe addition");
-
-      markOutMatched(shouldBeEmpty, data.added);
+};
 
-      ok(!data.changed || !data.changed.cookies ||
-         !data.changed.cookies["https://sectest1.example.org"],
-         "Nothing got changed for cookies");
-      ok(!data.changed || !data.changed.localStorage ||
-         !data.changed.localStorage["https://sectest1.example.org"],
-         "Nothing got changed for local storage");
-      ok(!data.changed || !data.changed.sessionStorage ||
-         !data.changed.sessionStorage["https://sectest1.example.org"],
-         "Nothing got changed for session storage");
-      ok(!data.changed || !data.changed.indexedDB ||
-         !data.changed.indexedDB["https://sectest1.example.org"],
-         "Nothing got changed for indexed db");
-
-      ok(!data.deleted || !data.deleted.cookies ||
-         !data.deleted.cookies["https://sectest1.example.org"],
-         "Nothing got deleted for cookies");
-      ok(!data.deleted || !data.deleted.localStorage ||
-         !data.deleted.localStorage["https://sectest1.example.org"],
-         "Nothing got deleted for local storage");
-      ok(!data.deleted || !data.deleted.sessionStorage ||
-         !data.deleted.sessionStorage["https://sectest1.example.org"],
-         "Nothing got deleted for session storage");
-      ok(!data.deleted || !data.deleted.indexedDB ||
-         !data.deleted.indexedDB["https://sectest1.example.org"],
-         "Nothing got deleted for indexed db");
-
-      if (!Object.keys(shouldBeEmpty).length) {
-        info("Everything to be received is received.");
-        endTestReloaded();
-      }
-    };
-
-    let endTestReloaded = () => {
-      front.off("stores-update", onStoresUpdate);
-      resolve();
-    };
-
-    front.on("stores-update", onStoresUpdate);
-
-    // eslint-disable-next-line mozilla/no-cpows-in-tests
-    let iframe = gBrowser.contentDocumentAsCPOW.createElement("iframe");
-    iframe.src = ALT_DOMAIN_SECURED + "storage-secured-iframe.html";
-    // eslint-disable-next-line mozilla/no-cpows-in-tests
-    gBrowser.contentDocumentAsCPOW.querySelector("body").appendChild(iframe);
-  });
-}
-
-function testRemoveIframe(front) {
-  info("Testing if iframe removal works properly");
-  return new Promise(resolve => {
-    let shouldBeEmpty = {
-      localStorage: {
-        "http://sectest1.example.org": []
-      },
-      sessionStorage: {
-        "http://sectest1.example.org": []
-      },
-      Cache: {
-        "http://sectest1.example.org": []
-      },
-      indexedDB: {
-        "http://sectest1.example.org": []
-      }
-    };
-
-    let onStoresUpdate = data => {
-      info("checking if the hosts list is correct for this iframe deletion");
-
-      markOutMatched(shouldBeEmpty, data.deleted, true);
-
-      ok(!data.deleted.cookies || !data.deleted.cookies["sectest1.example.org"],
-        "Nothing got deleted for Cookies as " +
-        "the same hostname is still present");
-
-      ok(!data.changed || !data.changed.cookies ||
-         !data.changed.cookies["http://sectest1.example.org"],
-         "Nothing got changed for cookies");
-      ok(!data.changed || !data.changed.localStorage ||
-         !data.changed.localStorage["http://sectest1.example.org"],
-         "Nothing got changed for local storage");
-      ok(!data.changed || !data.changed.sessionStorage ||
-         !data.changed.sessionStorage["http://sectest1.example.org"],
-         "Nothing got changed for session storage");
-
-      ok(!data.added || !data.added.cookies ||
-         !data.added.cookies["http://sectest1.example.org"],
-         "Nothing got added for cookies");
-      ok(!data.added || !data.added.localStorage ||
-         !data.added.localStorage["http://sectest1.example.org"],
-         "Nothing got added for local storage");
-      ok(!data.added || !data.added.sessionStorage ||
-         !data.added.sessionStorage["http://sectest1.example.org"],
-         "Nothing got added for session storage");
-
-      if (!Object.keys(shouldBeEmpty).length) {
-        info("Everything to be received is received.");
-        endTestReloaded();
-      }
-    };
-
-    let endTestReloaded = () => {
-      front.off("stores-update", onStoresUpdate);
-      resolve();
-    };
-
-    front.on("stores-update", onStoresUpdate);
-
-    ContentTask.spawn(gBrowser.selectedBrowser, {}, () => {
-      for (let iframe of content.document.querySelectorAll("iframe")) {
-        if (iframe.src.startsWith("http:")) {
-          iframe.remove();
-          break;
-        }
-      }
-    });
-  });
-}
+// afterIframeRemoved references the items deleted when an iframe containing
+// storage items is removed from the page.
+const afterIframeRemoved = {
+  cookies: {
+    "http://sectest1.example.org": []
+  },
+  indexedDB: {
+    "http://sectest1.example.org": []
+  },
+  localStorage: {
+    "http://sectest1.example.org": []
+  },
+  sessionStorage: {
+    "http://sectest1.example.org": []
+  },
+};
 
 add_task(async function() {
   await openTabAndSetupStorage(MAIN_DOMAIN + "storage-dynamic-windows.html");
 
   initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   let form = await connectDebuggerClient(client);
   let front = StorageFront(client, form);
   let data = await front.listStores();
+
   await testStores(data, front);
 
   await clearStorage();
 
   // Forcing GC/CC to get rid of docshells and windows created by this test.
   forceCollections();
   await client.close();
   forceCollections();
   DebuggerServer.destroy();
   forceCollections();
 });
+
+async function testStores(data, front) {
+  testWindowsBeforeReload(data);
+
+  await testAddIframe(front);
+  await testRemoveIframe(front);
+}
+
+function testWindowsBeforeReload(data) {
+  for (let storageType in beforeReload) {
+    ok(data[storageType], `${storageType} storage actor is present`);
+    is(Object.keys(data[storageType].hosts).length,
+       Object.keys(beforeReload[storageType]).length,
+        `Number of hosts for ${storageType} match`);
+    for (let host in beforeReload[storageType]) {
+      ok(data[storageType].hosts[host], `Host ${host} is present`);
+    }
+  }
+}
+
+async function testAddIframe(front) {
+  info("Testing if new iframe addition works properly");
+
+  let update = front.once("stores-update");
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, ALT_DOMAIN_SECURED,
+    secured => {
+      let doc = content.document;
+
+      let iframe = doc.createElement("iframe");
+      iframe.src = secured + "storage-secured-iframe.html";
+
+      doc.querySelector("body").appendChild(iframe);
+    }
+  );
+
+  let data = await update;
+
+  validateStorage(data, afterIframeAdded, "added");
+}
+
+async function testRemoveIframe(front) {
+  info("Testing if iframe removal works properly");
+
+  let update = front.once("stores-update");
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, {}, () => {
+    for (let iframe of content.document.querySelectorAll("iframe")) {
+      if (iframe.src.startsWith("http:")) {
+        iframe.remove();
+        break;
+      }
+    }
+  });
+
+  let data = await update;
+
+  validateStorage(data, afterIframeRemoved, "deleted");
+}
+
+function validateStorage(actual, expected, category = "") {
+  if (category) {
+    for (let cat of ["added", "changed", "deleted"]) {
+      if (cat === category) {
+        ok(actual[cat], `Data from the iframe has been ${cat}.`);
+      } else {
+        ok(!actual[cat], `No data was ${cat}.`);
+      }
+    }
+  }
+
+  for (let [type, expectedData] of Object.entries(expected)) {
+    let actualData = category ? actual[category][type] : actual[type];
+
+    ok(actualData, `${type} contains data.`);
+
+    let actualKeys = Object.keys(actualData);
+    let expectedKeys = Object.keys(expectedData);
+    is(actualKeys.length, expectedKeys.length, `${type} data is the correct length.`);
+
+    for (let [key, dataValues] of Object.entries(expectedData)) {
+      ok(actualData[key], `${type} data contains the key ${key}.`);
+
+      for (let dataValue of dataValues) {
+        ok(actualData[key].includes(dataValue),
+          `${type}[${key}] contains "${dataValue}".`);
+      }
+    }
+  }
+}
--- a/devtools/server/tests/browser/browser_storage_updates.js
+++ b/devtools/server/tests/browser/browser_storage_updates.js
@@ -4,25 +4,25 @@
 
 "use strict";
 
 const {StorageFront} = require("devtools/shared/fronts/storage");
 
 const TESTS = [
   // index 0
   {
-    action: function(win) {
-      info('win.addCookie("c1", "foobar1")');
-      win.addCookie("c1", "foobar1");
+    action: async function(win) {
+      info('addCookie("c1", "foobar1")');
+      await addCookie("c1", "foobar1");
 
-      info('win.addCookie("c2", "foobar2")');
-      win.addCookie("c2", "foobar2");
+      info('addCookie("c2", "foobar2")');
+      await addCookie("c2", "foobar2");
 
-      info('win.localStorage.setItem("l1", "foobar1")');
-      win.localStorage.setItem("l1", "foobar1");
+      info('localStorageSetItem("l1", "foobar1")');
+      await localStorageSetItem("l1", "foobar1");
     },
     expected: {
       added: {
         cookies: {
           "http://test1.example.org": [
             getCookieId("c1", "test1.example.org",
                         "/browser/devtools/server/tests/browser/"),
             getCookieId("c2", "test1.example.org",
@@ -33,22 +33,22 @@ const TESTS = [
           "http://test1.example.org": ["l1"]
         }
       }
     }
   },
 
   // index 1
   {
-    action: function(win) {
-      info('win.addCookie("c1", "new_foobar1")');
-      win.addCookie("c1", "new_foobar1");
+    action: async function() {
+      info('addCookie("c1", "new_foobar1")');
+      await addCookie("c1", "new_foobar1");
 
-      info('win.localStorage.setItem("l2", "foobar2")');
-      win.localStorage.setItem("l2", "foobar2");
+      info('localStorageSetItem("l2", "foobar2")');
+      await localStorageSetItem("l2", "foobar2");
     },
     expected: {
       changed: {
         cookies: {
           "http://test1.example.org": [
             getCookieId("c1", "test1.example.org",
                         "/browser/devtools/server/tests/browser/"),
           ]
@@ -59,25 +59,25 @@ const TESTS = [
           "http://test1.example.org": ["l2"]
         }
       }
     }
   },
 
   // index 2
   {
-    action: function(win) {
-      info('win.removeCookie("c2")');
-      win.removeCookie("c2");
+    action: async function() {
+      info('removeCookie("c2")');
+      await removeCookie("c2");
 
-      info('win.localStorage.removeItem("l1")');
-      win.localStorage.removeItem("l1");
+      info('localStorageRemoveItem("l1")');
+      await localStorageRemoveItem("l1");
 
-      info('win.localStorage.setItem("l3", "foobar3")');
-      win.localStorage.setItem("l3", "foobar3");
+      info('localStorageSetItem("l3", "foobar3")');
+      await localStorageSetItem("l3", "foobar3");
     },
     expected: {
       deleted: {
         cookies: {
           "http://test1.example.org": [
             getCookieId("c2", "test1.example.org",
                         "/browser/devtools/server/tests/browser/"),
           ]
@@ -91,34 +91,34 @@ const TESTS = [
           "http://test1.example.org": ["l3"]
         }
       }
     }
   },
 
   // index 3
   {
-    action: function(win) {
-      info('win.removeCookie("c1")');
-      win.removeCookie("c1");
+    action: async function() {
+      info('removeCookie("c1")');
+      await removeCookie("c1");
 
-      info('win.addCookie("c3", "foobar3")');
-      win.addCookie("c3", "foobar3");
+      info('addCookie("c3", "foobar3")');
+      await addCookie("c3", "foobar3");
 
-      info('win.localStorage.removeItem("l2")');
-      win.localStorage.removeItem("l2");
+      info('localStorageRemoveItem("l2")');
+      await localStorageRemoveItem("l2");
 
-      info('win.sessionStorage.setItem("s1", "foobar1")');
-      win.sessionStorage.setItem("s1", "foobar1");
+      info('sessionStorageSetItem("s1", "foobar1")');
+      await sessionStorageSetItem("s1", "foobar1");
 
-      info('win.sessionStorage.setItem("s2", "foobar2")');
-      win.sessionStorage.setItem("s2", "foobar2");
+      info('sessionStorageSetItem("s2", "foobar2")');
+      await sessionStorageSetItem("s2", "foobar2");
 
-      info('win.localStorage.setItem("l3", "new_foobar3")');
-      win.localStorage.setItem("l3", "new_foobar3");
+      info('localStorageSetItem("l3", "new_foobar3")');
+      await localStorageSetItem("l3", "new_foobar3");
     },
     expected: {
       added: {
         cookies: {
           "http://test1.example.org": [
             getCookieId("c3", "test1.example.org",
                         "/browser/devtools/server/tests/browser/"),
           ]
@@ -143,48 +143,68 @@ const TESTS = [
           "http://test1.example.org": ["l2"]
         }
       }
     }
   },
 
   // index 4
   {
-    action: function(win) {
-      info('win.sessionStorage.removeItem("s1")');
-      win.sessionStorage.removeItem("s1");
+    action: async function() {
+      info('sessionStorageRemoveItem("s1")');
+      await sessionStorageRemoveItem("s1");
     },
     expected: {
       deleted: {
         sessionStorage: {
           "http://test1.example.org": ["s1"]
         }
       }
     }
   },
 
   // index 5
   {
-    action: function(win) {
-      info("win.clearCookies()");
-      win.clearCookies();
+    action: async function() {
+      info("clearCookies()");
+      await clearCookies();
     },
     expected: {
       deleted: {
         cookies: {
           "http://test1.example.org": [
             getCookieId("c3", "test1.example.org",
                         "/browser/devtools/server/tests/browser/"),
           ]
         }
       }
     }
   }
 ];
 
+add_task(async function() {
+  await addTab(MAIN_DOMAIN + "storage-updates.html");
+
+  initDebuggerServer();
+
+  let client = new DebuggerClient(DebuggerServer.connectPipe());
+  let form = await connectDebuggerClient(client);
+  let front = StorageFront(client, form);
+
+  await front.listStores();
+
+  for (let i = 0; i < TESTS.length; i++) {
+    let test = TESTS[i];
+    await runTest(test, front, i);
+  }
+
+  await testClearLocalAndSessionStores(front);
+  await finishTests(client);
+});
+
 function markOutMatched(toBeEmptied, data) {
   if (!Object.keys(toBeEmptied).length) {
     info("Object empty");
     return;
   }
   ok(Object.keys(data).length, "At least one storage type should be present");
 
   for (let storageType in toBeEmptied) {
@@ -235,52 +255,51 @@ function onStoresUpdate(expected, {added
       (!expected.changed || !Object.keys(expected.changed).length) &&
       (!expected.deleted || !Object.keys(expected.deleted).length)) {
     info("Everything expected has been received for index " + index);
   } else {
     info("Still some updates pending for index " + index);
   }
 }
 
-function runTest({action, expected}, front, win, index) {
-  return new Promise(resolve => {
-    front.once("stores-update", function(addedChangedDeleted) {
-      onStoresUpdate(expected, addedChangedDeleted, index);
-      resolve();
-    });
+async function runTest({action, expected}, front, index) {
+  let update = front.once("stores-update");
 
-    info("Running test at index " + index);
-    action(win);
-  });
+  info("Running test at index " + index);
+  await action();
+
+  let addedChangedDeleted = await update;
+
+  onStoresUpdate(expected, addedChangedDeleted, index);
 }
 
-function testClearLocalAndSessionStores(front, win) {
-  return new Promise(resolve => {
-    // We need to wait until we have received stores-cleared for both local and
-    // session storage.
-    let localStorage = false;
-    let sessionStorage = false;
+async function testClearLocalAndSessionStores(front) {
+  // We need to wait until we have received stores-cleared for both local and
+  // session storage.
+  let localStorage = false;
+  let sessionStorage = false;
 
-    front.on("stores-cleared", function onStoresCleared(data) {
-      storesCleared(data);
+  await clearLocalAndSessionStores();
+
+  let data = await front.once("stores-cleared");
+
+  storesCleared(data);
 
-      if (data.localStorage) {
-        localStorage = true;
-      }
-      if (data.sessionStorage) {
-        sessionStorage = true;
-      }
-      if (localStorage && sessionStorage) {
-        front.off("stores-cleared", onStoresCleared);
-        resolve();
-      }
-    });
+  if (data.localStorage) {
+    localStorage = true;
+  }
+
+  data = await front.once("stores-cleared");
 
-    win.clearLocalAndSessionStores();
-  });
+  if (data.sessionStorage) {
+    sessionStorage = true;
+  }
+
+  ok(localStorage, "localStorage was cleared");
+  ok(sessionStorage, "sessionStorage was cleared");
 }
 
 function storesCleared(data) {
   if (data.sessionStorage || data.localStorage) {
     let hosts = data.sessionStorage || data.localStorage;
     info("Stores cleared required for session storage");
     is(hosts.length, 1, "number of hosts is 1");
     is(hosts[0], "http://test1.example.org",
@@ -291,30 +310,55 @@ function storesCleared(data) {
 }
 
 async function finishTests(client) {
   await client.close();
   DebuggerServer.destroy();
   finish();
 }
 
-add_task(async function() {
-  let browser = await addTab(MAIN_DOMAIN + "storage-updates.html");
-  // eslint-disable-next-line mozilla/no-cpows-in-tests
-  let doc = browser.contentDocumentAsCPOW;
+async function addCookie(name, value) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, [name, value], ([iName, iValue]) => {
+    content.wrappedJSObject.window.addCookie(iName, iValue);
+  });
+}
+
+async function removeCookie(name) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, name, iName => {
+    content.wrappedJSObject.window.removeCookie(iName);
+  });
+}
 
-  initDebuggerServer();
+async function localStorageSetItem(name, value) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, [name, value], ([iName, iValue]) => {
+    content.window.localStorage.setItem(iName, iValue);
+  });
+}
+
+async function localStorageRemoveItem(name) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, name, iName => {
+    content.window.localStorage.removeItem(iName);
+  });
+}
 
-  let client = new DebuggerClient(DebuggerServer.connectPipe());
-  let form = await connectDebuggerClient(client);
-  let front = StorageFront(client, form);
-  let win = doc.defaultView.wrappedJSObject;
+async function sessionStorageSetItem(name, value) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, [name, value], ([iName, iValue]) => {
+    content.window.sessionStorage.setItem(iName, iValue);
+  });
+}
 
-  await front.listStores();
+async function sessionStorageRemoveItem(name) {
+  await ContentTask.spawn(gBrowser.selectedBrowser, name, iName => {
+    content.window.sessionStorage.removeItem(iName);
+  });
+}
 
-  for (let i = 0; i < TESTS.length; i++) {
-    let test = TESTS[i];
-    await runTest(test, front, win, i);
-  }
+async function clearCookies() {
+  await ContentTask.spawn(gBrowser.selectedBrowser, {}, () => {
+    content.wrappedJSObject.window.clearCookies();
+  });
+}
 
-  await testClearLocalAndSessionStores(front, win);
-  await finishTests(client);
-});
+async function clearLocalAndSessionStores() {
+  await ContentTask.spawn(gBrowser.selectedBrowser, {}, () => {
+    content.wrappedJSObject.window.clearLocalAndSessionStores();
+  });
+}
--- a/dom/base/test/browser_bug1303838.js
+++ b/dom/base/test/browser_bug1303838.js
@@ -11,16 +11,28 @@
  * link click if loadDivertedInBackground is set to true.
  */
 
 "use strict";
 
 const BASE_URL = "http://mochi.test:8888/browser/dom/base/test/";
 
 add_task(async function() {
+  // On Linux, in our test automation, the mouse cursor floats over
+  // the first tab, which causes it to be warmed up when tab warming
+  // is enabled. The TabSwitchDone event doesn't fire until the warmed
+  // tab is evicted, which is after a few seconds. That means that
+  // this test ends up taking longer than we'd like, since its waiting
+  // for the TabSwitchDone event between tab switches.
+  //
+  // So now we make sure that warmed tabs are evicted very shortly
+  // after warming to avoid the test running too long.
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.tabs.remote.warmup.unloadDelayMs", 50]],
+  });
   await testLinkClick(false, false);
   await testLinkClick(false, true);
   await testLinkClick(true, false);
   await testLinkClick(true, true);
 });
 
 async function testLinkClick(withFrame, loadDivertedInBackground) {
   await SpecialPowers.pushPrefEnv({"set": [["browser.tabs.loadDivertedInBackground", loadDivertedInBackground]]});
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1518,17 +1518,17 @@ ContentParent::OnChannelError()
 {
   RefPtr<ContentParent> content(this);
   PContentParent::OnChannelError();
 }
 
 void
 ContentParent::OnChannelConnected(int32_t pid)
 {
-  SetOtherProcessId(pid);
+  MOZ_ASSERT(NS_IsMainThread());
 
 #if defined(ANDROID) || defined(LINUX)
   // Check nice preference
   int32_t nice = Preferences::GetInt("dom.ipc.content.nice", 0);
 
   // Environment variable overrides preference
   char* relativeNicenessStr = getenv("MOZ_CHILD_PROCESS_RELATIVE_NICENESS");
   if (relativeNicenessStr) {
@@ -1543,16 +1543,21 @@ ContentParent::OnChannelConnected(int32_
     if (NS_FAILED(rv)) {
       cpus = 1;
     }
     if (nice != 0 && cpus == 1) {
       setpriority(PRIO_PROCESS, pid, getpriority(PRIO_PROCESS, pid) + nice);
     }
   }
 #endif
+
+#ifdef MOZ_CODE_COVERAGE
+  Unused << SendShareCodeCoverageMutex(
+              CodeCoverageHandler::Get()->GetMutexHandle(pid));
+#endif
 }
 
 void
 ContentParent::ProcessingError(Result aCode, const char* aReason)
 {
   if (MsgDropped == aCode) {
     return;
   }
@@ -2051,39 +2056,36 @@ ContentParent::LaunchSubprocess(ProcessP
   nsCString schedulerPrefs = Scheduler::GetPrefs();
   extraArgs.push_back("-schedulerPrefs");
   extraArgs.push_back(schedulerPrefs.get());
 
   if (gSafeMode) {
     extraArgs.push_back("-safeMode");
   }
 
-  if (!mSubprocess->LaunchAndWaitForProcessHandle(extraArgs)) {
+  SetOtherProcessId(kInvalidProcessId, ProcessIdState::ePending);
+  if (!mSubprocess->Launch(extraArgs)) {
     NS_ERROR("failed to launch child in the parent");
     MarkAsDead();
     return false;
   }
 
-  base::ProcessId procId = base::GetProcId(mSubprocess->GetChildProcessHandle());
-
-  Open(mSubprocess->GetChannel(), procId);
-
-#ifdef MOZ_CODE_COVERAGE
-  Unused << SendShareCodeCoverageMutex(CodeCoverageHandler::Get()->GetMutexHandle(procId));
-#endif
+  OpenWithAsyncPid(mSubprocess->GetChannel());
 
   InitInternal(aInitialPriority);
 
   ContentProcessManager::GetSingleton()->AddContentProcess(this);
 
   mHangMonitorActor = ProcessHangMonitor::AddProcess(this);
 
   // Set a reply timeout for CPOWs.
   SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
 
+  // TODO: If OtherPid() is not called between mSubprocess->Launch() and this,
+  // then we're not really measuring how long it took to spawn the process.
   Telemetry::Accumulate(Telemetry::CONTENT_PROCESS_LAUNCH_TIME_MS,
                         static_cast<uint32_t>((TimeStamp::Now() - mLaunchTS)
                                               .ToMilliseconds()));
 
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     nsAutoString cpId;
     cpId.AppendInt(static_cast<uint64_t>(this->ChildID()));
@@ -2135,17 +2137,17 @@ ContentParent::ContentParent(ContentPare
   // Request Windows message deferral behavior on our side of the PContent
   // channel. Generally only applies to the situation where we get caught in
   // a deadlock with the plugin process when sending CPOWs.
   GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 #endif
 
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   bool isFile = mRemoteType.EqualsLiteral(FILE_REMOTE_TYPE);
-  mSubprocess = new GeckoChildProcessHost(GeckoProcessType_Content, isFile);
+  mSubprocess = new ContentProcessHost(this, isFile);
 }
 
 ContentParent::~ContentParent()
 {
   if (mForceKillTimer) {
     mForceKillTimer->Cancel();
   }
 
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -6,26 +6,26 @@
 
 #ifndef mozilla_dom_ContentParent_h
 #define mozilla_dom_ContentParent_h
 
 #include "mozilla/dom/PContentParent.h"
 #include "mozilla/dom/nsIContentParent.h"
 #include "mozilla/gfx/gfxVarReceiver.h"
 #include "mozilla/gfx/GPUProcessListener.h"
-#include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/HalTypes.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReportingProcess.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 
+#include "ContentProcessHost.h"
 #include "nsDataHashtable.h"
 #include "nsPluginTags.h"
 #include "nsFrameMessageManager.h"
 #include "nsHashKeys.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIObserver.h"
 #include "nsIThreadInternal.h"
 #include "nsIDOMGeoPositionCallback.h"
@@ -109,24 +109,24 @@ class ContentParent final : public PCont
                           , public nsIDOMGeoPositionCallback
                           , public nsIDOMGeoPositionErrorCallback
                           , public nsIInterfaceRequestor
                           , public gfx::gfxVarReceiver
                           , public mozilla::LinkedListElement<ContentParent>
                           , public gfx::GPUProcessListener
                           , public mozilla::MemoryReportingProcess
 {
-  typedef mozilla::ipc::GeckoChildProcessHost GeckoChildProcessHost;
   typedef mozilla::ipc::OptionalURIParams OptionalURIParams;
   typedef mozilla::ipc::PFileDescriptorSetParent PFileDescriptorSetParent;
   typedef mozilla::ipc::TestShellParent TestShellParent;
   typedef mozilla::ipc::URIParams URIParams;
   typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
   typedef mozilla::dom::ClonedMessageData ClonedMessageData;
 
+  friend class ContentProcessHost;
   friend class mozilla::PreallocatedProcessManagerImpl;
 
 public:
 
   virtual bool IsContentParent() const override { return true; }
 
   /**
    * Create a subprocess suitable for use later as a content process.
@@ -370,17 +370,17 @@ public:
   {
     return mIsForBrowser;
   }
   virtual bool IsForJSPlugin() const override
   {
     return mJSPluginID != nsFakePluginTag::NOT_JSPLUGIN;
   }
 
-  GeckoChildProcessHost* Process() const
+  ContentProcessHost* Process() const
   {
     return mSubprocess;
   }
 
   ContentParent* Opener() const
   {
     return mOpener;
   }
@@ -1227,17 +1227,17 @@ public:
   bool CanCommunicateWith(ContentParentId aOtherProcess);
 
 private:
 
   // If you add strong pointers to cycle collected objects here, be sure to
   // release these objects in ShutDownProcess.  See the comment there for more
   // details.
 
-  GeckoChildProcessHost* mSubprocess;
+  ContentProcessHost* mSubprocess;
   const TimeStamp mLaunchTS; // used to calculate time to start content process
   TimeStamp mActivateTS;
   ContentParent* mOpener;
 
   nsString mRemoteType;
 
   ContentParentId mChildID;
   int32_t mGeolocationWatchID;
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ContentProcessHost.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sts=8 sw=2 ts=2 tw=99 et :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ContentProcessHost.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace ipc;
+
+ContentProcessHost::ContentProcessHost(ContentParent* aContentParent,
+                                       bool aIsFileContent)
+ : GeckoChildProcessHost(GeckoProcessType_Content, aIsFileContent),
+   mHasLaunched(false),
+   mContentParent(aContentParent)
+{
+  MOZ_COUNT_CTOR(ContentProcessHost);
+}
+
+ContentProcessHost::~ContentProcessHost()
+{
+  MOZ_COUNT_DTOR(ContentProcessHost);
+}
+
+bool
+ContentProcessHost::Launch(StringVector aExtraOpts)
+{
+  MOZ_ASSERT(!mHasLaunched);
+  MOZ_ASSERT(mContentParent);
+
+  bool res = GeckoChildProcessHost::AsyncLaunch(aExtraOpts);
+  MOZ_RELEASE_ASSERT(res);
+  return true;
+}
+
+void
+ContentProcessHost::OnProcessHandleReady(ProcessHandle aProcessHandle)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  // This will wake up the main thread if it is waiting for the process to
+  // launch.
+  mContentParent->SetOtherProcessId(base::GetProcId(aProcessHandle));
+
+  mHasLaunched = true;
+  GeckoChildProcessHost::OnProcessHandleReady(aProcessHandle);
+}
+
+void
+ContentProcessHost::OnProcessLaunchError()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  mContentParent->SetOtherProcessId(mozilla::ipc::kInvalidProcessId,
+                                    ContentParent::ProcessIdState::eError);
+  mHasLaunched = true;
+  GeckoChildProcessHost::OnProcessLaunchError();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/ContentProcessHost.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: sts=8 sw=2 ts=2 tw=99 et :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _include_mozilla_dom_ipc_ContentProcessHost_h_
+#define _include_mozilla_dom_ipc_ContentProcessHost_h_
+
+#include "mozilla/ipc/GeckoChildProcessHost.h"
+
+namespace mozilla {
+namespace dom {
+
+class ContentParent;
+
+// ContentProcessHost is the "parent process" container for a subprocess handle
+// and IPC connection. It owns the parent process IPDL actor, which in this
+// case, is a ContentParent.
+class ContentProcessHost final : public ::mozilla::ipc::GeckoChildProcessHost
+{
+  friend class ContentParent;
+
+public:
+  explicit
+  ContentProcessHost(ContentParent* aContentParent,
+                     bool aIsFileContent = false);
+  ~ContentProcessHost();
+
+  // Launch the subprocess asynchronously. On failure, false is returned.
+  // Otherwise, true is returned, and either the OnProcessHandleReady method is
+  // called when the process is created, or OnProcessLaunchError will be called
+  // if the process could not be spawned due to an asynchronous error.
+  bool Launch(StringVector aExtraOpts);
+
+  // Called on the IO thread.
+  void OnProcessHandleReady(ProcessHandle aProcessHandle) override;
+  void OnProcessLaunchError() override;
+
+private:
+  DISALLOW_COPY_AND_ASSIGN(ContentProcessHost);
+
+  bool mHasLaunched;
+
+  ContentParent* mContentParent; // weak
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _include_mozilla_dom_ipc_ContentProcessHost_h_
--- a/dom/ipc/PTabContext.ipdlh
+++ b/dom/ipc/PTabContext.ipdlh
@@ -1,14 +1,16 @@
 /* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */
 /* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+include "mozilla/dom/TabMessageUtils.h";
+
 include protocol PBrowser;
 include PBrowserOrId;
 
 using UIStateChangeType from "nsPIDOMWindow.h";
 using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
 
 namespace mozilla {
 namespace dom {
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -23,16 +23,17 @@ EXPORTS.mozilla.dom += [
     'CoalescedMouseData.h',
     'CoalescedWheelData.h',
     'ContentBridgeChild.h',
     'ContentBridgeParent.h',
     'ContentChild.h',
     'ContentParent.h',
     'ContentPrefs.h',
     'ContentProcess.h',
+    'ContentProcessHost.h',
     'ContentProcessManager.h',
     'CPOWManagerGetter.h',
     'FilePickerParent.h',
     'MemoryReportRequest.h',
     'nsIContentChild.h',
     'nsIContentParent.h',
     'PermissionMessageUtils.h',
     'TabChild.h',
@@ -55,16 +56,17 @@ UNIFIED_SOURCES += [
     'CoalescedMouseData.cpp',
     'CoalescedWheelData.cpp',
     'ColorPickerParent.cpp',
     'ContentBridgeChild.cpp',
     'ContentBridgeParent.cpp',
     'ContentParent.cpp',
     'ContentPrefs.cpp',
     'ContentProcess.cpp',
+    'ContentProcessHost.cpp',
     'ContentProcessManager.cpp',
     'FilePickerParent.cpp',
     'MemoryReportRequest.cpp',
     'nsIContentChild.cpp',
     'nsIContentParent.cpp',
     'PermissionMessageUtils.cpp',
     'PreallocatedProcessManager.cpp',
     'ProcessPriorityManager.cpp',
--- a/gfx/ipc/GPUChild.cpp
+++ b/gfx/ipc/GPUChild.cpp
@@ -12,16 +12,17 @@
 #include "mozilla/TelemetryIPC.h"
 #include "mozilla/dom/CheckerboardReportService.h"
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/gfx/gfxVars.h"
 #if defined(XP_WIN)
 # include "mozilla/gfx/DeviceManagerDx.h"
 #endif
 #include "mozilla/ipc/CrashReporterHost.h"
+#include "mozilla/layers/APZInputBridgeChild.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
 #include "mozilla/Unused.h"
 #include "mozilla/HangDetails.h"
 #include "nsIObserverService.h"
 
 #ifdef MOZ_GECKO_PROFILER
 #include "ProfilerParent.h"
 #endif
@@ -104,16 +105,32 @@ GPUChild::EnsureGPUReady()
   }
 
   gfxPlatform::GetPlatform()->ImportGPUDeviceData(data);
   Telemetry::AccumulateTimeDelta(Telemetry::GPU_PROCESS_LAUNCH_TIME_MS_2, mHost->GetLaunchTime());
   mGPUReady = true;
   return true;
 }
 
+PAPZInputBridgeChild*
+GPUChild::AllocPAPZInputBridgeChild(const uint64_t& aLayersId)
+{
+  APZInputBridgeChild* child = new APZInputBridgeChild();
+  child->AddRef();
+  return child;
+}
+
+bool
+GPUChild::DeallocPAPZInputBridgeChild(PAPZInputBridgeChild* aActor)
+{
+  APZInputBridgeChild* child = static_cast<APZInputBridgeChild*>(aActor);
+  child->Release();
+  return true;
+}
+
 mozilla::ipc::IPCResult
 GPUChild::RecvInitComplete(const GPUDeviceData& aData)
 {
   // We synchronously requested GPU parameters before this arrived.
   if (mGPUReady) {
     return IPC_OK();
   }
 
--- a/gfx/ipc/GPUChild.h
+++ b/gfx/ipc/GPUChild.h
@@ -32,16 +32,19 @@ class GPUChild final
 public:
   explicit GPUChild(GPUProcessHost* aHost);
   ~GPUChild();
 
   void Init();
 
   bool EnsureGPUReady();
 
+  PAPZInputBridgeChild* AllocPAPZInputBridgeChild(const uint64_t& aLayersId) override;
+  bool DeallocPAPZInputBridgeChild(PAPZInputBridgeChild* aActor) override;
+
   // gfxVarReceiver overrides.
   void OnVarChanged(const GfxVarUpdate& aVar) override;
 
   // PGPUChild overrides.
   mozilla::ipc::IPCResult RecvInitComplete(const GPUDeviceData& aData) override;
   mozilla::ipc::IPCResult RecvReportCheckerboard(const uint32_t& aSeverity, const nsCString& aLog) override;
   mozilla::ipc::IPCResult RecvInitCrashReporter(Shmem&& shmem, const NativeThreadId& aThreadId) override;
 
--- a/gfx/ipc/GPUParent.cpp
+++ b/gfx/ipc/GPUParent.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/dom/VideoDecoderManagerChild.h"
 #include "mozilla/dom/VideoDecoderManagerParent.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/ipc/CrashReporterClient.h"
 #include "mozilla/ipc/ProcessChild.h"
+#include "mozilla/layers/APZInputBridgeParent.h"
 #include "mozilla/layers/APZThreadUtils.h"
 #include "mozilla/layers/APZUtils.h"    // for apz::InitializeGlobalState
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorManagerParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
 #include "mozilla/layers/UiCompositorControllerParent.h"
@@ -120,17 +121,17 @@ GPUParent::Init(base::ProcessId aParentP
 
   if (NS_FAILED(NS_InitMinimalXPCOM())) {
     return false;
   }
 
   CompositorThreadHolder::Start();
   // TODO: Bug 1406327, Start VRListenerThreadHolder when loading VR content.
   VRListenerThreadHolder::Start();
-  APZThreadUtils::SetControllerThread(CompositorThreadHolder::Loop());
+  APZThreadUtils::SetControllerThread(MessageLoop::current());
   apz::InitializeGlobalState();
   LayerTreeOwnerTracker::Initialize();
   mozilla::ipc::SetThisProcessName("GPU Process");
 #ifdef XP_WIN
   wmf::MFStartup();
 #endif
   return true;
 }
@@ -157,16 +158,32 @@ GPUParent::NotifyDeviceReset()
 
   // Notify the main process that there's been a device reset
   // and that they should reset their compositors and repaint
   GPUDeviceData data;
   RecvGetDeviceStatus(&data);
   Unused << SendNotifyDeviceReset(data);
 }
 
+PAPZInputBridgeParent*
+GPUParent::AllocPAPZInputBridgeParent(const uint64_t& aLayersId)
+{
+  APZInputBridgeParent* parent = new APZInputBridgeParent(aLayersId);
+  parent->AddRef();
+  return parent;
+}
+
+bool
+GPUParent::DeallocPAPZInputBridgeParent(PAPZInputBridgeParent* aActor)
+{
+  APZInputBridgeParent* parent = static_cast<APZInputBridgeParent*>(aActor);
+  parent->Release();
+  return true;
+}
+
 mozilla::ipc::IPCResult
 GPUParent::RecvInit(nsTArray<GfxPrefSetting>&& prefs,
                     nsTArray<GfxVarUpdate>&& vars,
                     const DevicePrefs& devicePrefs,
                     nsTArray<LayerTreeIdMapping>&& aMappings)
 {
   const nsTArray<gfxPrefs::Pref*>& globalPrefs = gfxPrefs::all();
   for (auto& setting : prefs) {
--- a/gfx/ipc/GPUParent.h
+++ b/gfx/ipc/GPUParent.h
@@ -26,16 +26,19 @@ public:
 
   static GPUParent* GetSingleton();
 
   bool Init(base::ProcessId aParentPid,
             MessageLoop* aIOLoop,
             IPC::Channel* aChannel);
   void NotifyDeviceReset();
 
+  PAPZInputBridgeParent* AllocPAPZInputBridgeParent(const uint64_t& aLayersId) override;
+  bool DeallocPAPZInputBridgeParent(PAPZInputBridgeParent* aActor) override;
+
   mozilla::ipc::IPCResult RecvInit(nsTArray<GfxPrefSetting>&& prefs,
                                    nsTArray<GfxVarUpdate>&& vars,
                                    const DevicePrefs& devicePrefs,
                                    nsTArray<LayerTreeIdMapping>&& mappings) override;
   mozilla::ipc::IPCResult RecvInitCompositorManager(Endpoint<PCompositorManagerParent>&& aEndpoint) override;
   mozilla::ipc::IPCResult RecvInitVsyncBridge(Endpoint<PVsyncBridgeParent>&& aVsyncEndpoint) override;
   mozilla::ipc::IPCResult RecvInitImageBridge(Endpoint<PImageBridgeParent>&& aEndpoint) override;
   mozilla::ipc::IPCResult RecvInitVRManager(Endpoint<PVRManagerParent>&& aEndpoint) override;
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -822,16 +822,22 @@ GPUProcessManager::CreateRemoteSession(n
 
   RefPtr<APZCTreeManagerChild> apz = nullptr;
   if (aOptions.UseAPZ()) {
     PAPZCTreeManagerChild* papz = child->SendPAPZCTreeManagerConstructor(0);
     if (!papz) {
       return nullptr;
     }
     apz = static_cast<APZCTreeManagerChild*>(papz);
+
+    PAPZInputBridgeChild* pinput = mGPUChild->SendPAPZInputBridgeConstructor(aRootLayerTreeId);
+    if (!pinput) {
+      return nullptr;
+    }
+    apz->SetInputBridge(static_cast<APZInputBridgeChild*>(pinput));
   }
 
   RefPtr<RemoteCompositorSession> session =
     new RemoteCompositorSession(aWidget, child, widget, apz, aRootLayerTreeId);
   return session.forget();
 #else
   gfxCriticalNote << "Platform does not support out-of-process compositing";
   return nullptr;
--- a/gfx/ipc/PGPU.ipdl
+++ b/gfx/ipc/PGPU.ipdl
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include GraphicsMessages;
 include MemoryReportTypes;
 include HangTypes;
+include protocol PAPZInputBridge;
 include protocol PCompositorManager;
 include protocol PImageBridge;
 include protocol PProfiler;
 include protocol PVRManager;
 include protocol PVsyncBridge;
 include protocol PUiCompositorController;
 include protocol PVideoDecoderManager;
 
@@ -41,19 +42,28 @@ struct GfxPrefSetting {
   GfxPrefValue value;
 };
 
 struct LayerTreeIdMapping {
   uint64_t layersId;
   ProcessId ownerId;
 };
 
+// This protocol allows the UI process to talk to the GPU process. There is one
+// instance of this protocol, with the GPUParent living on the main thread of
+// the GPU process and the GPUChild living on the main thread of the UI process.
 sync protocol PGPU
 {
+  manages PAPZInputBridge;
+
 parent:
+  // Sent from the UI process to initialize a new APZ input bridge when a new
+  // top-level compositor is created.
+  async PAPZInputBridge(uint64_t aLayersId);
+
   // Sent by the UI process to initiate core settings.
   async Init(GfxPrefSetting[] prefs,
              GfxVarUpdate[] vars,
              DevicePrefs devicePrefs,
              LayerTreeIdMapping[] mapping);
 
   async InitCompositorManager(Endpoint<PCompositorManagerParent> endpoint);
   async InitVsyncBridge(Endpoint<PVsyncBridgeParent> endpoint);
--- a/gfx/ipc/RemoteCompositorSession.cpp
+++ b/gfx/ipc/RemoteCompositorSession.cpp
@@ -86,16 +86,17 @@ RemoteCompositorSession::GetAPZCTreeMana
 }
 
 void
 RemoteCompositorSession::Shutdown()
 {
   mContentController = nullptr;
   if (mAPZ) {
     mAPZ->SetCompositorSession(nullptr);
+    mAPZ->Destroy();
   }
   mCompositorBridgeChild->Destroy();
   mCompositorBridgeChild = nullptr;
   mCompositorWidgetDelegate = nullptr;
   mWidget = nullptr;
 #if defined(MOZ_WIDGET_ANDROID)
   if (mUiCompositorControllerChild) {
     mUiCompositorControllerChild->Destroy();
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/public/APZInputBridge.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_APZInputBridge_h
+#define mozilla_layers_APZInputBridge_h
+
+#include "mozilla/EventForwards.h"      // for WidgetInputEvent, nsEventStatus
+#include "Units.h"                      // for LayoutDeviceIntPoint
+
+namespace mozilla {
+
+class InputData;
+
+namespace layers {
+
+class APZInputBridgeParent;
+struct ScrollableLayerGuid;
+
+/**
+ * This class lives in the main process, and is accessed via the controller
+ * thread (which is the process main thread for desktop, and the Java UI
+ * thread for Android). This class exposes a synchronous API to deliver
+ * incoming input events to APZ and modify them in-place to unapply the APZ
+ * async transform. If there is a GPU process, then this class does sync IPC
+ * calls over to the GPU process in order to accomplish this. Otherwise,
+ * APZCTreeManager overrides and implements these methods directly.
+ */
+class APZInputBridge {
+public:
+  /**
+   * General handler for incoming input events. Manipulates the frame metrics
+   * based on what type of input it is. For example, a PinchGestureEvent will
+   * cause scaling. This should only be called externally to this class, and
+   * must be called on the controller thread.
+   *
+   * This function transforms |aEvent| to have its coordinates in DOM space.
+   * This is so that the event can be passed through the DOM and content can
+   * handle them. The event may need to be converted to a WidgetInputEvent
+   * by the caller if it wants to do this.
+   *
+   * The following values may be returned by this function:
+   * nsEventStatus_eConsumeNoDefault is returned to indicate the
+   *   APZ is consuming this event and the caller should discard the event with
+   *   extreme prejudice. The exact scenarios under which this is returned is
+   *   implementation-dependent and may vary.
+   * nsEventStatus_eIgnore is returned to indicate that the APZ code didn't
+   *   use this event. This might be because it was directed at a point on
+   *   the screen where there was no APZ, or because the thing the user was
+   *   trying to do was not allowed. (For example, attempting to pan a
+   *   non-pannable document).
+   * nsEventStatus_eConsumeDoDefault is returned to indicate that the APZ
+   *   code may have used this event to do some user-visible thing. Note that
+   *   in some cases CONSUMED is returned even if the event was NOT used. This
+   *   is because we cannot always know at the time of event delivery whether
+   *   the event will be used or not. So we err on the side of sending
+   *   CONSUMED when we are uncertain.
+   *
+   * @param aEvent input event object; is modified in-place
+   * @param aOutTargetGuid returns the guid of the apzc this event was
+   * delivered to. May be null.
+   * @param aOutInputBlockId returns the id of the input block that this event
+   * was added to, if that was the case. May be null.
+   */
+  virtual nsEventStatus ReceiveInputEvent(
+      InputData& aEvent,
+      ScrollableLayerGuid* aOutTargetGuid,
+      uint64_t* aOutInputBlockId) = 0;
+
+  /**
+   * WidgetInputEvent handler. Transforms |aEvent| (which is assumed to be an
+   * already-existing instance of an WidgetInputEvent which may be an
+   * WidgetTouchEvent) to have its coordinates in DOM space. This is so that the
+   * event can be passed through the DOM and content can handle them.
+   *
+   * NOTE: Be careful of invoking the WidgetInputEvent variant. This can only be
+   * called on the main thread. See widget/InputData.h for more information on
+   * why we have InputData and WidgetInputEvent separated. If this function is
+   * used, the controller thread must be the main thread, or undefined behaviour
+   * may occur.
+   * NOTE: On unix, mouse events are treated as touch and are forwarded
+   * to the appropriate apz as such.
+   *
+   * See documentation for other ReceiveInputEvent above.
+   */
+  nsEventStatus ReceiveInputEvent(
+      WidgetInputEvent& aEvent,
+      ScrollableLayerGuid* aOutTargetGuid,
+      uint64_t* aOutInputBlockId);
+
+  // Returns whether or not a wheel event action will be (or was) performed by
+  // APZ. If this returns true, the event must not perform a synchronous
+  // scroll.
+  //
+  // Even if this returns false, all wheel events in APZ-aware widgets must
+  // be sent through APZ so they are transformed correctly for TabParent.
+  static bool WillHandleWheelEvent(WidgetWheelEvent* aEvent);
+
+protected:
+  friend class APZInputBridgeParent;
+
+  // Methods to help process WidgetInputEvents (or manage conversion to/from InputData)
+
+  virtual void ProcessUnhandledEvent(
+      LayoutDeviceIntPoint* aRefPoint,
+      ScrollableLayerGuid* aOutTargetGuid,
+      uint64_t* aOutFocusSequenceNumber) = 0;
+
+  virtual void UpdateWheelTransaction(
+      LayoutDeviceIntPoint aRefPoint,
+      EventMessage aEventMessage) = 0;
+
+  virtual ~APZInputBridge() { }
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_APZInputBridge_h
--- a/gfx/layers/apz/public/IAPZCTreeManager.h
+++ b/gfx/layers/apz/public/IAPZCTreeManager.h
@@ -5,27 +5,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_layers_IAPZCTreeManager_h
 #define mozilla_layers_IAPZCTreeManager_h
 
 #include <stdint.h>                     // for uint64_t, uint32_t
 
 #include "FrameMetrics.h"               // for FrameMetrics, etc
-#include "mozilla/EventForwards.h"      // for WidgetInputEvent, nsEventStatus
-#include "mozilla/layers/LayersTypes.h"    // for TouchBehaviorFlags
+#include "mozilla/layers/LayersTypes.h" // for TouchBehaviorFlags
 #include "nsTArrayForwardDeclare.h"     // for nsTArray, nsTArray_Impl, etc
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
-#include "Units.h"                      // for CSSPoint, CSSRect, etc
+#include "Units.h"                      // for CSSRect, etc
 
 namespace mozilla {
-class InputData;
-
 namespace layers {
 
+class APZInputBridge;
 class KeyboardMap;
 
 enum AllowedTouchBehavior {
   NONE =               0,
   VERTICAL_PAN =       1 << 0,
   HORIZONTAL_PAN =     1 << 1,
   PINCH_ZOOM =         1 << 2,
   DOUBLE_TAP_ZOOM =    1 << 3,
@@ -40,77 +38,16 @@ enum ZoomToRectBehavior : uint32_t {
 };
 
 class AsyncDragMetrics;
 
 class IAPZCTreeManager {
   NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(IAPZCTreeManager)
 
 public:
-
-  /**
-   * General handler for incoming input events. Manipulates the frame metrics
-   * based on what type of input it is. For example, a PinchGestureEvent will
-   * cause scaling. This should only be called externally to this class, and
-   * must be called on the controller thread.
-   *
-   * This function transforms |aEvent| to have its coordinates in DOM space.
-   * This is so that the event can be passed through the DOM and content can
-   * handle them. The event may need to be converted to a WidgetInputEvent
-   * by the caller if it wants to do this.
-   *
-   * The following values may be returned by this function:
-   * nsEventStatus_eConsumeNoDefault is returned to indicate the
-   *   APZ is consuming this event and the caller should discard the event with
-   *   extreme prejudice. The exact scenarios under which this is returned is
-   *   implementation-dependent and may vary.
-   * nsEventStatus_eIgnore is returned to indicate that the APZ code didn't
-   *   use this event. This might be because it was directed at a point on
-   *   the screen where there was no APZ, or because the thing the user was
-   *   trying to do was not allowed. (For example, attempting to pan a
-   *   non-pannable document).
-   * nsEventStatus_eConsumeDoDefault is returned to indicate that the APZ
-   *   code may have used this event to do some user-visible thing. Note that
-   *   in some cases CONSUMED is returned even if the event was NOT used. This
-   *   is because we cannot always know at the time of event delivery whether
-   *   the event will be used or not. So we err on the side of sending
-   *   CONSUMED when we are uncertain.
-   *
-   * @param aEvent input event object; is modified in-place
-   * @param aOutTargetGuid returns the guid of the apzc this event was
-   * delivered to. May be null.
-   * @param aOutInputBlockId returns the id of the input block that this event
-   * was added to, if that was the case. May be null.
-   */
-  virtual nsEventStatus ReceiveInputEvent(
-      InputData& aEvent,
-      ScrollableLayerGuid* aOutTargetGuid,
-      uint64_t* aOutInputBlockId) = 0;
-
-  /**
-   * WidgetInputEvent handler. Transforms |aEvent| (which is assumed to be an
-   * already-existing instance of an WidgetInputEvent which may be an
-   * WidgetTouchEvent) to have its coordinates in DOM space. This is so that the
-   * event can be passed through the DOM and content can handle them.
-   *
-   * NOTE: Be careful of invoking the WidgetInputEvent variant. This can only be
-   * called on the main thread. See widget/InputData.h for more information on
-   * why we have InputData and WidgetInputEvent separated. If this function is
-   * used, the controller thread must be the main thread, or undefined behaviour
-   * may occur.
-   * NOTE: On unix, mouse events are treated as touch and are forwarded
-   * to the appropriate apz as such.
-   *
-   * See documentation for other ReceiveInputEvent above.
-   */
-  nsEventStatus ReceiveInputEvent(
-      WidgetInputEvent& aEvent,
-      ScrollableLayerGuid* aOutTargetGuid,
-      uint64_t* aOutInputBlockId);
-
   /**
    * Set the keyboard shortcuts to use for translating keyboard events.
    */
   virtual void SetKeyboardMap(const KeyboardMap& aKeyboardMap) = 0;
 
   /**
    * Kicks an animation to zoom to a rect. This may be either a zoom out or zoom
    * in. The actual animation is done on the sampler thread after being set
@@ -185,38 +122,27 @@ public:
   /**
    * Function used to disable LongTap gestures.
    *
    * On slow running tests, drags and touch events can be misinterpreted
    * as a long tap. This allows tests to disable long tap gesture detection.
    */
   virtual void SetLongTapEnabled(bool aTapGestureEnabled) = 0;
 
-
-  // Returns whether or not a wheel event action will be (or was) performed by
-  // APZ. If this returns true, the event must not perform a synchronous
-  // scroll.
-  //
-  // Even if this returns false, all wheel events in APZ-aware widgets must
-  // be sent through APZ so they are transformed correctly for TabParent.
-  static bool WillHandleWheelEvent(WidgetWheelEvent* aEvent);
+  /**
+   * Returns an APZInputBridge interface that can be used to send input
+   * events to APZ in a synchronous manner. This will always be non-null, and
+   * the returned object's lifetime will match the lifetime of this
+   * IAPZCTreeManager implementation.
+   * It is only valid to call this function in the UI process.
+   */
+  virtual APZInputBridge* InputBridge() = 0;
 
 protected:
 
-  // Methods to help process WidgetInputEvents (or manage conversion to/from InputData)
-
-  virtual void ProcessUnhandledEvent(
-      LayoutDeviceIntPoint* aRefPoint,
-      ScrollableLayerGuid* aOutTargetGuid,
-      uint64_t* aOutFocusSequenceNumber) = 0;
-
-  virtual void UpdateWheelTransaction(
-      LayoutDeviceIntPoint aRefPoint,
-      EventMessage aEventMessage) = 0;
-
   // Discourage destruction outside of decref
 
   virtual ~IAPZCTreeManager() { }
 };
 
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1072,17 +1072,17 @@ template<typename PanGestureOrScrollWhee
 static bool
 WillHandleInput(const PanGestureOrScrollWheelInput& aPanInput)
 {
   if (!XRE_IsParentProcess() || !NS_IsMainThread()) {
     return true;
   }
 
   WidgetWheelEvent wheelEvent = aPanInput.ToWidgetWheelEvent(nullptr);
-  return IAPZCTreeManager::WillHandleWheelEvent(&wheelEvent);
+  return APZInputBridge::WillHandleWheelEvent(&wheelEvent);
 }
 
 void
 APZCTreeManager::FlushApzRepaints(uint64_t aLayersId)
 {
   // Previously, paints were throttled and therefore this method was used to
   // ensure any pending paints were flushed. Now, paints are flushed
   // immediately, so it is safe to simply send a notification now.
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -10,16 +10,17 @@
 #include <unordered_map>                          // for std::unordered_map
 
 #include "FocusState.h"                 // for FocusState
 #include "gfxPoint.h"                   // for gfxPoint
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/gfx/CompositorHitTestInfo.h"
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
+#include "mozilla/layers/APZInputBridge.h" // for APZInputBridge
 #include "mozilla/layers/APZTestData.h" // for APZTestData
 #include "mozilla/layers/IAPZCTreeManager.h" // for IAPZCTreeManager
 #include "mozilla/layers/KeyboardMap.h" // for KeyboardMap
 #include "mozilla/RecursiveMutex.h"     // for RecursiveMutex
 #include "mozilla/RefPtr.h"             // for RefPtr
 #include "mozilla/TimeStamp.h"          // for mozilla::TimeStamp
 #include "mozilla/UniquePtr.h"          // for UniquePtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
@@ -94,17 +95,18 @@ struct ScrollThumbData;
  * user input events that drive panning and zooming, changes to the scroll viewport
  * area, and changes to pan/zoom constraints.
  *
  * Note that the ClearTree function MUST be called when this class is no longer needed;
  * see the method documentation for details.
  *
  * Behaviour of APZ is controlled by a number of preferences shown \ref APZCPrefs "here".
  */
-class APZCTreeManager : public IAPZCTreeManager {
+class APZCTreeManager : public IAPZCTreeManager
+                      , public APZInputBridge {
 
   typedef mozilla::layers::AllowedTouchBehavior AllowedTouchBehavior;
   typedef mozilla::layers::AsyncDragMetrics AsyncDragMetrics;
 
   // Helper struct to hold some state while we build the hit-testing tree. The
   // sole purpose of this struct is to shorten the argument list to
   // UpdateHitTestingTree. All the state that we don't need to
   // push on the stack during recursion and pop on unwind is stored here.
@@ -468,16 +470,18 @@ public:
   /**
    * Function used to disable LongTap gestures.
    *
    * On slow running tests, drags and touch events can be misinterpreted
    * as a long tap. This allows tests to disable long tap gesture detection.
    */
   void SetLongTapEnabled(bool aTapGestureEnabled) override;
 
+  APZInputBridge* InputBridge() override { return this; }
+
   // Methods to help process WidgetInputEvents (or manage conversion to/from InputData)
 
   void ProcessUnhandledEvent(
       LayoutDeviceIntPoint* aRefPoint,
       ScrollableLayerGuid*  aOutTargetGuid,
       uint64_t*             aOutFocusSequenceNumber) override;
 
   void UpdateWheelTransaction(
rename from gfx/layers/apz/public/IAPZCTreeManager.cpp
rename to gfx/layers/apz/src/APZInputBridge.cpp
--- a/gfx/layers/apz/public/IAPZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZInputBridge.cpp
@@ -1,19 +1,19 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/layers/APZInputBridge.h"
 
 #include "gfxPrefs.h"                       // for gfxPrefs
-#include "InputData.h"                      // for InputData, etc
-#include "mozilla/EventStateManager.h"      // for WheelPrefs
+#include "InputData.h"                      // for MouseInput, etc
+#include "mozilla/EventStateManager.h"      // for EventStateManager
 #include "mozilla/layers/APZThreadUtils.h"  // for AssertOnControllerThread, etc
 #include "mozilla/MouseEvents.h"            // for WidgetMouseEvent
 #include "mozilla/TextEvents.h"             // for WidgetKeyboardEvent
 #include "mozilla/TouchEvents.h"            // for WidgetTouchEvent
 #include "mozilla/WheelHandlingHelper.h"    // for AutoWheelDeltaAdjuster
 
 namespace mozilla {
 namespace layers {
@@ -24,26 +24,26 @@ WillHandleMouseEvent(const WidgetMouseEv
   return aEvent.mMessage == eMouseMove ||
          aEvent.mMessage == eMouseDown ||
          aEvent.mMessage == eMouseUp ||
          aEvent.mMessage == eDragEnd ||
          (gfxPrefs::TestEventsAsyncEnabled() && aEvent.mMessage == eMouseHitTest);
 }
 
 /* static */ bool
-IAPZCTreeManager::WillHandleWheelEvent(WidgetWheelEvent* aEvent)
+APZInputBridge::WillHandleWheelEvent(WidgetWheelEvent* aEvent)
 {
   return EventStateManager::WheelEventIsScrollAction(aEvent) &&
          (aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_LINE ||
           aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL ||
           aEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PAGE);
 }
 
 nsEventStatus
-IAPZCTreeManager::ReceiveInputEvent(
+APZInputBridge::ReceiveInputEvent(
     WidgetInputEvent& aEvent,
     ScrollableLayerGuid* aOutTargetGuid,
     uint64_t* aOutInputBlockId)
 {
   APZThreadUtils::AssertOnControllerThread();
 
   // Initialize aOutInputBlockId to a sane value, and then later we overwrite
   // it if the input event goes into a block.
--- a/gfx/layers/ipc/APZCTreeManagerChild.cpp
+++ b/gfx/layers/ipc/APZCTreeManagerChild.cpp
@@ -4,144 +4,57 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/APZCTreeManagerChild.h"
 
 #include "InputData.h"                  // for InputData
 #include "mozilla/dom/TabParent.h"      // for TabParent
 #include "mozilla/layers/APZCCallbackHelper.h" // for APZCCallbackHelper
+#include "mozilla/layers/APZInputBridgeChild.h" // for APZInputBridgeChild
 #include "mozilla/layers/RemoteCompositorSession.h" // for RemoteCompositorSession
 
 namespace mozilla {
 namespace layers {
 
 APZCTreeManagerChild::APZCTreeManagerChild()
   : mCompositorSession(nullptr)
 {
 }
 
+APZCTreeManagerChild::~APZCTreeManagerChild()
+{
+}
+
 void
 APZCTreeManagerChild::SetCompositorSession(RemoteCompositorSession* aSession)
 {
   // Exactly one of mCompositorSession and aSession must be null (i.e. either
   // we're setting mCompositorSession or we're clearing it).
   MOZ_ASSERT(!mCompositorSession ^ !aSession);
   mCompositorSession = aSession;
 }
 
-nsEventStatus
-APZCTreeManagerChild::ReceiveInputEvent(
-    InputData& aEvent,
-    ScrollableLayerGuid* aOutTargetGuid,
-    uint64_t* aOutInputBlockId)
+void
+APZCTreeManagerChild::SetInputBridge(APZInputBridgeChild* aInputBridge)
 {
-  switch (aEvent.mInputType) {
-  case MULTITOUCH_INPUT: {
-    MultiTouchInput& event = aEvent.AsMultiTouchInput();
-    MultiTouchInput processedEvent;
-
-    nsEventStatus res;
-    SendReceiveMultiTouchInputEvent(event,
-                                    &res,
-                                    &processedEvent,
-                                    aOutTargetGuid,
-                                    aOutInputBlockId);
-
-    event = processedEvent;
-    return res;
-  }
-  case MOUSE_INPUT: {
-    MouseInput& event = aEvent.AsMouseInput();
-    MouseInput processedEvent;
-
-    nsEventStatus res;
-    SendReceiveMouseInputEvent(event,
-                               &res,
-                               &processedEvent,
-                               aOutTargetGuid,
-                               aOutInputBlockId);
-
-    event = processedEvent;
-    return res;
-  }
-  case PANGESTURE_INPUT: {
-    PanGestureInput& event = aEvent.AsPanGestureInput();
-    PanGestureInput processedEvent;
-
-    nsEventStatus res;
-    SendReceivePanGestureInputEvent(event,
-                                    &res,
-                                    &processedEvent,
-                                    aOutTargetGuid,
-                                    aOutInputBlockId);
-
-    event = processedEvent;
-    return res;
-  }
-  case PINCHGESTURE_INPUT: {
-    PinchGestureInput& event = aEvent.AsPinchGestureInput();
-    PinchGestureInput processedEvent;
+  // The input bridge only exists from the UI process to the GPU process.
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!mInputBridge);
 
-    nsEventStatus res;
-    SendReceivePinchGestureInputEvent(event,
-                                      &res,
-                                      &processedEvent,
-                                      aOutTargetGuid,
-                                      aOutInputBlockId);
-
-    event = processedEvent;
-    return res;
-  }
-  case TAPGESTURE_INPUT: {
-    TapGestureInput& event = aEvent.AsTapGestureInput();
-    TapGestureInput processedEvent;
-
-    nsEventStatus res;
-    SendReceiveTapGestureInputEvent(event,
-                                    &res,
-                                    &processedEvent,
-                                    aOutTargetGuid,
-                                    aOutInputBlockId);
-
-    event = processedEvent;
-    return res;
-  }
-  case SCROLLWHEEL_INPUT: {
-    ScrollWheelInput& event = aEvent.AsScrollWheelInput();
-    ScrollWheelInput processedEvent;
+  mInputBridge = aInputBridge;
+}
 
-    nsEventStatus res;
-    SendReceiveScrollWheelInputEvent(event,
-                                     &res,
-                                     &processedEvent,
-                                     aOutTargetGuid,
-                                     aOutInputBlockId);
-
-    event = processedEvent;
-    return res;
-  }
-  case KEYBOARD_INPUT: {
-    KeyboardInput& event = aEvent.AsKeyboardInput();
-    KeyboardInput processedEvent;
-
-    nsEventStatus res;
-    SendReceiveKeyboardInputEvent(event,
-                                  &res,
-                                  &processedEvent,
-                                  aOutTargetGuid,
-                                  aOutInputBlockId);
-
-    event = processedEvent;
-    return res;
-  }
-  default: {
-    MOZ_ASSERT_UNREACHABLE("Invalid InputData type.");
-    return nsEventStatus_eConsumeNoDefault;
-  }
+void
+APZCTreeManagerChild::Destroy()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mInputBridge) {
+    mInputBridge->Destroy();
+    mInputBridge = nullptr;
   }
 }
 
 void
 APZCTreeManagerChild::SetKeyboardMap(const KeyboardMap& aKeyboardMap)
 {
   SendSetKeyboardMap(aKeyboardMap);
 }
@@ -216,33 +129,23 @@ APZCTreeManagerChild::StopAutoscroll(con
 }
 
 void
 APZCTreeManagerChild::SetLongTapEnabled(bool aTapGestureEnabled)
 {
   SendSetLongTapEnabled(aTapGestureEnabled);
 }
 
-void
-APZCTreeManagerChild::UpdateWheelTransaction(
-    LayoutDeviceIntPoint aRefPoint,
-    EventMessage aEventMessage)
+APZInputBridge*
+APZCTreeManagerChild::InputBridge()
 {
-  SendUpdateWheelTransaction(aRefPoint, aEventMessage);
-}
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(mInputBridge);
 
-void APZCTreeManagerChild::ProcessUnhandledEvent(
-    LayoutDeviceIntPoint* aRefPoint,
-    ScrollableLayerGuid*  aOutTargetGuid,
-    uint64_t*             aOutFocusSequenceNumber)
-{
-  SendProcessUnhandledEvent(*aRefPoint,
-                            aRefPoint,
-                            aOutTargetGuid,
-                            aOutFocusSequenceNumber);
+  return mInputBridge.get();
 }
 
 mozilla::ipc::IPCResult
 APZCTreeManagerChild::RecvHandleTap(const TapType& aType,
                                     const LayoutDevicePoint& aPoint,
                                     const Modifiers& aModifiers,
                                     const ScrollableLayerGuid& aGuid,
                                     const uint64_t& aInputBlockId)
--- a/gfx/layers/ipc/APZCTreeManagerChild.h
+++ b/gfx/layers/ipc/APZCTreeManagerChild.h
@@ -2,38 +2,36 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_layers_APZCTreeManagerChild_h
 #define mozilla_layers_APZCTreeManagerChild_h
 
+#include "mozilla/layers/APZInputBridge.h"
 #include "mozilla/layers/IAPZCTreeManager.h"
 #include "mozilla/layers/PAPZCTreeManagerChild.h"
 
 namespace mozilla {
 namespace layers {
 
+class APZInputBridgeChild;
 class RemoteCompositorSession;
 
 class APZCTreeManagerChild
   : public IAPZCTreeManager
   , public PAPZCTreeManagerChild
 {
 public:
   APZCTreeManagerChild();
 
   void SetCompositorSession(RemoteCompositorSession* aSession);
-
-  nsEventStatus
-  ReceiveInputEvent(
-          InputData& aEvent,
-          ScrollableLayerGuid* aOutTargetGuid,
-          uint64_t* aOutInputBlockId) override;
+  void SetInputBridge(APZInputBridgeChild* aInputBridge);
+  void Destroy();
 
   void
   SetKeyboardMap(const KeyboardMap& aKeyboardMap) override;
 
   void
   ZoomToRect(
           const ScrollableLayerGuid& aGuid,
           const CSSRect& aRect,
@@ -73,44 +71,36 @@ public:
           const ScreenPoint& aAnchorLocation) override;
 
   void
   StopAutoscroll(const ScrollableLayerGuid& aGuid) override;
 
   void
   SetLongTapEnabled(bool aTapGestureEnabled) override;
 
-  void
-  ProcessUnhandledEvent(
-          LayoutDeviceIntPoint* aRefPoint,
-          ScrollableLayerGuid*  aOutTargetGuid,
-          uint64_t*             aOutFocusSequenceNumber) override;
-
-  void
-  UpdateWheelTransaction(
-          LayoutDeviceIntPoint aRefPoint,
-          EventMessage aEventMessage) override;
+  APZInputBridge*
+  InputBridge() override;
 
 protected:
   mozilla::ipc::IPCResult RecvHandleTap(const TapType& aType,
                                         const LayoutDevicePoint& aPoint,
                                         const Modifiers& aModifiers,
                                         const ScrollableLayerGuid& aGuid,
                                         const uint64_t& aInputBlockId) override;
 
   mozilla::ipc::IPCResult RecvNotifyPinchGesture(const PinchGestureType& aType,
                                                  const ScrollableLayerGuid& aGuid,
                                                  const LayoutDeviceCoord& aSpanChange,
                                                  const Modifiers& aModifiers) override;
 
   mozilla::ipc::IPCResult RecvCancelAutoscroll(const FrameMetrics::ViewID& aScrollId) override;
 
-  virtual
-  ~APZCTreeManagerChild() { }
+  virtual ~APZCTreeManagerChild();
 
 private:
   MOZ_NON_OWNING_REF RemoteCompositorSession* mCompositorSession;
+  RefPtr<APZInputBridgeChild> mInputBridge;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_APZCTreeManagerChild_h
--- a/gfx/layers/ipc/APZCTreeManagerParent.cpp
+++ b/gfx/layers/ipc/APZCTreeManagerParent.cpp
@@ -26,149 +26,16 @@ APZCTreeManagerParent::~APZCTreeManagerP
 void
 APZCTreeManagerParent::ChildAdopted(RefPtr<APZCTreeManager> aAPZCTreeManager)
 {
   MOZ_ASSERT(aAPZCTreeManager != nullptr);
   mTreeManager = aAPZCTreeManager;
 }
 
 mozilla::ipc::IPCResult
-APZCTreeManagerParent::RecvReceiveMultiTouchInputEvent(
-    const MultiTouchInput& aEvent,
-    nsEventStatus* aOutStatus,
-    MultiTouchInput* aOutEvent,
-    ScrollableLayerGuid* aOutTargetGuid,
-    uint64_t* aOutInputBlockId)
-{
-  MultiTouchInput event = aEvent;
-
-  *aOutStatus = mTreeManager->ReceiveInputEvent(
-    event,
-    aOutTargetGuid,
-    aOutInputBlockId);
-  *aOutEvent = event;
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-APZCTreeManagerParent::RecvReceiveMouseInputEvent(
-    const MouseInput& aEvent,
-    nsEventStatus* aOutStatus,
-    MouseInput* aOutEvent,
-    ScrollableLayerGuid* aOutTargetGuid,
-    uint64_t* aOutInputBlockId)
-{
-  MouseInput event = aEvent;
-
-  *aOutStatus = mTreeManager->ReceiveInputEvent(
-    event,
-    aOutTargetGuid,
-    aOutInputBlockId);
-  *aOutEvent = event;
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-APZCTreeManagerParent::RecvReceivePanGestureInputEvent(
-    const PanGestureInput& aEvent,
-    nsEventStatus* aOutStatus,
-    PanGestureInput* aOutEvent,
-    ScrollableLayerGuid* aOutTargetGuid,
-    uint64_t* aOutInputBlockId)
-{
-  PanGestureInput event = aEvent;
-
-  *aOutStatus = mTreeManager->ReceiveInputEvent(
-    event,
-    aOutTargetGuid,
-    aOutInputBlockId);
-  *aOutEvent = event;
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-APZCTreeManagerParent::RecvReceivePinchGestureInputEvent(
-    const PinchGestureInput& aEvent,
-    nsEventStatus* aOutStatus,
-    PinchGestureInput* aOutEvent,
-    ScrollableLayerGuid* aOutTargetGuid,
-    uint64_t* aOutInputBlockId)
-{
-  PinchGestureInput event = aEvent;
-
-  *aOutStatus = mTreeManager->ReceiveInputEvent(
-    event,
-    aOutTargetGuid,
-    aOutInputBlockId);
-  *aOutEvent = event;
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-APZCTreeManagerParent::RecvReceiveTapGestureInputEvent(
-    const TapGestureInput& aEvent,
-    nsEventStatus* aOutStatus,
-    TapGestureInput* aOutEvent,
-    ScrollableLayerGuid* aOutTargetGuid,
-    uint64_t* aOutInputBlockId)
-{
-  TapGestureInput event = aEvent;
-
-  *aOutStatus = mTreeManager->ReceiveInputEvent(
-    event,
-    aOutTargetGuid,
-    aOutInputBlockId);
-  *aOutEvent = event;
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-APZCTreeManagerParent::RecvReceiveScrollWheelInputEvent(
-    const ScrollWheelInput& aEvent,
-    nsEventStatus* aOutStatus,
-    ScrollWheelInput* aOutEvent,
-    ScrollableLayerGuid* aOutTargetGuid,
-    uint64_t* aOutInputBlockId)
-{
-  ScrollWheelInput event = aEvent;
-
-  *aOutStatus = mTreeManager->ReceiveInputEvent(
-    event,
-    aOutTargetGuid,
-    aOutInputBlockId);
-  *aOutEvent = event;
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-APZCTreeManagerParent::RecvReceiveKeyboardInputEvent(
-        const KeyboardInput& aEvent,
-        nsEventStatus* aOutStatus,
-        KeyboardInput* aOutEvent,
-        ScrollableLayerGuid* aOutTargetGuid,
-        uint64_t* aOutInputBlockId)
-{
-  KeyboardInput event = aEvent;
-
-  *aOutStatus = mTreeManager->ReceiveInputEvent(
-    event,
-    aOutTargetGuid,
-    aOutInputBlockId);
-  *aOutEvent = event;
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
 APZCTreeManagerParent::RecvSetKeyboardMap(const KeyboardMap& aKeyboardMap)
 {
   APZThreadUtils::RunOnControllerThread(NewRunnableMethod<KeyboardMap>(
     "layers::IAPZCTreeManager::SetKeyboardMap",
     mTreeManager,
     &IAPZCTreeManager::SetKeyboardMap,
     aKeyboardMap));
 
@@ -339,33 +206,10 @@ APZCTreeManagerParent::RecvStopAutoscrol
 
 mozilla::ipc::IPCResult
 APZCTreeManagerParent::RecvSetLongTapEnabled(const bool& aTapGestureEnabled)
 {
   mTreeManager->SetLongTapEnabled(aTapGestureEnabled);
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult
-APZCTreeManagerParent::RecvUpdateWheelTransaction(
-        const LayoutDeviceIntPoint& aRefPoint,
-        const EventMessage& aEventMessage)
-{
-  mTreeManager->UpdateWheelTransaction(aRefPoint, aEventMessage);
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-APZCTreeManagerParent::RecvProcessUnhandledEvent(
-        const LayoutDeviceIntPoint& aRefPoint,
-        LayoutDeviceIntPoint* aOutRefPoint,
-        ScrollableLayerGuid*  aOutTargetGuid,
-        uint64_t*             aOutFocusSequenceNumber)
-{
-  LayoutDeviceIntPoint refPoint = aRefPoint;
-  mTreeManager->ProcessUnhandledEvent(&refPoint, aOutTargetGuid, aOutFocusSequenceNumber);
-  *aOutRefPoint = refPoint;
-
-  return IPC_OK();
-}
-
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/APZCTreeManagerParent.h
+++ b/gfx/layers/ipc/APZCTreeManagerParent.h
@@ -26,72 +26,16 @@ public:
 
   /**
    * Called when the layer tree that this protocol is connected to
    * is adopted by another compositor, and we need to switch APZCTreeManagers.
    */
   void ChildAdopted(RefPtr<APZCTreeManager> aAPZCTreeManager);
 
   mozilla::ipc::IPCResult
-  RecvReceiveMultiTouchInputEvent(
-          const MultiTouchInput& aEvent,
-          nsEventStatus* aOutStatus,
-          MultiTouchInput* aOutEvent,
-          ScrollableLayerGuid* aOutTargetGuid,
-          uint64_t* aOutInputBlockId) override;
-
-  mozilla::ipc::IPCResult
-  RecvReceiveMouseInputEvent(
-          const MouseInput& aEvent,
-          nsEventStatus* aOutStatus,
-          MouseInput* aOutEvent,
-          ScrollableLayerGuid* aOutTargetGuid,
-          uint64_t* aOutInputBlockId) override;
-
-  mozilla::ipc::IPCResult
-  RecvReceivePanGestureInputEvent(
-          const PanGestureInput& aEvent,
-          nsEventStatus* aOutStatus,
-          PanGestureInput* aOutEvent,
-          ScrollableLayerGuid* aOutTargetGuid,
-          uint64_t* aOutInputBlockId) override;
-
-  mozilla::ipc::IPCResult
-  RecvReceivePinchGestureInputEvent(
-          const PinchGestureInput& aEvent,
-          nsEventStatus* aOutStatus,
-          PinchGestureInput* aOutEvent,
-          ScrollableLayerGuid* aOutTargetGuid,
-          uint64_t* aOutInputBlockId) override;
-
-  mozilla::ipc::IPCResult
-  RecvReceiveTapGestureInputEvent(
-          const TapGestureInput& aEvent,
-          nsEventStatus* aOutStatus,
-          TapGestureInput* aOutEvent,
-          ScrollableLayerGuid* aOutTargetGuid,
-          uint64_t* aOutInputBlockId) override;
-
-  mozilla::ipc::IPCResult
-  RecvReceiveScrollWheelInputEvent(
-          const ScrollWheelInput& aEvent,
-          nsEventStatus* aOutStatus,
-          ScrollWheelInput* aOutEvent,
-          ScrollableLayerGuid* aOutTargetGuid,
-          uint64_t* aOutInputBlockId) override;
-
-  mozilla::ipc::IPCResult
-  RecvReceiveKeyboardInputEvent(
-          const KeyboardInput& aEvent,
-          nsEventStatus* aOutStatus,
-          KeyboardInput* aOutEvent,
-          ScrollableLayerGuid* aOutTargetGuid,
-          uint64_t* aOutInputBlockId) override;
-
-  mozilla::ipc::IPCResult
   RecvSetKeyboardMap(const KeyboardMap& aKeyboardMap) override;
 
   mozilla::ipc::IPCResult
   RecvZoomToRect(
           const ScrollableLayerGuid& aGuid,
           const CSSRect& aRect,
           const uint32_t& aFlags) override;
 
@@ -129,28 +73,16 @@ public:
           const ScreenPoint& aAnchorLocation) override;
 
   mozilla::ipc::IPCResult
   RecvStopAutoscroll(const ScrollableLayerGuid& aGuid) override;
 
   mozilla::ipc::IPCResult
   RecvSetLongTapEnabled(const bool& aTapGestureEnabled) override;
 
-  mozilla::ipc::IPCResult
-  RecvUpdateWheelTransaction(
-          const LayoutDeviceIntPoint& aRefPoint,
-          const EventMessage& aEventMessage) override;
-
-  mozilla::ipc::IPCResult
-  RecvProcessUnhandledEvent(
-          const LayoutDeviceIntPoint& aRefPoint,
-          LayoutDeviceIntPoint* aOutRefPoint,
-          ScrollableLayerGuid*  aOutTargetGuid,
-          uint64_t*             aOutFocusSequenceNumber) override;
-
   void
   ActorDestroy(ActorDestroyReason aWhy) override { }
 
 private:
   uint64_t mLayersId;
   RefPtr<APZCTreeManager> mTreeManager;
 };
 
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/APZInputBridgeChild.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/APZInputBridgeChild.h"
+
+namespace mozilla {
+namespace layers {
+
+APZInputBridgeChild::APZInputBridgeChild()
+  : mDestroyed(false)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+}
+
+APZInputBridgeChild::~APZInputBridgeChild()
+{
+}
+
+void
+APZInputBridgeChild::Destroy()
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mDestroyed) {
+    return;
+  }
+
+  Send__delete__(this);
+  mDestroyed = true;
+}
+
+void
+APZInputBridgeChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mDestroyed = true;
+}
+
+nsEventStatus
+APZInputBridgeChild::ReceiveInputEvent(
+    InputData& aEvent,
+    ScrollableLayerGuid* aOutTargetGuid,
+    uint64_t* aOutInputBlockId)
+{
+  switch (aEvent.mInputType) {
+  case MULTITOUCH_INPUT: {
+    MultiTouchInput& event = aEvent.AsMultiTouchInput();
+    MultiTouchInput processedEvent;
+
+    nsEventStatus res;
+    SendReceiveMultiTouchInputEvent(event,
+                                    &res,
+                                    &processedEvent,
+                                    aOutTargetGuid,
+                                    aOutInputBlockId);
+
+    event = processedEvent;
+    return res;
+  }
+  case MOUSE_INPUT: {
+    MouseInput& event = aEvent.AsMouseInput();
+    MouseInput processedEvent;
+
+    nsEventStatus res;
+    SendReceiveMouseInputEvent(event,
+                               &res,
+                               &processedEvent,
+                               aOutTargetGuid,
+                               aOutInputBlockId);
+
+    event = processedEvent;
+    return res;
+  }
+  case PANGESTURE_INPUT: {
+    PanGestureInput& event = aEvent.AsPanGestureInput();
+    PanGestureInput processedEvent;
+
+    nsEventStatus res;
+    SendReceivePanGestureInputEvent(event,
+                                    &res,
+                                    &processedEvent,
+                                    aOutTargetGuid,
+                                    aOutInputBlockId);
+
+    event = processedEvent;
+    return res;
+  }
+  case PINCHGESTURE_INPUT: {
+    PinchGestureInput& event = aEvent.AsPinchGestureInput();
+    PinchGestureInput processedEvent;
+
+    nsEventStatus res;
+    SendReceivePinchGestureInputEvent(event,
+                                      &res,
+                                      &processedEvent,
+                                      aOutTargetGuid,
+                                      aOutInputBlockId);
+
+    event = processedEvent;
+    return res;
+  }
+  case TAPGESTURE_INPUT: {
+    TapGestureInput& event = aEvent.AsTapGestureInput();
+    TapGestureInput processedEvent;
+
+    nsEventStatus res;
+    SendReceiveTapGestureInputEvent(event,
+                                    &res,
+                                    &processedEvent,
+                                    aOutTargetGuid,
+                                    aOutInputBlockId);
+
+    event = processedEvent;
+    return res;
+  }
+  case SCROLLWHEEL_INPUT: {
+    ScrollWheelInput& event = aEvent.AsScrollWheelInput();
+    ScrollWheelInput processedEvent;
+
+    nsEventStatus res;
+    SendReceiveScrollWheelInputEvent(event,
+                                     &res,
+                                     &processedEvent,
+                                     aOutTargetGuid,
+                                     aOutInputBlockId);
+
+    event = processedEvent;
+    return res;
+  }
+  case KEYBOARD_INPUT: {
+    KeyboardInput& event = aEvent.AsKeyboardInput();
+    KeyboardInput processedEvent;
+
+    nsEventStatus res;
+    SendReceiveKeyboardInputEvent(event,
+                                  &res,
+                                  &processedEvent,
+                                  aOutTargetGuid,
+                                  aOutInputBlockId);
+
+    event = processedEvent;
+    return res;
+  }
+  default: {
+    MOZ_ASSERT_UNREACHABLE("Invalid InputData type.");
+    return nsEventStatus_eConsumeNoDefault;
+  }
+  }
+}
+
+void APZInputBridgeChild::ProcessUnhandledEvent(
+    LayoutDeviceIntPoint* aRefPoint,
+    ScrollableLayerGuid*  aOutTargetGuid,
+    uint64_t*             aOutFocusSequenceNumber)
+{
+  SendProcessUnhandledEvent(*aRefPoint,
+                            aRefPoint,
+                            aOutTargetGuid,
+                            aOutFocusSequenceNumber);
+}
+
+void
+APZInputBridgeChild::UpdateWheelTransaction(
+    LayoutDeviceIntPoint aRefPoint,
+    EventMessage aEventMessage)
+{
+  SendUpdateWheelTransaction(aRefPoint, aEventMessage);
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/APZInputBridgeChild.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_APZInputBridgeChild_h
+#define mozilla_layers_APZInputBridgeChild_h
+
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/PAPZInputBridgeChild.h"
+
+namespace mozilla {
+namespace layers {
+
+class APZInputBridgeChild : public PAPZInputBridgeChild
+                          , public APZInputBridge
+{
+  NS_INLINE_DECL_REFCOUNTING(APZInputBridgeChild)
+
+public:
+  APZInputBridgeChild();
+  void Destroy();
+
+  nsEventStatus ReceiveInputEvent(
+      InputData& aEvent,
+      ScrollableLayerGuid* aOutTargetGuid,
+      uint64_t* aOutInputBlockId) override;
+
+protected:
+  void ProcessUnhandledEvent(
+      LayoutDeviceIntPoint* aRefPoint,
+      ScrollableLayerGuid* aOutTargetGuid,
+      uint64_t* aOutFocusSequenceNumber) override;
+
+  void UpdateWheelTransaction(
+      LayoutDeviceIntPoint aRefPoint,
+      EventMessage aEventMessage) override;
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+  virtual ~APZInputBridgeChild();
+
+private:
+  bool mDestroyed;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_APZInputBridgeChild_h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/APZInputBridgeParent.cpp
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/APZInputBridgeParent.h"
+
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+
+namespace mozilla {
+namespace layers {
+
+APZInputBridgeParent::APZInputBridgeParent(const uint64_t& aLayersId)
+{
+  MOZ_ASSERT(XRE_IsGPUProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mTreeManager = CompositorBridgeParent::GetAPZCTreeManager(aLayersId);
+  MOZ_ASSERT(mTreeManager);
+}
+
+APZInputBridgeParent::~APZInputBridgeParent()
+{
+}
+
+mozilla::ipc::IPCResult
+APZInputBridgeParent::RecvReceiveMultiTouchInputEvent(
+    const MultiTouchInput& aEvent,
+    nsEventStatus* aOutStatus,
+    MultiTouchInput* aOutEvent,
+    ScrollableLayerGuid* aOutTargetGuid,
+    uint64_t* aOutInputBlockId)
+{
+  MultiTouchInput event = aEvent;
+
+  *aOutStatus = mTreeManager->InputBridge()->ReceiveInputEvent(
+    event,
+    aOutTargetGuid,
+    aOutInputBlockId);
+  *aOutEvent = event;
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+APZInputBridgeParent::RecvReceiveMouseInputEvent(
+    const MouseInput& aEvent,
+    nsEventStatus* aOutStatus,
+    MouseInput* aOutEvent,
+    ScrollableLayerGuid* aOutTargetGuid,
+    uint64_t* aOutInputBlockId)
+{
+  MouseInput event = aEvent;
+
+  *aOutStatus = mTreeManager->InputBridge()->ReceiveInputEvent(
+    event,
+    aOutTargetGuid,
+    aOutInputBlockId);
+  *aOutEvent = event;
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+APZInputBridgeParent::RecvReceivePanGestureInputEvent(
+    const PanGestureInput& aEvent,
+    nsEventStatus* aOutStatus,
+    PanGestureInput* aOutEvent,
+    ScrollableLayerGuid* aOutTargetGuid,
+    uint64_t* aOutInputBlockId)
+{
+  PanGestureInput event = aEvent;
+
+  *aOutStatus = mTreeManager->InputBridge()->ReceiveInputEvent(
+    event,
+    aOutTargetGuid,
+    aOutInputBlockId);
+  *aOutEvent = event;
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+APZInputBridgeParent::RecvReceivePinchGestureInputEvent(
+    const PinchGestureInput& aEvent,
+    nsEventStatus* aOutStatus,
+    PinchGestureInput* aOutEvent,
+    ScrollableLayerGuid* aOutTargetGuid,
+    uint64_t* aOutInputBlockId)
+{
+  PinchGestureInput event = aEvent;
+
+  *aOutStatus = mTreeManager->InputBridge()->ReceiveInputEvent(
+    event,
+    aOutTargetGuid,
+    aOutInputBlockId);
+  *aOutEvent = event;
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+APZInputBridgeParent::RecvReceiveTapGestureInputEvent(
+    const TapGestureInput& aEvent,
+    nsEventStatus* aOutStatus,
+    TapGestureInput* aOutEvent,
+    ScrollableLayerGuid* aOutTargetGuid,
+    uint64_t* aOutInputBlockId)
+{
+  TapGestureInput event = aEvent;
+
+  *aOutStatus = mTreeManager->InputBridge()->ReceiveInputEvent(
+    event,
+    aOutTargetGuid,
+    aOutInputBlockId);
+  *aOutEvent = event;
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+APZInputBridgeParent::RecvReceiveScrollWheelInputEvent(
+    const ScrollWheelInput& aEvent,
+    nsEventStatus* aOutStatus,
+    ScrollWheelInput* aOutEvent,
+    ScrollableLayerGuid* aOutTargetGuid,
+    uint64_t* aOutInputBlockId)
+{
+  ScrollWheelInput event = aEvent;
+
+  *aOutStatus = mTreeManager->InputBridge()->ReceiveInputEvent(
+    event,
+    aOutTargetGuid,
+    aOutInputBlockId);
+  *aOutEvent = event;
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+APZInputBridgeParent::RecvReceiveKeyboardInputEvent(
+        const KeyboardInput& aEvent,
+        nsEventStatus* aOutStatus,
+        KeyboardInput* aOutEvent,
+        ScrollableLayerGuid* aOutTargetGuid,
+        uint64_t* aOutInputBlockId)
+{
+  KeyboardInput event = aEvent;
+
+  *aOutStatus = mTreeManager->InputBridge()->ReceiveInputEvent(
+    event,
+    aOutTargetGuid,
+    aOutInputBlockId);
+  *aOutEvent = event;
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+APZInputBridgeParent::RecvUpdateWheelTransaction(
+        const LayoutDeviceIntPoint& aRefPoint,
+        const EventMessage& aEventMessage)
+{
+  mTreeManager->InputBridge()->UpdateWheelTransaction(
+        aRefPoint, aEventMessage);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+APZInputBridgeParent::RecvProcessUnhandledEvent(
+        const LayoutDeviceIntPoint& aRefPoint,
+        LayoutDeviceIntPoint* aOutRefPoint,
+        ScrollableLayerGuid*  aOutTargetGuid,
+        uint64_t*             aOutFocusSequenceNumber)
+{
+  LayoutDeviceIntPoint refPoint = aRefPoint;
+  mTreeManager->InputBridge()->ProcessUnhandledEvent(
+        &refPoint, aOutTargetGuid, aOutFocusSequenceNumber);
+  *aOutRefPoint = refPoint;
+
+  return IPC_OK();
+}
+
+void
+APZInputBridgeParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  // We shouldn't need it after this
+  mTreeManager = nullptr;
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/APZInputBridgeParent.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_layers_APZInputBridgeParent_h
+#define mozilla_layers_APZInputBridgeParent_h
+
+#include "mozilla/layers/PAPZInputBridgeParent.h"
+
+namespace mozilla {
+namespace layers {
+
+class IAPZCTreeManager;
+
+class APZInputBridgeParent : public PAPZInputBridgeParent
+{
+  NS_INLINE_DECL_REFCOUNTING(APZInputBridgeParent)
+
+public:
+  explicit APZInputBridgeParent(const uint64_t& aLayersId);
+
+  mozilla::ipc::IPCResult
+  RecvReceiveMultiTouchInputEvent(
+          const MultiTouchInput& aEvent,
+          nsEventStatus* aOutStatus,
+          MultiTouchInput* aOutEvent,
+          ScrollableLayerGuid* aOutTargetGuid,
+          uint64_t* aOutInputBlockId) override;
+
+  mozilla::ipc::IPCResult
+  RecvReceiveMouseInputEvent(
+          const MouseInput& aEvent,
+          nsEventStatus* aOutStatus,
+          MouseInput* aOutEvent,
+          ScrollableLayerGuid* aOutTargetGuid,
+          uint64_t* aOutInputBlockId) override;
+
+  mozilla::ipc::IPCResult
+  RecvReceivePanGestureInputEvent(
+          const PanGestureInput& aEvent,
+          nsEventStatus* aOutStatus,
+          PanGestureInput* aOutEvent,
+          ScrollableLayerGuid* aOutTargetGuid,
+          uint64_t* aOutInputBlockId) override;
+
+  mozilla::ipc::IPCResult
+  RecvReceivePinchGestureInputEvent(
+          const PinchGestureInput& aEvent,
+          nsEventStatus* aOutStatus,
+          PinchGestureInput* aOutEvent,
+          ScrollableLayerGuid* aOutTargetGuid,
+          uint64_t* aOutInputBlockId) override;
+
+  mozilla::ipc::IPCResult
+  RecvReceiveTapGestureInputEvent(
+          const TapGestureInput& aEvent,
+          nsEventStatus* aOutStatus,
+          TapGestureInput* aOutEvent,
+          ScrollableLayerGuid* aOutTargetGuid,
+          uint64_t* aOutInputBlockId) override;
+
+  mozilla::ipc::IPCResult
+  RecvReceiveScrollWheelInputEvent(
+          const ScrollWheelInput& aEvent,
+          nsEventStatus* aOutStatus,
+          ScrollWheelInput* aOutEvent,
+          ScrollableLayerGuid* aOutTargetGuid,
+          uint64_t* aOutInputBlockId) override;
+
+  mozilla::ipc::IPCResult
+  RecvReceiveKeyboardInputEvent(
+          const KeyboardInput& aEvent,
+          nsEventStatus* aOutStatus,
+          KeyboardInput* aOutEvent,
+          ScrollableLayerGuid* aOutTargetGuid,
+          uint64_t* aOutInputBlockId) override;
+
+  mozilla::ipc::IPCResult
+  RecvUpdateWheelTransaction(
+          const LayoutDeviceIntPoint& aRefPoint,
+          const EventMessage& aEventMessage) override;
+
+  mozilla::ipc::IPCResult
+  RecvProcessUnhandledEvent(
+          const LayoutDeviceIntPoint& aRefPoint,
+          LayoutDeviceIntPoint* aOutRefPoint,
+          ScrollableLayerGuid*  aOutTargetGuid,
+          uint64_t*             aOutFocusSequenceNumber) override;
+
+  void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+protected:
+  virtual ~APZInputBridgeParent();
+
+private:
+  RefPtr<IAPZCTreeManager> mTreeManager;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // mozilla_layers_APZInputBridgeParent_h
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -352,39 +352,44 @@ void
 CompositorBridgeParent::InitSameProcess(widget::CompositorWidget* aWidget,
                                         const uint64_t& aLayerTreeId)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(NS_IsMainThread());
 
   mWidget = aWidget;
   mRootLayerTreeID = aLayerTreeId;
-  if (mOptions.UseAPZ()) {
-    mApzcTreeManager = new APZCTreeManager(mRootLayerTreeID);
-    mApzSampler = new APZSampler(mApzcTreeManager);
-  }
 
   Initialize();
 }
 
 mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvInitialize(const uint64_t& aRootLayerTreeId)
 {
+  MOZ_ASSERT(XRE_IsGPUProcess());
+
   mRootLayerTreeID = aRootLayerTreeId;
 
   Initialize();
   return IPC_OK();
 }
 
 void
 CompositorBridgeParent::Initialize()
 {
   MOZ_ASSERT(CompositorThread(),
              "The compositor thread must be Initialized before instanciating a CompositorBridgeParent.");
 
+  if (mOptions.UseAPZ()) {
+    MOZ_ASSERT(!mApzcTreeManager);
+    MOZ_ASSERT(!mApzSampler);
+    mApzcTreeManager = new APZCTreeManager(mRootLayerTreeID);
+    mApzSampler = new APZSampler(mApzcTreeManager);
+  }
+
   mCompositorBridgeID = 0;
   // FIXME: This holds on the the fact that right now the only thing that
   // can destroy this instance is initialized on the compositor thread after
   // this task has been processed.
   MOZ_ASSERT(CompositorLoop());
   CompositorLoop()->PostTask(NewRunnableFunction("AddCompositorRunnable",
                                                  &AddCompositor,
                                                  this, &mCompositorBridgeID));
@@ -1082,28 +1087,25 @@ CompositorBridgeParent::ForceComposeToTa
   AutoRestore<bool> override(mOverrideComposeReadiness);
   mOverrideComposeReadiness = true;
   mCompositorScheduler->ForceComposeToTarget(aTarget, aRect);
 }
 
 PAPZCTreeManagerParent*
 CompositorBridgeParent::AllocPAPZCTreeManagerParent(const uint64_t& aLayersId)
 {
+  // This should only ever get called in the GPU process.
+  MOZ_ASSERT(XRE_IsGPUProcess());
   // We should only ever get this if APZ is enabled in this compositor.
   MOZ_ASSERT(mOptions.UseAPZ());
-
+  // The mApzcTreeManager should have been created via RecvInitialize()
+  MOZ_ASSERT(mApzcTreeManager);
   // The main process should pass in 0 because we assume mRootLayerTreeID
   MOZ_ASSERT(aLayersId == 0);
 
-  // This message doubles as initialization
-  MOZ_ASSERT(!mApzcTreeManager);
-
-  mApzcTreeManager = new APZCTreeManager(mRootLayerTreeID);
-  mApzSampler = new APZSampler(mApzcTreeManager);
-
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   CompositorBridgeParent::LayerTreeState& state = sIndirectLayerTrees[mRootLayerTreeID];
   MOZ_ASSERT(state.mParent.get() == this);
   MOZ_ASSERT(!state.mApzcTreeManagerParent);
   state.mApzcTreeManagerParent = new APZCTreeManagerParent(mRootLayerTreeID, mApzcTreeManager);
 
   return state.mApzcTreeManagerParent;
 }
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -476,39 +476,39 @@ public:
                                                       TextureFactoryIdentifier* aTextureFactoryIdentifier,
                                                       wr::IdNamespace* aIdNamespace) override;
   bool DeallocPWebRenderBridgeParent(PWebRenderBridgeParent* aActor) override;
   RefPtr<WebRenderBridgeParent> GetWebRenderBridgeParent() const;
   Maybe<TimeStamp> GetTestingTimeStamp() const;
 
   static CompositorBridgeParent* GetCompositorBridgeParentFromLayersId(const uint64_t& aLayersId);
 
+  /**
+   * This returns a reference to the IAPZCTreeManager "controller subinterface"
+   * to which pan/zoom-related events can be sent. The controller subinterface
+   * doesn't expose any sampler-thread APZCTreeManager methods.
+   */
+  static already_AddRefed<IAPZCTreeManager> GetAPZCTreeManager(uint64_t aLayersId);
+
 #if defined(MOZ_WIDGET_ANDROID)
   gfx::IntSize GetEGLSurfaceSize() {
     return mEGLSurfaceSize;
   }
 #endif // defined(MOZ_WIDGET_ANDROID)
 
 private:
 
   void Initialize();
 
   /**
    * Called during destruction in order to release resources as early as possible.
    */
   void StopAndClearResources();
 
   /**
-   * This returns a reference to the IAPZCTreeManager "controller subinterface"
-   * to which pan/zoom-related events can be sent. The controller subinterface
-   * doesn't expose any sampler-thread APZCTreeManager methods.
-   */
-  static already_AddRefed<IAPZCTreeManager> GetAPZCTreeManager(uint64_t aLayersId);
-
-  /**
    * Release compositor-thread resources referred to by |aID|.
    *
    * Must run on the content main thread.
    */
   static void DeallocateLayerTreeId(uint64_t aId);
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
--- a/gfx/layers/ipc/PAPZCTreeManager.ipdl
+++ b/gfx/layers/ipc/PAPZCTreeManager.ipdl
@@ -1,65 +1,47 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include "mozilla/GfxMessageUtils.h";
 include "mozilla/layers/LayersMessageUtils.h";
 include "ipc/nsGUIEventIPC.h";
-include "mozilla/dom/TabMessageUtils.h"; // Needed for IPC::ParamTraits<nsEventStatus>.
 
 include protocol PCompositorBridge;
 
-using CSSPoint from "Units.h";
 using CSSRect from "Units.h";
 using LayoutDeviceCoord from "Units.h";
-using LayoutDeviceIntPoint from "Units.h";
 using mozilla::LayoutDevicePoint from "Units.h";
 using ScreenPoint from "Units.h";
 using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h";
 using mozilla::layers::MaybeZoomConstraints from "FrameMetrics.h";
 using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h";
 using mozilla::layers::TouchBehaviorFlags from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::AsyncDragMetrics from "mozilla/layers/AsyncDragMetrics.h";
 using mozilla::layers::GeckoContentController::TapType from "mozilla/layers/GeckoContentController.h";
 using class mozilla::layers::KeyboardMap from "mozilla/layers/KeyboardMap.h";
 
-using nsEventStatus from "mozilla/EventForwards.h";
-using EventMessage from "mozilla/EventForwards.h";
 using mozilla::Modifiers from "mozilla/EventForwards.h";
-using class mozilla::WidgetInputEvent from "mozilla/BasicEvents.h";
-using class mozilla::WidgetMouseEventBase from "mozilla/MouseEvents.h";
-using mozilla::WidgetMouseEvent::Reason from "mozilla/MouseEvents.h";
-using class mozilla::WidgetTouchEvent from "mozilla/TouchEvents.h";
-using class mozilla::WidgetWheelEvent from "mozilla/MouseEvents.h";
-using class mozilla::InputData from "InputData.h";
-using class mozilla::MultiTouchInput from "InputData.h";
-using class mozilla::MouseInput from "InputData.h";
-using class mozilla::PanGestureInput from "InputData.h";
-using class mozilla::PinchGestureInput from "InputData.h";
 using mozilla::PinchGestureInput::PinchGestureType from "InputData.h";
-using class mozilla::TapGestureInput from "InputData.h";
-using class mozilla::ScrollWheelInput from "InputData.h";
-using class mozilla::KeyboardInput from "InputData.h";
 
 namespace mozilla {
 namespace layers {
 
 /**
  * PAPZCTreeManager is a protocol for remoting an IAPZCTreeManager. PAPZCTreeManager
  * lives on the PCompositorBridge protocol which either connects to the compositor
  * thread in the main process, or to the compositor thread in the gpu processs.
  *
  * PAPZCTreeManagerParent lives in the compositor thread, while PAPZCTreeManagerChild
  * lives in the main thread of the main or the content process. APZCTreeManagerParent
  * and APZCTreeManagerChild implement this protocol.
  */
-sync protocol PAPZCTreeManager
+protocol PAPZCTreeManager
 {
 manager PCompositorBridge;
 
 parent:
 
   // These messages correspond to the methods
   // on the IAPZCTreeManager interface
 
@@ -80,68 +62,16 @@ parent:
   async StartScrollbarDrag(ScrollableLayerGuid aGuid, AsyncDragMetrics aDragMetrics);
 
   async StartAutoscroll(ScrollableLayerGuid aGuid, ScreenPoint aAnchorLocation);
 
   async StopAutoscroll(ScrollableLayerGuid aGuid);
 
   async SetLongTapEnabled(bool aTapGestureEnabled);
 
-  // The following messages are used to
-  // implement the ReceiveInputEvent methods
-
-  sync ReceiveMultiTouchInputEvent(MultiTouchInput aEvent)
-    returns (nsEventStatus       aOutStatus,
-             MultiTouchInput     aOutEvent,
-             ScrollableLayerGuid aOutTargetGuid,
-             uint64_t            aOutInputBlockId);
-
-  sync ReceiveMouseInputEvent(MouseInput aEvent)
-    returns (nsEventStatus       aOutStatus,
-             MouseInput          aOutEvent,
-             ScrollableLayerGuid aOutTargetGuid,
-             uint64_t            aOutInputBlockId);
-
-  sync ReceivePanGestureInputEvent(PanGestureInput aEvent)
-    returns (nsEventStatus       aOutStatus,
-             PanGestureInput     aOutEvent,
-             ScrollableLayerGuid aOutTargetGuid,
-             uint64_t            aOutInputBlockId);
-
-  sync ReceivePinchGestureInputEvent(PinchGestureInput aEvent)
-    returns (nsEventStatus       aOutStatus,
-             PinchGestureInput   aOutEvent,
-             ScrollableLayerGuid aOutTargetGuid,
-             uint64_t            aOutInputBlockId);
-
-  sync ReceiveTapGestureInputEvent(TapGestureInput aEvent)
-    returns (nsEventStatus       aOutStatus,
-             TapGestureInput     aOutEvent,
-             ScrollableLayerGuid aOutTargetGuid,
-             uint64_t            aOutInputBlockId);
-
-  sync ReceiveScrollWheelInputEvent(ScrollWheelInput aEvent)
-    returns (nsEventStatus       aOutStatus,
-             ScrollWheelInput    aOutEvent,
-             ScrollableLayerGuid aOutTargetGuid,
-             uint64_t            aOutInputBlockId);
-
-  sync ReceiveKeyboardInputEvent(KeyboardInput aEvent)
-    returns (nsEventStatus       aOutStatus,
-             KeyboardInput       aOutEvent,
-             ScrollableLayerGuid aOutTargetGuid,
-             uint64_t            aOutInputBlockId);
-
-  async UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, EventMessage aEventMessage);
-
-  sync ProcessUnhandledEvent(LayoutDeviceIntPoint aRefPoint)
-    returns (LayoutDeviceIntPoint   aOutRefPoint,
-             ScrollableLayerGuid    aOutTargetGuid,
-             uint64_t               aOutFocusSequenceNumber);
-
   async __delete__();
 
 child:
 
   async HandleTap(TapType aType, LayoutDevicePoint point, Modifiers aModifiers,
                   ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
 
   async NotifyPinchGesture(PinchGestureType aType, ScrollableLayerGuid aGuid,
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/PAPZInputBridge.ipdl
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+using LayoutDeviceIntPoint from "Units.h";
+using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h";
+
+using nsEventStatus from "mozilla/EventForwards.h";
+using EventMessage from "mozilla/EventForwards.h";
+using class mozilla::MultiTouchInput from "InputData.h";
+using class mozilla::MouseInput from "InputData.h";
+using class mozilla::PanGestureInput from "InputData.h";
+using class mozilla::PinchGestureInput from "InputData.h";
+using class mozilla::TapGestureInput from "InputData.h";
+using class mozilla::ScrollWheelInput from "InputData.h";
+using class mozilla::KeyboardInput from "InputData.h";
+
+include protocol PGPU;
+
+namespace mozilla {
+namespace layers {
+
+/**
+ * This protocol is used to send input events from the UI process to the
+ * GPU process for handling by APZ. There is one instance per top-level
+ * compositor, or in other words, one instance per concrete APZCTreeManager
+ * instance. The child side lives on the main thread in the UI process,
+ * and the parent side lives on the main thread in the GPU process. If there
+ * is no GPU process, then this protocol is not instantiated.
+ */
+sync protocol PAPZInputBridge
+{
+manager PGPU;
+
+parent:
+  // The following messages are used to
+  // implement the ReceiveInputEvent methods
+
+  sync ReceiveMultiTouchInputEvent(MultiTouchInput aEvent)
+    returns (nsEventStatus       aOutStatus,
+             MultiTouchInput     aOutEvent,
+             ScrollableLayerGuid aOutTargetGuid,
+             uint64_t            aOutInputBlockId);
+
+  sync ReceiveMouseInputEvent(MouseInput aEvent)
+    returns (nsEventStatus       aOutStatus,
+             MouseInput          aOutEvent,
+             ScrollableLayerGuid aOutTargetGuid,
+             uint64_t            aOutInputBlockId);
+
+  sync ReceivePanGestureInputEvent(PanGestureInput aEvent)
+    returns (nsEventStatus       aOutStatus,
+             PanGestureInput     aOutEvent,
+             ScrollableLayerGuid aOutTargetGuid,
+             uint64_t            aOutInputBlockId);
+
+  sync ReceivePinchGestureInputEvent(PinchGestureInput aEvent)
+    returns (nsEventStatus       aOutStatus,
+             PinchGestureInput   aOutEvent,
+             ScrollableLayerGuid aOutTargetGuid,
+             uint64_t            aOutInputBlockId);
+
+  sync ReceiveTapGestureInputEvent(TapGestureInput aEvent)
+    returns (nsEventStatus       aOutStatus,
+             TapGestureInput     aOutEvent,
+             ScrollableLayerGuid aOutTargetGuid,
+             uint64_t            aOutInputBlockId);
+
+  sync ReceiveScrollWheelInputEvent(ScrollWheelInput aEvent)
+    returns (nsEventStatus       aOutStatus,
+             ScrollWheelInput    aOutEvent,
+             ScrollableLayerGuid aOutTargetGuid,
+             uint64_t            aOutInputBlockId);
+
+  sync ReceiveKeyboardInputEvent(KeyboardInput aEvent)
+    returns (nsEventStatus       aOutStatus,
+             KeyboardInput       aOutEvent,
+             ScrollableLayerGuid aOutTargetGuid,
+             uint64_t            aOutInputBlockId);
+
+  async UpdateWheelTransaction(LayoutDeviceIntPoint aRefPoint, EventMessage aEventMessage);
+
+  sync ProcessUnhandledEvent(LayoutDeviceIntPoint aRefPoint)
+    returns (LayoutDeviceIntPoint   aOutRefPoint,
+             ScrollableLayerGuid    aOutTargetGuid,
+             uint64_t               aOutFocusSequenceNumber);
+
+  async __delete__();
+};
+
+} // namespace gfx
+} // namespace mozilla
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -87,16 +87,17 @@ EXPORTS.gfxipc += [
 
 EXPORTS.mozilla.dom += [
     'apz/util/CheckerboardReportService.h',
 ]
 
 EXPORTS.mozilla.layers += [
     'AnimationHelper.h',
     'AnimationInfo.h',
+    'apz/public/APZInputBridge.h',
     'apz/public/APZSampler.h',
     'apz/public/CompositorController.h',
     'apz/public/GeckoContentController.h',
     'apz/public/IAPZCTreeManager.h',
     'apz/public/MetricsSharingController.h',
     # exporting things from apz/src is temporary until we extract a
     # proper interface for the code there
     'apz/src/APZUtils.h',
@@ -161,16 +162,18 @@ EXPORTS.mozilla.layers += [
     'D3D11YCbCrImage.h',
     'D3D9SurfaceImage.h',
     'DirectionUtils.h',
     'Effects.h',
     'ImageDataSerializer.h',
     'ipc/APZChild.h',
     'ipc/APZCTreeManagerChild.h',
     'ipc/APZCTreeManagerParent.h',
+    'ipc/APZInputBridgeChild.h',
+    'ipc/APZInputBridgeParent.h',
     'ipc/CompositableForwarder.h',
     'ipc/CompositableTransactionParent.h',
     'ipc/CompositorBridgeChild.h',
     'ipc/CompositorBridgeParent.h',
     'ipc/CompositorManagerChild.h',
     'ipc/CompositorManagerParent.h',
     'ipc/CompositorThread.h',
     'ipc/CompositorVsyncScheduler.h',
@@ -287,18 +290,18 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'andr
     ]
     EXPORTS.mozilla.layers += [
         'apz/src/AndroidDynamicToolbarAnimator.h',
     ]
 
 UNIFIED_SOURCES += [
     'AnimationHelper.cpp',
     'AnimationInfo.cpp',
-    'apz/public/IAPZCTreeManager.cpp',
     'apz/src/APZCTreeManager.cpp',
+    'apz/src/APZInputBridge.cpp',
     'apz/src/APZSampler.cpp',
     'apz/src/APZUtils.cpp',
     'apz/src/AsyncPanZoomController.cpp',
     'apz/src/AutoscrollAnimation.cpp',
     'apz/src/Axis.cpp',
     'apz/src/CheckerboardEvent.cpp',
     'apz/src/DragTracker.cpp',
     'apz/src/FocusState.cpp',
@@ -390,16 +393,18 @@ UNIFIED_SOURCES += [
     'Effects.cpp',
     'FrameMetrics.cpp',
     'GLImages.cpp',
     'ImageDataSerializer.cpp',
     'ImageLayers.cpp',
     'ipc/APZChild.cpp',
     'ipc/APZCTreeManagerChild.cpp',
     'ipc/APZCTreeManagerParent.cpp',
+    'ipc/APZInputBridgeChild.cpp',
+    'ipc/APZInputBridgeParent.cpp',
     'ipc/CompositableTransactionParent.cpp',
     'ipc/CompositorBench.cpp',
     'ipc/CompositorBridgeChild.cpp',
     'ipc/CompositorBridgeParent.cpp',
     'ipc/CompositorManagerChild.cpp',
     'ipc/CompositorManagerParent.cpp',
     'ipc/CompositorThread.cpp',
     'ipc/CompositorVsyncScheduler.cpp',
@@ -497,16 +502,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'coco
         'opengl/MacIOSurfaceTextureHostOGL.cpp',
     ]
 
 IPDL_SOURCES += [
     'ipc/LayersMessages.ipdlh',
     'ipc/LayersSurfaces.ipdlh',
     'ipc/PAPZ.ipdl',
     'ipc/PAPZCTreeManager.ipdl',
+    'ipc/PAPZInputBridge.ipdl',
     'ipc/PCompositorBridge.ipdl',
     'ipc/PCompositorManager.ipdl',
     'ipc/PImageBridge.ipdl',
     'ipc/PLayerTransaction.ipdl',
     'ipc/PTexture.ipdl',
     'ipc/PUiCompositorController.ipdl',
     'ipc/PVideoBridge.ipdl',
     'ipc/PWebRenderBridge.ipdl',
--- a/gfx/layers/wr/WebRenderScrollData.cpp
+++ b/gfx/layers/wr/WebRenderScrollData.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/WebRenderScrollData.h"
 
 #include "Layers.h"
 #include "LayersLogging.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/layout/RenderFrameParent.h"
 #include "mozilla/Unused.h"
 #include "nsDisplayList.h"
 #include "nsTArray.h"
 #include "UnitTransforms.h"
 
 namespace mozilla {
 namespace layers {
--- a/gfx/layers/wr/WebRenderScrollData.h
+++ b/gfx/layers/wr/WebRenderScrollData.h
@@ -25,16 +25,17 @@ class nsDisplayItem;
 
 namespace mozilla {
 
 struct ActiveScrolledRoot;
 
 namespace layers {
 
 class Layer;
+class WebRenderLayerManager;
 class WebRenderScrollData;
 
 // Data needed by APZ, per layer. One instance of this class is created for
 // each layer in the layer tree and sent over PWebRenderBridge to the APZ code.
 // Each WebRenderLayerScrollData is conceptually associated with an "owning"
 // WebRenderScrollData.
 class WebRenderLayerScrollData
 {
--- a/gfx/layers/wr/WebRenderUserData.cpp
+++ b/gfx/layers/wr/WebRenderUserData.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebRenderUserData.h"
 
+#include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/ImageClient.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/layers/WebRenderMessages.h"
 #include "mozilla/layers/IpcResourceUpdateQueue.h"
 #include "mozilla/layers/SharedSurfacesChild.h"
 #include "nsDisplayListInvalidation.h"
 #include "nsIFrame.h"
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -549,20 +549,23 @@ GeckoChildProcessHost::RunPerformAsyncLa
 
   bool ok = PerformAsyncLaunch(aExtraOpts);
   if (!ok) {
     // WaitUntilConnected might be waiting for us to signal.
     // If something failed let's set the error state and notify.
     MonitorAutoLock lock(mMonitor);
     mProcessState = PROCESS_ERROR;
     lock.Notify();
+    OnProcessLaunchError();
     CHROMIUM_LOG(ERROR) << "Failed to launch " <<
       XRE_ChildProcessTypeToString(mProcessType) << " subprocess";
     Telemetry::Accumulate(Telemetry::SUBPROCESS_LAUNCH_FAILURE,
       nsDependentCString(XRE_ChildProcessTypeToString(mProcessType)));
+  } else {
+    OnProcessHandleReady(mChildProcessHandle);
   }
   return ok;
 }
 
 void
 #if defined(XP_WIN)
 AddAppDirToCommandLine(CommandLine& aCmdLine)
 #else
@@ -1126,16 +1129,24 @@ GeckoChildProcessHost::OpenPrivilegedHan
     MOZ_ASSERT(aPid == base::GetProcId(mChildProcessHandle));
     return true;
   }
 
   return base::OpenPrivilegedProcessHandle(aPid, &mChildProcessHandle);
 }
 
 void
+GeckoChildProcessHost::OnProcessHandleReady(ProcessHandle aProcessHandle)
+{}
+
+void
+GeckoChildProcessHost::OnProcessLaunchError()
+{}
+
+void
 GeckoChildProcessHost::OnChannelConnected(int32_t peer_pid)
 {
   if (!OpenPrivilegedHandle(peer_pid)) {
     MOZ_CRASH("can't open handle to child process");
   }
   MonitorAutoLock lock(mMonitor);
   mProcessState = PROCESS_CONNECTED;
   lock.Notify();
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -69,16 +69,18 @@ public:
   // Block until the child process has been created and it connects to
   // the IPC channel, meaning it's fully initialized.  (Or until an
   // error occurs.)
   bool SyncLaunch(StringVector aExtraOpts=StringVector(),
                   int32_t timeoutMs=0);
 
   virtual bool PerformAsyncLaunch(StringVector aExtraOpts=StringVector());
 
+  virtual void OnProcessHandleReady(ProcessHandle aProcessHandle);
+  virtual void OnProcessLaunchError();
   virtual void OnChannelConnected(int32_t peer_pid) override;
   virtual void OnMessageReceived(IPC::Message&& aMsg) override;
   virtual void OnChannelError() override;
   virtual void GetQueuedMessages(std::queue<IPC::Message>& queue) override;
 
   virtual void InitializeChannel();
 
   virtual bool CanShutdown() override { return true; }
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -573,18 +573,20 @@ IProtocol::GetActorEventTarget()
 already_AddRefed<nsIEventTarget>
 IProtocol::GetActorEventTargetInternal(IProtocol* aActor)
 {
   return Manager()->GetActorEventTargetInternal(aActor);
 }
 
 IToplevelProtocol::IToplevelProtocol(ProtocolId aProtoId, Side aSide)
  : IProtocol(aSide),
+   mMonitor("mozilla.ipc.IToplevelProtocol.mMonitor"),
    mProtocolId(aProtoId),
    mOtherPid(mozilla::ipc::kInvalidProcessId),
+   mOtherPidState(ProcessIdState::eUnstarted),
    mLastRouteId(aSide == ParentSide ? kFreedActorId : kNullActorId),
    mLastShmemId(aSide == ParentSide ? kFreedActorId : kNullActorId),
    mEventTargetMutex("ProtocolEventTargetMutex")
 {
 }
 
 IToplevelProtocol::~IToplevelProtocol()
 {
@@ -600,23 +602,40 @@ IToplevelProtocol::OtherPid() const
   base::ProcessId pid = OtherPidMaybeInvalid();
   MOZ_RELEASE_ASSERT(pid != kInvalidProcessId);
   return pid;
 }
 
 base::ProcessId
 IToplevelProtocol::OtherPidMaybeInvalid() const
 {
+  MonitorAutoLock lock(mMonitor);
+
+  if (mOtherPidState == ProcessIdState::eUnstarted) {
+    // If you're asking for the pid of a process we haven't even tried to
+    // start, you get an invalid pid back immediately.
+    return kInvalidProcessId;
+  }
+
+  while (mOtherPidState < ProcessIdState::eReady) {
+    lock.Wait();
+  }
+  MOZ_RELEASE_ASSERT(mOtherPidState == ProcessIdState::eReady);
+
   return mOtherPid;
 }
 
 void
-IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid)
+IToplevelProtocol::SetOtherProcessId(base::ProcessId aOtherPid,
+                                     ProcessIdState aState)
 {
+  MonitorAutoLock lock(mMonitor);
   mOtherPid = aOtherPid;
+  mOtherPidState = aState;
+  lock.NotifyAll();
 }
 
 bool
 IToplevelProtocol::TakeMinidump(nsIFile** aDump, uint32_t* aSequence)
 {
   MOZ_RELEASE_ASSERT(GetSide() == ParentSide);
   return XRE_TakeMinidumpForChild(OtherPid(), aDump, aSequence);
 }
@@ -644,16 +663,24 @@ bool
 IToplevelProtocol::Open(MessageChannel* aChannel,
                         nsIEventTarget* aEventTarget,
                         mozilla::ipc::Side aSide)
 {
   SetOtherProcessId(base::GetCurrentProcId());
   return GetIPCChannel()->Open(aChannel, aEventTarget, aSide);
 }
 
+bool
+IToplevelProtocol::OpenWithAsyncPid(mozilla::ipc::Transport* aTransport,
+                                    MessageLoop* aThread,
+                                    mozilla::ipc::Side aSide)
+{
+  return GetIPCChannel()->Open(aTransport, aThread, aSide);
+}
+
 void
 IToplevelProtocol::Close()
 {
   GetIPCChannel()->Close();
 }
 
 void
 IToplevelProtocol::SetReplyTimeoutMs(int32_t aTimeoutMs)
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -265,29 +265,37 @@ class IToplevelProtocol : public IProtoc
 {
     template<class PFooSide> friend class Endpoint;
 
 protected:
     explicit IToplevelProtocol(ProtocolId aProtoId, Side aSide);
     ~IToplevelProtocol();
 
 public:
+    enum ProcessIdState {
+        eUnstarted,
+        ePending,
+        eReady,
+        eError
+    };
+
     using SchedulerGroupSet = nsILabelableRunnable::SchedulerGroupSet;
 
     void SetTransport(UniquePtr<Transport> aTrans)
     {
         mTrans = Move(aTrans);
     }
 
     Transport* GetTransport() const { return mTrans.get(); }
 
     ProtocolId GetProtocolId() const { return mProtocolId; }
 
-    base::ProcessId OtherPid() const override;
-    void SetOtherProcessId(base::ProcessId aOtherPid);
+    base::ProcessId OtherPid() const final;
+    void SetOtherProcessId(base::ProcessId aOtherPid,
+                           ProcessIdState aState = ProcessIdState::eReady);
 
     bool TakeMinidump(nsIFile** aDump, uint32_t* aSequence);
 
     virtual void OnChannelClose() = 0;
     virtual void OnChannelError() = 0;
     virtual void ProcessingError(Result aError, const char* aMsgName) {}
     virtual void OnChannelConnected(int32_t peer_pid) {}
 
@@ -299,16 +307,20 @@ public:
     bool Open(MessageChannel* aChannel,
               MessageLoop* aMessageLoop,
               mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
 
     bool Open(MessageChannel* aChannel,
               nsIEventTarget* aEventTarget,
               mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
 
+    bool OpenWithAsyncPid(mozilla::ipc::Transport* aTransport,
+                          MessageLoop* aThread = nullptr,
+                          mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
+
     void Close();
 
     void SetReplyTimeoutMs(int32_t aTimeoutMs);
 
     virtual int32_t Register(IProtocol*) override;
     virtual int32_t RegisterID(IProtocol*, int32_t) override;
     virtual IProtocol* Lookup(int32_t) override;
     virtual void Unregister(int32_t) override;
@@ -426,22 +438,26 @@ protected:
                                                 nsIEventTarget* aEventTarget) override;
     virtual void ReplaceEventTargetForActorInternal(
       IProtocol* aActor,
       nsIEventTarget* aEventTarget) override;
 
     virtual already_AddRefed<nsIEventTarget>
     GetActorEventTargetInternal(IProtocol* aActor) override;
 
+    // This monitor protects mOtherPid and mOtherPidState. All other fields
+    // should only be accessed on the worker thread.
+    mutable mozilla::Monitor mMonitor;
   private:
     base::ProcessId OtherPidMaybeInvalid() const;
 
     ProtocolId mProtocolId;
     UniquePtr<Transport> mTrans;
     base::ProcessId mOtherPid;
+    ProcessIdState mOtherPidState;
     IDMap<IProtocol*> mActorMap;
     int32_t mLastRouteId;
     IDMap<Shmem::SharedMemory*> mShmemMap;
     Shmem::id_t mLastShmemId;
     bool mIsMainThreadProtocol;
 
     Mutex mEventTargetMutex;
     IDMap<nsCOMPtr<nsIEventTarget>> mEventTargetMap;
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -923,31 +923,31 @@ description =
 [PRemoteSpellcheckEngine::SetDictionary]
 description =
 [PGPU::AddLayerTreeIdMapping]
 description =
 [PGPU::GetDeviceStatus]
 description =
 [PGPU::SimulateDeviceReset]
 description =
-[PAPZCTreeManager::ReceiveMultiTouchInputEvent]
+[PAPZInputBridge::ReceiveMultiTouchInputEvent]
 description =
-[PAPZCTreeManager::ReceiveMouseInputEvent]
+[PAPZInputBridge::ReceiveMouseInputEvent]
 description =
-[PAPZCTreeManager::ReceivePanGestureInputEvent]
+[PAPZInputBridge::ReceivePanGestureInputEvent]
 description =
-[PAPZCTreeManager::ReceivePinchGestureInputEvent]
+[PAPZInputBridge::ReceivePinchGestureInputEvent]
 description =
-[PAPZCTreeManager::ReceiveTapGestureInputEvent]
+[PAPZInputBridge::ReceiveTapGestureInputEvent]
 description =
-[PAPZCTreeManager::ReceiveScrollWheelInputEvent]
+[PAPZInputBridge::ReceiveScrollWheelInputEvent]
 description =
-[PAPZCTreeManager::ProcessUnhandledEvent]
+[PAPZInputBridge::ProcessUnhandledEvent]
 description =
-[PAPZCTreeManager::ReceiveKeyboardInputEvent]
+[PAPZInputBridge::ReceiveKeyboardInputEvent]
 description =
 [PCompositorBridge::Initialize]
 description =
 [PCompositorBridge::GetFrameUniformity]
 description =
 [PCompositorBridge::WillClose]
 description =
 [PCompositorBridge::Pause]
--- a/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
@@ -119,17 +119,17 @@ TransceiverImpl::InitVideo()
       mReceiveTrack);
 }
 
 nsresult
 TransceiverImpl::UpdateSinkIdentity(const dom::MediaStreamTrack* aTrack,
                                     nsIPrincipal* aPrincipal,
                                     const PeerIdentity* aSinkIdentity)
 {
-  if (mJsepTransceiver->IsStopped()) {
+  if (!(mJsepTransceiver->mJsDirection & sdp::kSend)) {
     return NS_OK;
   }
 
   mTransmitPipeline->UpdateSinkIdentity_m(aTrack, aPrincipal, aSinkIdentity);
   return NS_OK;
 }
 
 void
--- a/mozglue/build/WindowsDllBlocklist.cpp
+++ b/mozglue/build/WindowsDllBlocklist.cpp
@@ -24,16 +24,17 @@
 #pragma warning( pop )
 
 #include "Authenticode.h"
 #include "nsAutoPtr.h"
 #include "nsWindowsDllInterceptor.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/StackWalk_windows.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
 #include "mozilla/WindowsVersion.h"
 #include "nsWindowsHelpers.h"
 #include "WindowsDllBlocklist.h"
 #include "mozilla/AutoProfilerLabel.h"
 #include "mozilla/glue/WindowsDllServices.h"
 
 using namespace mozilla;
 
@@ -798,23 +799,37 @@ continue_loading:
   // Prevent the stack walker from suspending this thread when LdrLoadDll
   // holds the RtlLookupFunctionEntry lock.
   AutoSuppressStackWalking suppress;
 #endif
 
   return stub_LdrLoadDll(filePath, flags, moduleFileName, handle);
 }
 
+#if defined(NIGHTLY_BUILD)
+// Map of specific thread proc addresses we should block. In particular,
+// LoadLibrary* APIs which indicate DLL injection
+static mozilla::Vector<void*, 4>* gStartAddressesToBlock;
+#endif
+
 static bool
 ShouldBlockThread(void* aStartAddress)
 {
   // Allows crashfirefox.exe to continue to work. Also if your threadproc is null, this crash is intentional.
   if (aStartAddress == 0)
     return false;
 
+#if defined(NIGHTLY_BUILD)
+  for (auto p : *gStartAddressesToBlock) {
+    if (p == aStartAddress) {
+      return true;
+    }
+  }
+#endif
+
   bool shouldBlock = false;
   MEMORY_BASIC_INFORMATION startAddressInfo = {0};
   if (VirtualQuery(aStartAddress, &startAddressInfo, sizeof(startAddressInfo))) {
     shouldBlock |= startAddressInfo.State != MEM_COMMIT;
     shouldBlock |= startAddressInfo.Protect != PAGE_EXECUTE_READ;
   }
 
   return shouldBlock;
@@ -845,16 +860,17 @@ static WindowsDllInterceptor Kernel32Int
 MFBT_API void
 DllBlocklist_Initialize(uint32_t aInitFlags)
 {
   if (sBlocklistInitAttempted) {
     return;
   }
   sInitFlags = aInitFlags;
   sBlocklistInitAttempted = true;
+  gStartAddressesToBlock = new mozilla::Vector<void*, 4>;
 
   // In order to be effective against AppInit DLLs, the blocklist must be
   // initialized before user32.dll is loaded into the process (bug 932100).
   if (GetModuleHandleA("user32.dll")) {
     sUser32BeforeBlocklist = true;
 #ifdef DEBUG
     printf_stderr("DLL blocklist was unable to intercept AppInit DLLs.\n");
 #endif
@@ -900,16 +916,44 @@ DllBlocklist_Initialize(uint32_t aInitFl
     if(!Kernel32Intercept.AddDetour("BaseThreadInitThunk",
                                     reinterpret_cast<intptr_t>(patched_BaseThreadInitThunk),
                                     (void**) &stub_BaseThreadInitThunk)) {
 #ifdef DEBUG
       printf_stderr("BaseThreadInitThunk hook failed\n");
 #endif
     }
   }
+
+#if defined(NIGHTLY_BUILD)
+  // Populate a list of thread start addresses to block.
+  HMODULE hKernel = GetModuleHandleW(L"kernel32.dll");
+  if (hKernel) {
+    void* pProc;
+
+    pProc = (void*)GetProcAddress(hKernel, "LoadLibraryA");
+    if (pProc) {
+      gStartAddressesToBlock->append(pProc);
+    }
+
+    pProc = (void*)GetProcAddress(hKernel, "LoadLibraryW");
+    if (pProc) {
+      gStartAddressesToBlock->append(pProc);
+    }
+
+    pProc = (void*)GetProcAddress(hKernel, "LoadLibraryExA");
+    if (pProc) {
+      gStartAddressesToBlock->append(pProc);
+    }
+
+    pProc = (void*)GetProcAddress(hKernel, "LoadLibraryExW");
+    if (pProc) {
+      gStartAddressesToBlock->append(pProc);
+    }
+  }
+#endif
 }
 
 MFBT_API void
 DllBlocklist_WriteNotes(HANDLE file)
 {
   DWORD nBytes;
 
   WriteFile(file, kBlockedDllsParameter, kBlockedDllsParameterLen, &nBytes, nullptr);
--- a/python/mach_commands.py
+++ b/python/mach_commands.py
@@ -15,17 +15,16 @@ from concurrent.futures import (
     as_completed,
     thread,
 )
 
 import mozinfo
 from manifestparser import TestManifest
 from manifestparser import filters as mpf
 
-import mozpack.path as mozpath
 from mozbuild.base import (
     MachCommandBase,
 )
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
@@ -61,20 +60,16 @@ class MachCommands(MachCommandBase):
                                 append_env=append_env)
 
     @Command('python-test', category='testing',
              description='Run Python unit tests with an appropriate test runner.')
     @CommandArgument('-v', '--verbose',
                      default=False,
                      action='store_true',
                      help='Verbose output.')
-    @CommandArgument('--stop',
-                     default=False,
-                     action='store_true',
-                     help='Stop running tests after the first error or failure.')
     @CommandArgument('-j', '--jobs',
                      default=1,
                      type=int,
                      help='Number of concurrent jobs to run. Default is 1.')
     @CommandArgument('--subsuite',
                      default=None,
                      help=('Python subsuite to run. If not specified, all subsuites are run. '
                            'Use the string `default` to only run tests without a subsuite.'))
@@ -90,39 +85,20 @@ class MachCommands(MachCommandBase):
             import mozfile
             mozfile.remove(tempdir)
 
     def run_python_tests(self,
                          tests=[],
                          test_objects=None,
                          subsuite=None,
                          verbose=False,
-                         stop=False,
                          jobs=1,
                          **kwargs):
         self._activate_virtualenv()
 
-        def find_tests_by_path():
-            import glob
-            files = []
-            for t in tests:
-                if t.endswith('.py') and os.path.isfile(t):
-                    files.append(t)
-                elif os.path.isdir(t):
-                    for root, _, _ in os.walk(t):
-                        files += glob.glob(mozpath.join(root, 'test*.py'))
-                        files += glob.glob(mozpath.join(root, 'unit*.py'))
-                else:
-                    self.log(logging.WARN, 'python-test',
-                             {'test': t},
-                             'TEST-UNEXPECTED-FAIL | Invalid test: {test}')
-                    if stop:
-                        break
-            return files
-
         # Python's unittest, and in particular discover, has problems with
         # clashing namespaces when importing multiple test modules. What follows
         # is a simple way to keep environments separate, at the price of
         # launching Python multiple times. Most tests are run via mozunit,
         # which produces output in the format Mozilla infrastructure expects.
         # Some tests are run via pytest.
         if test_objects is None:
             from moztest.resolve import TestResolver
--- a/services/sync/tests/unit/test_bookmark_tracker.js
+++ b/services/sync/tests/unit/test_bookmark_tracker.js
@@ -237,73 +237,16 @@ add_task(async function test_tracking() 
     Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2);
 
   } finally {
     _("Clean up.");
     await cleanup();
   }
 });
 
-add_task(async function test_batch_tracking() {
-  _("Test tracker does the correct thing during and after a places 'batch'");
-
-  await startTracking();
-
-  PlacesUtils.bookmarks.runInBatchMode({
-    runBatched() {
-      PlacesUtils.bookmarks.createFolder(
-        PlacesUtils.bookmarks.bookmarksMenuFolder,
-        "Test Folder", PlacesUtils.bookmarks.DEFAULT_INDEX);
-      // We should be tracking the new folder and its parent (and need to jump
-      // through blocking hoops...)
-      promiseSpinningly(verifyTrackedCount(2));
-      // But not have bumped the score.
-      Assert.equal(tracker.score, 0);
-    }
-  }, null);
-
-  // Out of batch mode - tracker should be the same, but score should be up.
-  await verifyTrackedCount(2);
-  Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
-  await cleanup();
-});
-
-add_task(async function test_nested_batch_tracking() {
-  _("Test tracker does the correct thing if a places 'batch' is nested");
-
-  await startTracking();
-
-  PlacesUtils.bookmarks.runInBatchMode({
-    runBatched() {
-
-      PlacesUtils.bookmarks.runInBatchMode({
-        runBatched() {
-          PlacesUtils.bookmarks.createFolder(
-            PlacesUtils.bookmarks.bookmarksMenuFolder,
-            "Test Folder", PlacesUtils.bookmarks.DEFAULT_INDEX);
-          // We should be tracking the new folder and its parent (and need to jump
-          // through blocking hoops...)
-          promiseSpinningly(verifyTrackedCount(2));
-          // But not have bumped the score.
-          Assert.equal(tracker.score, 0);
-        }
-      }, null);
-      _("inner batch complete.");
-      // should still not have a score as the outer batch is pending.
-      promiseSpinningly(verifyTrackedCount(2));
-      Assert.equal(tracker.score, 0);
-    }
-  }, null);
-
-  // Out of both batches - tracker should be the same, but score should be up.
-  await verifyTrackedCount(2);
-  Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
-  await cleanup();
-});
-
 add_task(async function test_tracker_sql_batching() {
   _("Test tracker does the correct thing when it is forced to batch SQL queries");
 
   const SQLITE_MAX_VARIABLE_NUMBER = 999;
   let numItems = SQLITE_MAX_VARIABLE_NUMBER * 2 + 10;
 
   await startTracking();
 
--- a/servo/.gitignore
+++ b/servo/.gitignore
@@ -17,16 +17,17 @@
 *.swo
 *.csv
 *.rej
 *.orig
 .DS_Store
 Servo.app
 .config.mk.last
 /glfw
+capture_webrender/
 
 # Editors
 
 # IntelliJ
 .idea
 *.iws
 *.iml
 
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -2348,16 +2348,24 @@ dependencies = [
 ]
 
 [[package]]
 name = "regex-syntax"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
+name = "ron"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
 name = "rust-webvr"
 version = "0.9.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "gl_generator 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gvr-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3487,16 +3495,18 @@ dependencies = [
  "freetype 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "plane-split 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.57.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "webrender_api"
@@ -3888,16 +3898,17 @@ dependencies = [
 "checksum rayon 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "485541959c8ecc49865526fe6c4de9653dd6e60d829d6edf0be228167b60372d"
 "checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8"
 "checksum redox_syscall 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "29dbdfd4b9df8ab31dec47c6087b7b13cbf4a776f335e4de8efba8288dda075b"
 "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
 "checksum ref_filter_map 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2b5ceb840e4009da4841ed22a15eb49f64fdd00a2138945c5beacf506b2fb5ed"
 "checksum ref_slice 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "825740057197b7d43025e7faf6477eaabc03434e153233da02d1f44602f71527"
 "checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
 "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
+"checksum ron 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "da06feaa07f69125ab9ddc769b11de29090122170b402547f64b86fe16ebc399"
 "checksum rust-webvr 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)" = "9629ce5b5c3cda05259d225e639851daf39c55c170358d3056205dd205deaab2"
 "checksum rust-webvr-api 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "712e22ba3c03a7075b40842ae91029a0ab96a81f95e97c0cf623800ec0cbac07"
 "checksum rustc-demangle 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3058a43ada2c2d0b92b3ae38007a2d0fa5e9db971be260e0171408a4ff471c95"
 "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
 "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084"
 "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
 "checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
 "checksum scoped_threadpool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef399c8893e8cb7aa9696e895427fab3a6bf265977bb96e126f24ddd2cda85a"
--- a/servo/components/compositing/Cargo.toml
+++ b/servo/components/compositing/Cargo.toml
@@ -22,10 +22,10 @@ net_traits = {path = "../net_traits"}
 nonzero = {path = "../nonzero"}
 profile_traits = {path = "../profile_traits"}
 script_traits = {path = "../script_traits"}
 servo_config = {path = "../config"}
 servo_geometry = {path = "../geometry"}
 servo_url = {path = "../url"}
 style_traits = {path = "../style_traits"}
 time = "0.1.17"
-webrender = {git = "https://github.com/servo/webrender"}
+webrender = {git = "https://github.com/servo/webrender", features = ["capture"]}
 webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
--- a/servo/components/compositing/compositor.rs
+++ b/servo/components/compositing/compositor.rs
@@ -18,24 +18,25 @@ use nonzero::NonZero;
 use profile_traits::time::{self, ProfilerCategory, profile};
 use script_traits::{AnimationState, AnimationTickType, ConstellationMsg, LayoutControlMsg};
 use script_traits::{MouseButton, MouseEventType, ScrollState, TouchEventType, TouchId};
 use script_traits::{UntrustedNodeAddress, WindowSizeData, WindowSizeType};
 use script_traits::CompositorEvent::{MouseMoveEvent, MouseButtonEvent, TouchEvent};
 use servo_config::opts;
 use servo_geometry::DeviceIndependentPixel;
 use std::collections::HashMap;
+use std::env;
 use std::fs::File;
 use std::rc::Rc;
 use std::sync::mpsc::Sender;
 use std::time::{Duration, Instant};
 use style_traits::{CSSPixel, DevicePixel, PinchZoomFactor};
 use style_traits::cursor::CursorKind;
 use style_traits::viewport::ViewportConstraints;
-use time::{precise_time_ns, precise_time_s};
+use time::{now, precise_time_ns, precise_time_s};
 use touch::{TouchHandler, TouchAction};
 use webrender;
 use webrender_api::{self, DeviceUintRect, DeviceUintSize, HitTestFlags, HitTestResult};
 use webrender_api::{LayoutVector2D, ScrollEventPhase, ScrollLocation};
 use windowing::{self, MouseWindowEvent, WebRenderDebugOption, WindowMethods};
 
 #[derive(Debug, PartialEq)]