Merge autoland to mozilla-central. a=merge
authorCiure Andrei <aciure@mozilla.com>
Wed, 14 Aug 2019 12:34:46 +0300
changeset 487847 a6ba020c9f7cd1abecd8eb2287020468ec1da6e8
parent 487833 d3deef805f92d4a899d7b6de76b347328f33e54b (current diff)
parent 487846 214fac6eb1c05177c7ef8eb29b87cceb09eb4110 (diff)
child 487851 62094e9b468a0903e56b7af177d7590d616a8d5b
child 488058 b18e834a1dafe9e1b8e36585239027030e3d07e3
push id36431
push useraciure@mozilla.com
push dateWed, 14 Aug 2019 09:42:16 +0000
treeherdermozilla-central@a6ba020c9f7c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone70.0a1
first release with
nightly linux32
a6ba020c9f7c / 70.0a1 / 20190814094216 / files
nightly linux64
a6ba020c9f7c / 70.0a1 / 20190814094216 / files
nightly mac
a6ba020c9f7c / 70.0a1 / 20190814094216 / files
nightly win32
a6ba020c9f7c / 70.0a1 / 20190814094216 / files
nightly win64
a6ba020c9f7c / 70.0a1 / 20190814094216 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
gfx/gl/GLConsts.py
testing/web-platform/meta/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/004.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_004.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_005.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_006.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_007.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/history_back.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/history_forward.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/history_go_minus.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/history_go_plus.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_1.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_2.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_3.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_4.html.ini
testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_5.html.ini
toolkit/library/gtest/static/TestUCRTDepends.cpp
toolkit/library/gtest/static/moz.build
--- a/browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js
+++ b/browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js
@@ -27,16 +27,19 @@ add_task(async function purgeHistoryTest
       ok(backButton.hasAttribute("disabled"), "Back button is disabled");
       ok(forwardButton.hasAttribute("disabled"), "Forward button is disabled");
 
       await ContentTask.spawn(browser, null, async function() {
         let startHistory = content.history.length;
         content.history.pushState({}, "");
         content.history.pushState({}, "");
         content.history.back();
+        await new Promise(function(r) {
+          setTimeout(r);
+        });
         let newHistory = content.history.length;
         Assert.equal(startHistory, 1, "Initial SHistory size");
         Assert.equal(newHistory, 3, "New SHistory size");
       });
 
       ok(
         browser.webNavigation.canGoBack,
         "New value for webNavigation.canGoBack"
--- a/browser/components/aboutlogins/content/components/login-item.js
+++ b/browser/components/aboutlogins/content/components/login-item.js
@@ -141,37 +141,21 @@ export default class LoginItem extends H
         this.setLogin(event.detail, { skipFocusChange: true });
         break;
       }
       case "AboutLoginsLoadInitialFavicon": {
         this.render();
         break;
       }
       case "AboutLoginsLoginSelected": {
-        let login = event.detail;
-        if (this.hasPendingChanges()) {
-          event.preventDefault();
-          this.showConfirmationDialog("discard-changes", () => {
-            // Clear any pending changes
-            this.setLogin(login);
-
-            window.dispatchEvent(
-              new CustomEvent("AboutLoginsLoginSelected", {
-                detail: login,
-                cancelable: true,
-              })
-            );
-          });
-        } else {
-          this.setLogin(login);
-        }
+        this.confirmPendingChangesOnEvent(event, event.detail);
         break;
       }
       case "AboutLoginsShowBlankLogin": {
-        this.setLogin({});
+        this.confirmPendingChangesOnEvent(event, {});
         break;
       }
       case "blur": {
         // Add https:// prefix if one was not provided.
         let originValue = this._originInput.value.trim();
         if (!originValue) {
           return;
         }
@@ -318,16 +302,41 @@ export default class LoginItem extends H
 
           recordTelemetryEvent({ object: "new_login", method: "save" });
         }
       }
     }
   }
 
   /**
+   * Helper to show the "Discard changes" confirmation dialog and delay the
+   * received event after confirmation.
+   * @param {object} event The event to be delayed.
+   * @param {object} login The login to be shown on confirmation.
+   */
+  confirmPendingChangesOnEvent(event, login) {
+    if (this.hasPendingChanges()) {
+      event.preventDefault();
+      this.showConfirmationDialog("discard-changes", () => {
+        // Clear any pending changes
+        this.setLogin(login);
+
+        window.dispatchEvent(
+          new CustomEvent(event.type, {
+            detail: login,
+            cancelable: false,
+          })
+        );
+      });
+    } else {
+      this.setLogin(login);
+    }
+  }
+
+  /**
    * Shows a confirmation dialog.
    * @param {string} type The type of confirmation dialog to display.
    * @param {boolean} onConfirm Optional, the function to execute when the confirm button is clicked.
    */
   showConfirmationDialog(type, onConfirm = () => {}) {
     const dialog = document.querySelector("confirmation-dialog");
     let options;
     switch (type) {
--- a/browser/components/aboutlogins/content/components/login-list.js
+++ b/browser/components/aboutlogins/content/components/login-list.js
@@ -118,17 +118,21 @@ export default class LoginList extends H
       );
     }
   }
 
   handleEvent(event) {
     switch (event.type) {
       case "click": {
         if (event.originalTarget == this._createLoginButton) {
-          window.dispatchEvent(new CustomEvent("AboutLoginsShowBlankLogin"));
+          window.dispatchEvent(
+            new CustomEvent("AboutLoginsShowBlankLogin", {
+              cancelable: true,
+            })
+          );
           recordTelemetryEvent({ object: "new_login", method: "new" });
           return;
         }
 
         let listItem = event.originalTarget.closest(".login-list-item");
         if (!listItem || !listItem.dataset.guid) {
           return;
         }
@@ -187,18 +191,20 @@ export default class LoginList extends H
         if (listItem) {
           this._setListItemAsSelected(listItem);
         } else {
           this.render();
         }
         break;
       }
       case "AboutLoginsShowBlankLogin": {
-        this._selectedGuid = null;
-        this._setListItemAsSelected(this._blankLoginListItem);
+        if (!event.defaultPrevented) {
+          this._selectedGuid = null;
+          this._setListItemAsSelected(this._blankLoginListItem);
+        }
         break;
       }
       case "keydown": {
         this._handleKeyboardNav(event);
         break;
       }
     }
   }
--- a/browser/components/aboutlogins/tests/browser/browser_updateLogin.js
+++ b/browser/components/aboutlogins/tests/browser/browser_updateLogin.js
@@ -55,64 +55,67 @@ add_task(async function test_login_item(
       let usernameInput = loginItem.shadowRoot.querySelector(
         "input[name='username']"
       );
       let passwordInput = loginItem.shadowRoot.querySelector(
         "input[name='password']"
       );
 
       let editButton = loginItem.shadowRoot.querySelector(".edit-button");
-      editButton.click();
-      await Promise.resolve();
+
+      async function test_discard_dialog(exitPoint) {
+        editButton.click();
+        await Promise.resolve();
+
+        usernameInput.value += "-undome";
+        passwordInput.value += "-undome";
+
+        let dialog = content.document.querySelector("confirmation-dialog");
+        ok(dialog.hidden, "Confirm dialog should initially be hidden");
+
+        exitPoint.click();
+
+        ok(!dialog.hidden, "Confirm dialog should be visible");
+
+        let confirmDiscardButton = dialog.shadowRoot.querySelector(
+          ".confirm-button"
+        );
+        await content.document.l10n.translateElements([
+          dialog.shadowRoot.querySelector(".title"),
+          dialog.shadowRoot.querySelector(".message"),
+          confirmDiscardButton,
+        ]);
 
-      usernameInput.value += "-undome";
-      passwordInput.value += "-undome";
+        confirmDiscardButton.click();
+
+        ok(dialog.hidden, "Confirm dialog should be hidden after confirming");
+
+        await Promise.resolve();
+        loginListItem.click();
+
+        await ContentTaskUtils.waitForCondition(
+          () => usernameInput.value == login.username
+        );
 
-      let dialog = content.document.querySelector("confirmation-dialog");
-      ok(dialog.hidden, "Confirm dialog should initially be hidden");
+        is(
+          usernameInput.value,
+          login.username,
+          "Username change should be reverted"
+        );
+        is(
+          passwordInput.value,
+          login.password,
+          "Password change should be reverted"
+        );
+      }
+
+      await test_discard_dialog(loginList._createLoginButton);
 
       let cancelButton = loginItem.shadowRoot.querySelector(".cancel-button");
-      cancelButton.click();
-
-      ok(!dialog.hidden, "Confirm dialog should be visible");
-
-      let confirmDiscardButton = dialog.shadowRoot.querySelector(
-        ".confirm-button"
-      );
-      await content.document.l10n.translateElements([
-        dialog.shadowRoot.querySelector(".title"),
-        dialog.shadowRoot.querySelector(".message"),
-        confirmDiscardButton,
-      ]);
-
-      confirmDiscardButton.click();
-
-      ok(dialog.hidden, "Confirm dialog should be hidden after confirming");
-
-      usernameInput = loginItem.shadowRoot.querySelector(
-        "input[name='username']"
-      );
-      passwordInput = loginItem.shadowRoot.querySelector(
-        "input[name='password']"
-      );
-
-      await ContentTaskUtils.waitForCondition(
-        () => usernameInput.value == login.username
-      );
-
-      is(
-        usernameInput.value,
-        login.username,
-        "Username change should be reverted"
-      );
-      is(
-        passwordInput.value,
-        login.password,
-        "Password change should be reverted"
-      );
+      await test_discard_dialog(cancelButton);
 
       editButton.click();
       await Promise.resolve();
 
       usernameInput.value += "-saveme";
       passwordInput.value += "-saveme";
 
       ok(loginItem.dataset.editing, "LoginItem should be in 'edit' mode");
--- a/devtools/client/framework/selection.js
+++ b/devtools/client/framework/selection.js
@@ -1,26 +1,35 @@
 /* -*- 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 nodeConstants = require("devtools/shared/dom-node-constants");
-var EventEmitter = require("devtools/shared/event-emitter");
+const EventEmitter = require("devtools/shared/event-emitter");
+
+loader.lazyRequireGetter(
+  this,
+  "nodeConstants",
+  "devtools/shared/dom-node-constants"
+);
 
 /**
+ * Selection is a singleton belonging to the Toolbox that manages the current selected
+ * NodeFront. In addition, it provides some helpers about the context of the selected
+ * node.
+ *
  * API
  *
- *   new Selection(walker=null)
+ *   new Selection()
  *   destroy()
- *   node (readonly)
- *   setNode(node, origin="unknown")
+ *   nodeFront (readonly)
+ *   setNodeFront(node, origin="unknown")
  *
  * Helpers:
  *
  *   window
  *   document
  *   isRoot()
  *   isNode()
  *   isHTMLNode()
@@ -43,39 +52,31 @@ var EventEmitter = require("devtools/sha
  * Events:
  *   "new-node-front" when the inner node changed
  *   "attribute-changed" when an attribute is changed
  *   "detached-front" when the node (or one of its parents) is removed from
  *   the document
  *   "reparented" when the node (or one of its parents) is moved under
  *   a different node
  */
-
-/**
- * A Selection object. Hold a reference to a node.
- * Includes some helpers, fire some helpful events.
- */
-function Selection(walker) {
+function Selection() {
   EventEmitter.decorate(this);
 
+  // The WalkerFront is dynamic and is always set to the selected NodeFront's WalkerFront.
+  this._walker = null;
   // A single node front can be represented twice on the client when the node is a slotted
   // element. It will be displayed once as a direct child of the host element, and once as
   // a child of a slot in the "shadow DOM". The latter is called the slotted version.
   this._isSlotted = false;
 
   this._onMutations = this._onMutations.bind(this);
   this.setNodeFront = this.setNodeFront.bind(this);
-  this.setWalker(walker);
 }
 
-exports.Selection = Selection;
-
 Selection.prototype = {
-  _walker: null,
-
   _onMutations: function(mutations) {
     let attributeChange = false;
     let pseudoChange = false;
     let detached = false;
     let parentNode = null;
 
     for (const m of mutations) {
       if (!attributeChange && m.type == "attributes") {
@@ -102,20 +103,20 @@ Selection.prototype = {
       this.emit("pseudoclass");
     }
     if (detached) {
       this.emit("detached-front", parentNode);
     }
   },
 
   destroy: function() {
-    this.setWalker(null);
+    this.setWalker();
   },
 
-  setWalker: function(walker) {
+  setWalker: function(walker = null) {
     if (this._walker) {
       this._walker.off("mutations", this._onMutations);
     }
     this._walker = walker;
     if (this._walker) {
       this._walker.on("mutations", this._onMutations);
     }
   },
@@ -148,21 +149,23 @@ Selection.prototype = {
       // (e.g. once when the webpage start to navigate away from the current webpage,
       // and then again while the new page is being loaded).
       return;
     }
 
     this._isSlotted = isSlotted;
     this._nodeFront = nodeFront;
 
-    this.emit("new-node-front", nodeFront, this.reason);
-  },
+    if (nodeFront) {
+      this.setWalker(nodeFront.walkerFront);
+    } else {
+      this.setWalker();
+    }
 
-  get documentFront() {
-    return this._walker.document(this._nodeFront);
+    this.emit("new-node-front", nodeFront, this.reason);
   },
 
   get nodeFront() {
     return this._nodeFront;
   },
 
   isRoot: function() {
     return (
@@ -305,8 +308,10 @@ Selection.prototype = {
   isSlotted: function() {
     return this._isSlotted;
   },
 
   isShadowRootNode: function() {
     return this.isNode() && this.nodeFront.isShadowRoot;
   },
 };
+
+module.exports = Selection;
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -58,16 +58,17 @@ support-files =
 [browser_about-devtools-toolbox_reload.js]
 [browser_browser_toolbox.js]
 skip-if = coverage # Bug 1387827
 [browser_browser_toolbox_debugger.js]
 skip-if = os == 'win' || debug # Bug 1282269, 1448084
 [browser_devtools_api.js]
 [browser_devtools_api_destroy.js]
 [browser_dynamic_tool_enabling.js]
+[browser_front_parentFront.js]
 [browser_ignore_toolbox_network_requests.js]
 [browser_keybindings_01.js]
 [browser_keybindings_02.js]
 [browser_keybindings_03.js]
 [browser_menu_api.js]
 [browser_new_activation_workflow.js]
 [browser_source_map-01.js]
 [browser_source_map-absolute.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/browser_front_parentFront.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the Front's parentFront attribute returns the correct parent front.
+
+const TEST_URL = `data:text/html;charset=utf-8,<div id="test"></div>`;
+
+add_task(async function() {
+  gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+  const tab = await addTab(TEST_URL);
+  const target = await TargetFactory.forTab(tab);
+  await target.attach();
+
+  const inspectorFront = await target.getFront("inspector");
+  const walker = inspectorFront.walker;
+  const pageStyleFront = await inspectorFront.getPageStyle();
+  const nodeFront = await walker.querySelector(walker.rootNode, "#test");
+
+  is(
+    inspectorFront.parentFront,
+    target,
+    "Got the correct parentFront from the InspectorFront."
+  );
+  is(
+    walker.parentFront,
+    inspectorFront,
+    "Got the correct parentFront from the WalkerFront."
+  );
+  is(
+    pageStyleFront.parentFront,
+    inspectorFront,
+    "Got the correct parentFront from the PageStyleFront."
+  );
+  is(
+    nodeFront.parentFront,
+    walker,
+    "Got the correct parentFront from the NodeFront."
+  );
+});
--- a/devtools/client/framework/test/browser_target_get-front.js
+++ b/devtools/client/framework/test/browser_target_get-front.js
@@ -14,16 +14,21 @@ add_task(async function() {
 
   info("Test the targetFront attribute for the root");
   const { client } = target;
   is(
     client.mainRoot.targetFront,
     null,
     "got null from the targetFront attribute for the root"
   );
+  is(
+    client.mainRoot.parentFront,
+    null,
+    "got null from the parentFront attribute for the root"
+  );
 
   info("Test getting a front twice");
   const getAccessibilityFront = await target.getFront("accessibility");
   const getAccessibilityFront2 = await target.getFront("accessibility");
   is(
     getAccessibilityFront,
     getAccessibilityFront2,
     "got the same front when calling getFront twice"
@@ -33,16 +38,26 @@ add_task(async function() {
     target,
     "got the correct targetFront attribute from the front"
   );
   is(
     getAccessibilityFront2.targetFront,
     target,
     "got the correct targetFront attribute from the front"
   );
+  is(
+    getAccessibilityFront.parentFront,
+    target,
+    "got the correct parentFront attribute from the front"
+  );
+  is(
+    getAccessibilityFront2.parentFront,
+    target,
+    "got the correct parentFront attribute from the front"
+  );
 
   info("Test getting a front on different targets");
   const target1Front = await target.getFront("accessibility");
   const target2Front = await target2.getFront("accessibility");
   is(
     target1Front !== target2Front,
     true,
     "got different fronts when calling getFront on different targets"
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -19,31 +19,18 @@ const HTML_NS = "http://www.w3.org/1999/
 
 var { Ci, Cc } = require("chrome");
 var promise = require("promise");
 const { debounce } = require("devtools/shared/debounce");
 var Services = require("Services");
 var ChromeUtils = require("ChromeUtils");
 var { gDevTools } = require("devtools/client/framework/devtools");
 var EventEmitter = require("devtools/shared/event-emitter");
+const Selection = require("devtools/client/framework/selection");
 var Telemetry = require("devtools/client/shared/telemetry");
-
-loader.lazyRequireGetter(
-  this,
-  "createToolboxStore",
-  "devtools/client/framework/store",
-  true
-);
-loader.lazyRequireGetter(
-  this,
-  "registerWalkerListeners",
-  "devtools/client/framework/actions/index",
-  true
-);
-
 const { getUnicodeUrl } = require("devtools/client/shared/unicode-url");
 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;
@@ -54,16 +41,28 @@ const { BrowserLoader } = ChromeUtils.im
 
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper(
   "devtools/client/locales/toolbox.properties"
 );
 
 loader.lazyRequireGetter(
   this,
+  "createToolboxStore",
+  "devtools/client/framework/store",
+  true
+);
+loader.lazyRequireGetter(
+  this,
+  "registerWalkerListeners",
+  "devtools/client/framework/actions/index",
+  true
+);
+loader.lazyRequireGetter(
+  this,
   "AppConstants",
   "resource://gre/modules/AppConstants.jsm",
   true
 );
 loader.lazyRequireGetter(this, "flags", "devtools/shared/flags");
 loader.lazyRequireGetter(
   this,
   "KeyShortcuts",
@@ -191,16 +190,17 @@ function Toolbox(
   hostType,
   contentWindow,
   frameId,
   msSinceProcessStart
 ) {
   this._target = target;
   this._win = contentWindow;
   this.frameId = frameId;
+  this.selection = new Selection();
   this.telemetry = new Telemetry();
 
   // The session ID is used to determine which telemetry events belong to which
   // toolbox session. Because we use Amplitude to analyse the telemetry data we
   // must use the time since the system wide epoch as the session ID.
   this.sessionId = msSinceProcessStart;
 
   // Map of the available DevTools WebExtensions:
@@ -250,16 +250,18 @@ function Toolbox(
   this._onToolbarFocus = this._onToolbarFocus.bind(this);
   this._onToolbarArrowKeypress = this._onToolbarArrowKeypress.bind(this);
   this._onPickerClick = this._onPickerClick.bind(this);
   this._onPickerKeypress = this._onPickerKeypress.bind(this);
   this._onPickerStarting = this._onPickerStarting.bind(this);
   this._onPickerStarted = this._onPickerStarted.bind(this);
   this._onPickerStopped = this._onPickerStopped.bind(this);
   this._onPickerCanceled = this._onPickerCanceled.bind(this);
+  this._onPickerPicked = this._onPickerPicked.bind(this);
+  this._onPickerPreviewed = this._onPickerPreviewed.bind(this);
   this._onInspectObject = this._onInspectObject.bind(this);
   this._onNewSelectedNodeFront = this._onNewSelectedNodeFront.bind(this);
   this._onToolSelected = this._onToolSelected.bind(this);
   this._onTargetClosed = this._onTargetClosed.bind(this);
   this._onContextMenu = this._onContextMenu.bind(this);
   this.updateToolboxButtonsVisibility = this.updateToolboxButtonsVisibility.bind(
     this
   );
@@ -294,16 +296,18 @@ function Toolbox(
   this._target.on("will-navigate", this._onWillNavigate);
   this._target.on("navigate", this._refreshHostTitle);
   this._target.on("frame-update", this._updateFrames);
   this._target.on("inspect-object", this._onInspectObject);
 
   this.on("host-changed", this._refreshHostTitle);
   this.on("select", this._onToolSelected);
 
+  this.selection.on("new-node-front", this._onNewSelectedNodeFront);
+
   gDevTools.on("tool-registered", this._toolRegistered);
   gDevTools.on("tool-unregistered", this._toolUnregistered);
 
   /**
    * Get text direction for the current locale direction.
    *
    * `getComputedStyle` forces a synchronous reflow, so use a lazy getter in order to
    * call it only once.
@@ -523,24 +527,16 @@ Toolbox.prototype = {
    * Get the toolbox's walker front. Note that it may not always have been
    * initialized first. Use `initInspector()` if needed.
    */
   get walker() {
     return this._walker;
   },
 
   /**
-   * Get the toolbox's node selection. Note that it may not always have been
-   * initialized first. Use `initInspector()` if needed.
-   */
-  get selection() {
-    return this._selection;
-  },
-
-  /**
    * Get the toggled state of the split console
    */
   get splitConsole() {
     return this._splitConsole;
   },
 
   /**
    * Get the focused state of the split console
@@ -1807,16 +1803,34 @@ Toolbox.prototype = {
   _onPickerStopped: function() {
     this.tellRDMAboutPickerState(false);
     this.off("select", this.inspectorFront.nodePicker.stop);
     this.doc.removeEventListener("keypress", this._onPickerKeypress, true);
     this.pickerButton.isChecked = false;
   },
 
   /**
+   * When the picker is canceled, make sure the toolbox
+   * gets the focus.
+   */
+  _onPickerCanceled: function() {
+    if (this.hostType !== Toolbox.HostType.WINDOW) {
+      this.win.focus();
+    }
+  },
+
+  _onPickerPicked: function(nodeFront) {
+    this.selection.setNodeFront(nodeFront, { reason: "picker-node-picked" });
+  },
+
+  _onPickerPreviewed: function(nodeFront) {
+    this.selection.setNodeFront(nodeFront, { reason: "picker-node-previewed" });
+  },
+
+  /**
    * RDM sometimes simulates touch events. For this to work correctly at all times, it
    * needs to know when the picker is active or not.
    * This method communicates with the RDM Manager if it exists.
    *
    * @param {Boolean} state
    */
   tellRDMAboutPickerState: async function(state) {
     const { tab } = this.target;
@@ -1828,26 +1842,16 @@ Toolbox.prototype = {
       return;
     }
 
     const ui = ResponsiveUIManager.getResponsiveUIForTab(tab);
     await ui.emulationFront.setElementPickerState(state);
   },
 
   /**
-   * When the picker is canceled, make sure the toolbox
-   * gets the focus.
-   */
-  _onPickerCanceled: function() {
-    if (this.hostType !== Toolbox.HostType.WINDOW) {
-      this.win.focus();
-    }
-  },
-
-  /**
    * The element picker button enables the ability to select a DOM node by clicking
    * it on the page.
    */
   _buildPickerButton() {
     this.pickerButton = this._createButtonState({
       id: "command-button-pick",
       description: this._getPickerTooltip(),
       onClick: this._onPickerClick,
@@ -3314,17 +3318,16 @@ Toolbox.prototype = {
       this._initInspector = async function() {
         // Temporary fix for bug #1493131 - inspector has a different life cycle
         // than most other fronts because it is closely related to the toolbox.
         // TODO: replace with getFront once inspector is separated from the toolbox
         // TODO: remove these bindings
         this._inspector = await this.target.getFront("inspector");
         this._walker = this.inspectorFront.walker;
         this._highlighter = this.inspectorFront.highlighter;
-        this._selection = this.inspectorFront.selection;
 
         this.inspectorFront.nodePicker.on(
           "picker-starting",
           this._onPickerStarting
         );
         this.inspectorFront.nodePicker.on(
           "picker-started",
           this._onPickerStarted
@@ -3332,17 +3335,24 @@ Toolbox.prototype = {
         this.inspectorFront.nodePicker.on(
           "picker-stopped",
           this._onPickerStopped
         );
         this.inspectorFront.nodePicker.on(
           "picker-node-canceled",
           this._onPickerCanceled
         );
-        this._selection.on("new-node-front", this._onNewSelectedNodeFront);
+        this.inspectorFront.nodePicker.on(
+          "picker-node-picked",
+          this._onPickerPicked
+        );
+        this.inspectorFront.nodePicker.on(
+          "picker-node-previewed",
+          this._onPickerPreviewed
+        );
         registerWalkerListeners(this);
       }.bind(this)();
     }
     return this._initInspector;
   },
 
   /**
    * An helper function that returns an object contain a highlighter and unhighlighter
@@ -3451,17 +3461,16 @@ Toolbox.prototype = {
     }
 
     // Temporary fix for bug #1493131 - inspector has a different life cycle
     // than most other fronts because it is closely related to the toolbox.
     this._inspector.destroy();
 
     this._inspector = null;
     this._highlighter = null;
-    this._selection = null;
     this._walker = null;
   },
 
   /**
    * Get the toolbox's notification component
    *
    * @return The notification box component.
    */
@@ -3619,17 +3628,20 @@ Toolbox.prototype = {
     // then destroying the host, successfully or not) before destroying the
     // target.
     const onceDestroyed = new Promise(resolve => {
       resolve(
         settleAll(outstanding)
           .catch(console.error)
           .then(async () => {
             // Destroying the walker and inspector fronts
-            await this.destroyInspector();
+            this.destroyInspector();
+
+            this.selection.destroy();
+            this.selection = null;
 
             if (this._netMonitorAPI) {
               this._netMonitorAPI.destroy();
               this._netMonitorAPI = null;
             }
 
             this._removeWindowListeners();
             this._removeChromeEventHandlerEvents();
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -224,17 +224,16 @@ Inspector.prototype = {
     this._markupBox = this.panelDoc.getElementById("markup-box");
 
     return this._deferredOpen();
   },
 
   async initInspectorFront() {
     this.inspectorFront = await this.target.getFront("inspector");
     this.highlighter = this.inspectorFront.highlighter;
-    this.selection = this.inspectorFront.selection;
     this.walker = this.inspectorFront.walker;
   },
 
   get toolbox() {
     return this._toolbox;
   },
 
   get highlighters() {
@@ -292,16 +291,20 @@ Inspector.prototype = {
         this.searchBox,
         this.searchClearButton
       );
     }
 
     return this._search;
   },
 
+  get selection() {
+    return this.toolbox.selection;
+  },
+
   get cssProperties() {
     return this._cssProperties.cssProperties;
   },
 
   /**
    * Handle promise rejections for various asynchronous actions, and only log errors if
    * the inspector panel still exists.
    * This is useful to silence useless errors that happen when the inspector is closed
--- a/devtools/client/shared/widgets/ShapesInContextEditor.js
+++ b/devtools/client/shared/widgets/ShapesInContextEditor.js
@@ -149,16 +149,21 @@ class ShapesInContextEditor {
    */
   async hide() {
     try {
       await this.highlighter.hide();
     } catch (err) {
       // silent error
     }
 
+    // Stop if the panel has been destroyed during the call to hide.
+    if (this.destroyed) {
+      return;
+    }
+
     if (this.swatch) {
       this.swatch.classList.remove("active");
     }
     this.swatch = null;
     this.rule = null;
     this.textPropIndex = -1;
     this.textPropName = null;
 
@@ -329,12 +334,14 @@ class ShapesInContextEditor {
 
     this.textProperty.setValue(value);
   }
 
   destroy() {
     this.highlighter.off("highlighter-event", this.onHighlighterEvent);
     this.ruleView.off("ruleview-changed", this.onRuleViewChanged);
     this.highligherEventHandlers = {};
+
+    this.destroyed = true;
   }
 }
 
 module.exports = ShapesInContextEditor;
--- a/devtools/shared/fronts/accessibility.js
+++ b/devtools/shared/fronts/accessibility.js
@@ -1,27 +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/. */
+
 "use strict";
 
 const {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol.js");
 const {
   accessibleSpec,
   accessibleWalkerSpec,
   accessibilitySpec,
 } = require("devtools/shared/specs/accessibility");
 const events = require("devtools/shared/event-emitter");
 
 class AccessibleFront extends FrontClassWithSpec(accessibleSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     this.before("audited", this.audited.bind(this));
     this.before("name-change", this.nameChange.bind(this));
     this.before("value-change", this.valueChange.bind(this));
     this.before("description-change", this.descriptionChange.bind(this));
     this.before("shortcut-change", this.shortcutChange.bind(this));
     this.before("reorder", this.reorder.bind(this));
     this.before("text-change", this.textChange.bind(this));
@@ -151,18 +152,18 @@ class AccessibleFront extends FrontClass
   hydrate() {
     return super.hydrate().then(properties => {
       Object.assign(this._form, properties);
     });
   }
 }
 
 class AccessibleWalkerFront extends FrontClassWithSpec(accessibleWalkerSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
     this.before("accessible-destroy", this.accessibleDestroy.bind(this));
   }
 
   accessibleDestroy(accessible) {
     accessible.destroy();
   }
 
   form(json) {
@@ -174,18 +175,18 @@ class AccessibleWalkerFront extends Fron
       return this.pickAndFocus();
     }
 
     return super.pick();
   }
 }
 
 class AccessibilityFront extends FrontClassWithSpec(accessibilitySpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     this.before("init", this.init.bind(this));
     this.before("shutdown", this.shutdown.bind(this));
     this.before("can-be-enabled-change", this.canBeEnabled.bind(this));
     this.before("can-be-disabled-change", this.canBeDisabled.bind(this));
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "accessibilityActor";
--- a/devtools/shared/fronts/addon/addons.js
+++ b/devtools/shared/fronts/addon/addons.js
@@ -1,22 +1,23 @@
 /* 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 { addonsSpec } = require("devtools/shared/specs/addon/addons");
 const {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 
 class AddonsFront extends FrontClassWithSpec(addonsSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "addonsActor";
   }
 }
 
 exports.AddonsFront = AddonsFront;
 registerFront(AddonsFront);
--- a/devtools/shared/fronts/addon/webextension-inspected-window.js
+++ b/devtools/shared/fronts/addon/webextension-inspected-window.js
@@ -1,11 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const {
   webExtensionInspectedWindowSpec,
 } = require("devtools/shared/specs/addon/webextension-inspected-window");
 
 const {
   FrontClassWithSpec,
@@ -13,18 +14,18 @@ const {
 } = require("devtools/shared/protocol");
 
 /**
  * The corresponding Front object for the WebExtensionInspectedWindowActor.
  */
 class WebExtensionInspectedWindowFront extends FrontClassWithSpec(
   webExtensionInspectedWindowSpec
 ) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "webExtensionInspectedWindowActor";
   }
 }
 
 exports.WebExtensionInspectedWindowFront = WebExtensionInspectedWindowFront;
 registerFront(WebExtensionInspectedWindowFront);
--- a/devtools/shared/fronts/animation.js
+++ b/devtools/shared/fronts/animation.js
@@ -1,25 +1,26 @@
 /* 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 {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const {
   animationPlayerSpec,
   animationsSpec,
 } = require("devtools/shared/specs/animation");
 
 class AnimationPlayerFront extends FrontClassWithSpec(animationPlayerSpec) {
-  constructor(conn, form) {
-    super(conn, form);
+  constructor(conn, targetFront, parentFront) {
+    super(conn, targetFront, parentFront);
 
     this.state = {};
     this.before("changed", this.onChanged.bind(this));
   }
 
   form(form) {
     this._form = form;
     this.state = this.initialState;
@@ -195,18 +196,18 @@ class AnimationPlayerFront extends Front
     };
   }
 }
 
 exports.AnimationPlayerFront = AnimationPlayerFront;
 registerFront(AnimationPlayerFront);
 
 class AnimationsFront extends FrontClassWithSpec(animationsSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "animationsActor";
   }
 }
 
 exports.AnimationsFront = AnimationsFront;
 registerFront(AnimationsFront);
--- a/devtools/shared/fronts/changes.js
+++ b/devtools/shared/fronts/changes.js
@@ -9,18 +9,18 @@ const {
   registerFront,
 } = require("devtools/shared/protocol");
 const { changesSpec } = require("devtools/shared/specs/changes");
 
 /**
  * ChangesFront, the front object for the ChangesActor
  */
 class ChangesFront extends FrontClassWithSpec(changesSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "changesActor";
   }
 }
 
 exports.ChangesFront = ChangesFront;
 registerFront(ChangesFront);
--- a/devtools/shared/fronts/css-properties.js
+++ b/devtools/shared/fronts/css-properties.js
@@ -1,97 +1,72 @@
 /* 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";
 
-loader.lazyRequireGetter(
-  this,
-  "CSS_PROPERTIES_DB",
-  "devtools/shared/css/properties-db",
-  true
-);
+const {
+  FrontClassWithSpec,
+  registerFront,
+} = require("devtools/shared/protocol");
+const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
 
 loader.lazyRequireGetter(
   this,
   "cssColors",
   "devtools/shared/css/color-db",
   true
 );
-
-const {
-  FrontClassWithSpec,
-  registerFront,
-} = require("devtools/shared/protocol");
-const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
+loader.lazyRequireGetter(
+  this,
+  "CSS_PROPERTIES_DB",
+  "devtools/shared/css/properties-db",
+  true
+);
+loader.lazyRequireGetter(
+  this,
+  "CSS_TYPES",
+  "devtools/shared/css/constants",
+  true
+);
 
 /**
  * Build up a regular expression that matches a CSS variable token. This is an
  * ident token that starts with two dashes "--".
  *
  * https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
  */
 var NON_ASCII = "[^\\x00-\\x7F]";
 var ESCAPE = "\\\\[^\n\r]";
 var FIRST_CHAR = ["[_a-z]", NON_ASCII, ESCAPE].join("|");
 var TRAILING_CHAR = ["[_a-z0-9-]", NON_ASCII, ESCAPE].join("|");
 var IS_VARIABLE_TOKEN = new RegExp(
   `^--(${FIRST_CHAR})(${TRAILING_CHAR})*$`,
   "i"
 );
 
-loader.lazyRequireGetter(
-  this,
-  "CSS_TYPES",
-  "devtools/shared/css/constants",
-  true
-);
-
-/**
- * Check that this is a CSS variable.
- *
- * @param {String} input
- * @return {Boolean}
- */
-function isCssVariable(input) {
-  return !!input.match(IS_VARIABLE_TOKEN);
-}
-
 var cachedCssProperties = new WeakMap();
 
 /**
  * The CssProperties front provides a mechanism to have a one-time asynchronous
  * load of a CSS properties database. This is then fed into the CssProperties
  * interface that provides synchronous methods for finding out what CSS
  * properties the current server supports.
  */
 class CssPropertiesFront extends FrontClassWithSpec(cssPropertiesSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront) {
+    super(client, targetFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "cssPropertiesActor";
   }
 }
 
 /**
- * Query the feature supporting status in the featureSet.
- *
- * @param {Hashmap} featureSet the feature set hashmap
- * @param {String} feature the feature name string
- * @return {Boolean} has the feature or not
- */
-function hasFeature(featureSet, feature) {
-  if (feature in featureSet) {
-    return featureSet[feature];
-  }
-  return false;
-}
-
-/**
  * Ask questions to a CSS database. This class does not care how the database
  * gets loaded in, only the questions that you can ask to it.
  * Prototype functions are bound to 'this' so they can be passed around as helper
  * functions.
  *
  * @param {Object} db
  *                 A database of CSS properties
  * @param {Object} inheritedList
@@ -205,16 +180,40 @@ CssProperties.prototype = {
    * @return {Boolean} Return true if the server supports css-color-4 color function.
    */
   supportsCssColor4ColorFunction() {
     return this.cssColor4ColorFunction;
   },
 };
 
 /**
+ * Check that this is a CSS variable.
+ *
+ * @param {String} input
+ * @return {Boolean}
+ */
+function isCssVariable(input) {
+  return !!input.match(IS_VARIABLE_TOKEN);
+}
+
+/**
+ * Query the feature supporting status in the featureSet.
+ *
+ * @param {Hashmap} featureSet the feature set hashmap
+ * @param {String} feature the feature name string
+ * @return {Boolean} has the feature or not
+ */
+function hasFeature(featureSet, feature) {
+  if (feature in featureSet) {
+    return featureSet[feature];
+  }
+  return false;
+}
+
+/**
  * Create a CssProperties object with a fully loaded CSS database. The
  * CssProperties interface can be queried synchronously, but the initialization
  * is potentially async and should be handled up-front when the tool is created.
  *
  * The front is returned only with this function so that it can be destroyed
  * once the toolbox is destroyed.
  *
  * @param {Toolbox} The current toolbox.
--- a/devtools/shared/fronts/device.js
+++ b/devtools/shared/fronts/device.js
@@ -1,24 +1,25 @@
 /* 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 { Cu } = require("chrome");
 const { deviceSpec } = require("devtools/shared/specs/device");
 const {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const defer = require("devtools/shared/defer");
 
 class DeviceFront extends FrontClassWithSpec(deviceSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "deviceActor";
 
     // Backward compatibility when connected to Firefox 69 or older.
     // Re-emit the "multi-e10s-updated" event as "can-debug-sw-updated".
     // Front events are all cleared via EventEmitter::clearEvents in the Front
     // base class destroy.
--- a/devtools/shared/fronts/emulation.js
+++ b/devtools/shared/fronts/emulation.js
@@ -1,25 +1,26 @@
 /* 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 {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const { emulationSpec } = require("devtools/shared/specs/emulation");
 
 /**
  * The corresponding Front object for the EmulationActor.
  */
 class EmulationFront extends FrontClassWithSpec(emulationSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "emulationActor";
   }
 }
 
 exports.EmulationFront = EmulationFront;
 registerFront(EmulationFront);
--- a/devtools/shared/fronts/framerate.js
+++ b/devtools/shared/fronts/framerate.js
@@ -1,25 +1,26 @@
 /* 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 {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const { framerateSpec } = require("devtools/shared/specs/framerate");
 
 /**
  * The corresponding Front object for the FramerateActor.
  */
 class FramerateFront extends FrontClassWithSpec(framerateSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "framerateActor";
   }
 }
 
 exports.FramerateFront = FramerateFront;
 registerFront(FramerateFront);
--- a/devtools/shared/fronts/highlighters.js
+++ b/devtools/shared/fronts/highlighters.js
@@ -1,26 +1,27 @@
 /* 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 {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const flags = require("devtools/shared/flags");
 const {
   customHighlighterSpec,
   highlighterSpec,
 } = require("devtools/shared/specs/highlighters");
 
 class HighlighterFront extends FrontClassWithSpec(highlighterSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     this.isNodeFrontHighlighted = false;
     this.isPicking = false;
   }
 
   // Update the object given a form representation off the wire.
   form(json) {
     this.actorID = json.actor;
@@ -97,18 +98,18 @@ class HighlighterFront extends FrontClas
     this.emit("node-unhighlight");
   }
 }
 
 exports.HighlighterFront = HighlighterFront;
 registerFront(HighlighterFront);
 
 class CustomHighlighterFront extends FrontClassWithSpec(customHighlighterSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     this._isShown = false;
   }
 
   show(...args) {
     this._isShown = true;
     return super.show(...args);
   }
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -1,64 +1,60 @@
 /* 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 Services = require("Services");
+const defer = require("devtools/shared/defer");
 const Telemetry = require("devtools/client/shared/telemetry");
-const telemetry = new Telemetry();
 const { NodePicker } = require("devtools/shared/fronts/inspector/node-picker");
-const TELEMETRY_EYEDROPPER_OPENED = "DEVTOOLS_EYEDROPPER_OPENED_COUNT";
-const TELEMETRY_EYEDROPPER_OPENED_MENU =
-  "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT";
-const SHOW_ALL_ANONYMOUS_CONTENT_PREF =
-  "devtools.inspector.showAllAnonymousContent";
-const SHOW_UA_SHADOW_ROOTS_PREF = "devtools.inspector.showUserAgentShadowRoots";
-
 const {
   FrontClassWithSpec,
   types,
   registerFront,
 } = require("devtools/shared/protocol.js");
 const {
   inspectorSpec,
   walkerSpec,
 } = require("devtools/shared/specs/inspector");
 
-const Services = require("Services");
-const defer = require("devtools/shared/defer");
 loader.lazyRequireGetter(
   this,
   "nodeConstants",
   "devtools/shared/dom-node-constants"
 );
-loader.lazyRequireGetter(
-  this,
-  "Selection",
-  "devtools/client/framework/selection",
-  true
-);
 loader.lazyRequireGetter(this, "flags", "devtools/shared/flags");
 
+const TELEMETRY_EYEDROPPER_OPENED = "DEVTOOLS_EYEDROPPER_OPENED_COUNT";
+const TELEMETRY_EYEDROPPER_OPENED_MENU =
+  "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT";
+const SHOW_ALL_ANONYMOUS_CONTENT_PREF =
+  "devtools.inspector.showAllAnonymousContent";
+const SHOW_UA_SHADOW_ROOTS_PREF = "devtools.inspector.showUserAgentShadowRoots";
+
+const telemetry = new Telemetry();
+
 /**
  * Client side of the DOM walker.
  */
 class WalkerFront extends FrontClassWithSpec(walkerSpec) {
   /**
    * This is kept for backward-compatibility reasons with older remote target.
    * Targets previous to bug 916443
    */
   pick() {
     return super.pick().then(response => {
       return response.node;
     });
   }
 
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
     this._createRootNodePromise();
     this._orphaned = new Set();
     this._retainedOrphans = new Set();
 
     // Set to true if cleanup should be requested after every mutation list.
     this.autoCleanup = true;
 
     this.before("new-mutations", this.onMutations.bind(this));
@@ -476,36 +472,30 @@ class WalkerFront extends FrontClassWith
 exports.WalkerFront = WalkerFront;
 registerFront(WalkerFront);
 
 /**
  * Client side of the inspector actor, which is used to create
  * inspector-related actors, including the walker.
  */
 class InspectorFront extends FrontClassWithSpec(inspectorSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     this._client = client;
     this._highlighters = new Map();
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "inspectorActor";
   }
 
   // async initialization
   async initialize() {
     await Promise.all([this._getWalker(), this._getHighlighter()]);
-
-    this.selection = new Selection(this.walker);
-    this.nodePicker = new NodePicker(
-      this.highlighter,
-      this.walker,
-      this.selection
-    );
+    this.nodePicker = new NodePicker(this.highlighter, this.walker);
   }
 
   async _getWalker() {
     const showAllAnonymousContent = Services.prefs.getBoolPref(
       SHOW_ALL_ANONYMOUS_CONTENT_PREF
     );
     const showUserAgentShadowRoots = Services.prefs.getBoolPref(
       SHOW_UA_SHADOW_ROOTS_PREF
@@ -521,19 +511,16 @@ class InspectorFront extends FrontClassW
     this.highlighter = await this.getHighlighter(autohide);
   }
 
   hasHighlighter(type) {
     return this._highlighters.has(type);
   }
 
   destroy() {
-    // Selection isn't a Front and so isn't managed by InspectorFront
-    // and has to be destroyed manually
-    this.selection.destroy();
     // Highlighter fronts are managed by InspectorFront and so will be
     // automatically destroyed. But we have to clear the `_highlighters`
     // Map as well as explicitly call `finalize` request on all of them.
     this.destroyHighlighters();
     super.destroy();
   }
 
   destroyHighlighters() {
--- a/devtools/shared/fronts/inspector/node-picker.js
+++ b/devtools/shared/fronts/inspector/node-picker.js
@@ -1,37 +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";
+
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
 /**
  * Client-side NodePicker module.
  * To be used by inspector front when it needs to select DOM elements.
  */
 
 /**
  * Get the NodePicker instance for an inspector front.
  * The NodePicker wraps the highlighter so that it can interact with the
  * walkerFront and selection api. The nodeFront is stateless, with the
  * HighlighterFront managing it's own state.
  *
  * @param {highlighter} highlighterFront
  * @param {walker} walkerFront
- * @param {selection} selection api
  * @return {Object} the NodePicker public API
  */
 class NodePicker extends EventEmitter {
-  constructor(highlighter, walker, selection) {
+  constructor(highlighter, walker) {
     super();
     this.highlighter = highlighter;
     this.walker = walker;
-    this.selection = selection;
 
     this.cancel = this.cancel.bind(this);
     this.start = this.start.bind(this);
     this.stop = this.stop.bind(this);
     this.togglePicker = this.togglePicker.bind(this);
 
     this._onHovered = this._onHovered.bind(this);
     this._onPicked = this._onPicked.bind(this);
@@ -112,27 +111,27 @@ class NodePicker extends EventEmitter {
     this.emit("picker-node-hovered", data.node);
   }
 
   /**
    * When a node has been picked while the highlighter is in picker mode
    * @param {Object} data Information about the picked node
    */
   _onPicked(data) {
-    this.selection.setNodeFront(data.node, { reason: "picker-node-picked" });
+    this.emit("picker-node-picked", data.node);
     return this.stop();
   }
 
   /**
    * When a node has been shift-clicked (previewed) while the highlighter is in
    * picker mode
    * @param {Object} data Information about the picked node
    */
   _onPreviewed(data) {
-    this.selection.setNodeFront(data.node, { reason: "picker-node-previewed" });
+    this.emit("picker-node-previewed", data.node);
   }
 
   /**
    * When the picker is canceled, stop the picker, and make sure the toolbox
    * gets the focus.
    */
   _onCanceled() {
     return this.cancel();
--- a/devtools/shared/fronts/memory.js
+++ b/devtools/shared/fronts/memory.js
@@ -1,11 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const { memorySpec } = require("devtools/shared/specs/memory");
 const {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 
@@ -17,18 +18,18 @@ loader.lazyRequireGetter(
 );
 loader.lazyRequireGetter(
   this,
   "HeapSnapshotFileUtils",
   "devtools/shared/heapsnapshot/HeapSnapshotFileUtils"
 );
 
 class MemoryFront extends FrontClassWithSpec(memorySpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
     this._client = client;
     this.heapSnapshotFileActorID = null;
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "memoryActor";
   }
 
   /**
--- a/devtools/shared/fronts/node.js
+++ b/devtools/shared/fronts/node.js
@@ -1,22 +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/. */
+
 "use strict";
 
+const promise = require("promise");
 const {
   FrontClassWithSpec,
   types,
   registerFront,
 } = require("devtools/shared/protocol.js");
-
 const { nodeSpec, nodeListSpec } = require("devtools/shared/specs/node");
-
-const promise = require("promise");
 const { SimpleStringFront } = require("devtools/shared/fronts/string");
 
 loader.lazyRequireGetter(
   this,
   "nodeConstants",
   "devtools/shared/dom-node-constants"
 );
 
@@ -109,18 +108,18 @@ class AttributeModificationList {
  * Children are stored in a doubly-linked list, to make addition/removal
  * and traversal quick.
  *
  * Due to the order/incompleteness of the child list, it is safe to use
  * the parent node from clients, but the `children` request should be used
  * to traverse children.
  */
 class NodeFront extends FrontClassWithSpec(nodeSpec) {
-  constructor(conn, form) {
-    super(conn, form);
+  constructor(conn, targetFront, parentFront) {
+    super(conn, targetFront, parentFront);
     // The parent node
     this._parent = null;
     // The first child of this node.
     this._child = null;
     // The next sibling of this node.
     this._next = null;
     // The previous sibling of this node.
     this._prev = null;
@@ -395,16 +394,20 @@ class NodeFront extends FrontClassWithSp
       if (!parent.isDisplayed) {
         return false;
       }
       parent = parent.parentNode();
     }
     return true;
   }
 
+  get walkerFront() {
+    return this.parentFront;
+  }
+
   getNodeValue() {
     // backward-compatibility: if nodevalue is null and shortValue is defined, the actual
     // value of the node needs to be fetched on the server.
     if (this._form.nodeValue === null && this._form.shortValue) {
       return super.getNodeValue();
     }
 
     const str = this._form.nodeValue || "";
--- a/devtools/shared/fronts/perf.js
+++ b/devtools/shared/fronts/perf.js
@@ -1,21 +1,22 @@
 /* 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 {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const { perfSpec } = require("devtools/shared/specs/perf");
 
 class PerfFront extends FrontClassWithSpec(perfSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "perfActor";
   }
 }
 
 registerFront(PerfFront);
--- a/devtools/shared/fronts/performance-recording.js
+++ b/devtools/shared/fronts/performance-recording.js
@@ -58,18 +58,18 @@ class PerformanceRecordingFront extends 
     // just finished. This is because GC/Compositing markers can come into the array out
     // of order with the other markers, leading to strange collapsing in waterfall view.
     if (this._completed && !this._markersSorted) {
       this._markers = this._markers.sort((a, b) => a.start > b.start);
       this._markersSorted = true;
     }
   }
 
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
     this._markers = [];
     this._frames = [];
     this._memory = [];
     this._ticks = [];
     this._allocations = { sites: [], timestamps: [], frames: [], sizes: [] };
   }
 
   /**
--- a/devtools/shared/fronts/performance.js
+++ b/devtools/shared/fronts/performance.js
@@ -1,11 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const { Cu } = require("chrome");
 const {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const {
@@ -16,18 +17,18 @@ const { performanceSpec } = require("dev
 loader.lazyRequireGetter(
   this,
   "PerformanceIO",
   "devtools/client/performance/modules/io"
 );
 loader.lazyRequireGetter(this, "getSystemInfo", "devtools/shared/system", true);
 
 class PerformanceFront extends FrontClassWithSpec(performanceSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
     this._queuedRecordings = [];
     this._onRecordingStartedEvent = this._onRecordingStartedEvent.bind(this);
     this.flushQueuedRecordings = this.flushQueuedRecordings.bind(this);
 
     this.before("profiler-status", this._onProfilerStatus.bind(this));
     this.before("timeline-data", this._onTimelineEvent.bind(this));
     this.on("recording-started", this._onRecordingStartedEvent);
 
--- a/devtools/shared/fronts/preference.js
+++ b/devtools/shared/fronts/preference.js
@@ -1,22 +1,23 @@
 /* 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 { preferenceSpec } = require("devtools/shared/specs/preference");
 const {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 
 class PreferenceFront extends FrontClassWithSpec(preferenceSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "preferenceActor";
   }
 }
 
 exports.PreferenceFront = PreferenceFront;
 registerFront(PreferenceFront);
--- a/devtools/shared/fronts/promises.js
+++ b/devtools/shared/fronts/promises.js
@@ -1,25 +1,26 @@
 /* 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 {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const { promisesSpec } = require("devtools/shared/specs/promises");
 
 /**
  * PromisesFront, the front for the PromisesActor.
  */
 class PromisesFront extends FrontClassWithSpec(promisesSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "promisesActor";
   }
 }
 
 exports.PromisesFront = PromisesFront;
 registerFront(PromisesFront);
--- a/devtools/shared/fronts/reflow.js
+++ b/devtools/shared/fronts/reflow.js
@@ -14,18 +14,18 @@ const {
  * Usage example of the reflow front:
  *
  * let front = await target.getFront("reflow");
  * front.on("reflows", this._onReflows);
  * front.start();
  * // now wait for events to come
  */
 class ReflowFront extends FrontClassWithSpec(reflowSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "reflowActor";
   }
 }
 
 exports.ReflowFront = ReflowFront;
 registerFront(ReflowFront);
--- a/devtools/shared/fronts/screenshot.js
+++ b/devtools/shared/fronts/screenshot.js
@@ -1,23 +1,24 @@
 /* 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 { screenshotSpec } = require("devtools/shared/specs/screenshot");
 const saveScreenshot = require("devtools/shared/screenshot/save");
 const {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 
 class ScreenshotFront extends FrontClassWithSpec(screenshotSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "screenshotActor";
   }
 
   async captureAndSave(window, args) {
     const screenshot = await this.capture(args);
     return saveScreenshot(window, args, screenshot);
--- a/devtools/shared/fronts/storage.js
+++ b/devtools/shared/fronts/storage.js
@@ -1,11 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const { childSpecs, storageSpec } = require("devtools/shared/specs/storage");
 
@@ -16,18 +17,18 @@ for (const childSpec of Object.values(ch
       this.hosts = form.hosts;
       return null;
     }
   }
   registerFront(ChildStorageFront);
 }
 
 class StorageFront extends FrontClassWithSpec(storageSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "storageActor";
   }
 }
 
 exports.StorageFront = StorageFront;
 registerFront(StorageFront);
--- a/devtools/shared/fronts/styles.js
+++ b/devtools/shared/fronts/styles.js
@@ -1,11 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const {
   pageStyleSpec,
@@ -18,18 +19,18 @@ loader.lazyRequireGetter(
   "RuleRewriter",
   "devtools/shared/fronts/inspector/rule-rewriter"
 );
 
 /**
  * PageStyleFront, the front object for the PageStyleActor
  */
 class PageStyleFront extends FrontClassWithSpec(pageStyleSpec) {
-  constructor(conn) {
-    super(conn);
+  constructor(conn, targetFront, parentFront) {
+    super(conn, targetFront, parentFront);
     this.inspector = this.parent();
   }
 
   form(form) {
     this._form = form;
   }
 
   get walker() {
@@ -79,18 +80,18 @@ class PageStyleFront extends FrontClassW
 
 exports.PageStyleFront = PageStyleFront;
 registerFront(PageStyleFront);
 
 /**
  * StyleRuleFront, the front for the StyleRule actor.
  */
 class StyleRuleFront extends FrontClassWithSpec(styleRuleSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     this.before("location-changed", this._locationChangedPre.bind(this));
   }
 
   form(form) {
     this.actorID = form.actor;
     this._form = form;
     if (this._mediaText) {
--- a/devtools/shared/fronts/stylesheets.js
+++ b/devtools/shared/fronts/stylesheets.js
@@ -1,11 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const {
   mediaRuleSpec,
@@ -26,18 +27,18 @@ loader.lazyRequireGetter(
   "devtools/shared/indentation",
   true
 );
 
 /**
  * Corresponding client-side front for a MediaRuleActor.
  */
 class MediaRuleFront extends FrontClassWithSpec(mediaRuleSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     this._onMatchesChange = this._onMatchesChange.bind(this);
     this.on("matches-change", this._onMatchesChange);
   }
 
   _onMatchesChange(matches) {
     this._form.matches = matches;
   }
@@ -69,18 +70,18 @@ class MediaRuleFront extends FrontClassW
 
 exports.MediaRuleFront = MediaRuleFront;
 registerFront(MediaRuleFront);
 
 /**
  * StyleSheetFront is the client-side counterpart to a StyleSheetActor.
  */
 class StyleSheetFront extends FrontClassWithSpec(styleSheetSpec) {
-  constructor(conn, form) {
-    super(conn, form);
+  constructor(conn, targetFront, parentFront) {
+    super(conn, targetFront, parentFront);
 
     this._onPropertyChange = this._onPropertyChange.bind(this);
     this.on("property-change", this._onPropertyChange);
   }
 
   destroy() {
     this.off("property-change", this._onPropertyChange);
     super.destroy();
@@ -146,18 +147,18 @@ class StyleSheetFront extends FrontClass
 
 exports.StyleSheetFront = StyleSheetFront;
 registerFront(StyleSheetFront);
 
 /**
  * The corresponding Front object for the StyleSheetsActor.
  */
 class StyleSheetsFront extends FrontClassWithSpec(styleSheetsSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "styleSheetsActor";
   }
 }
 
 exports.StyleSheetsFront = StyleSheetsFront;
 registerFront(StyleSheetsFront);
--- a/devtools/shared/fronts/targets/addon.js
+++ b/devtools/shared/fronts/targets/addon.js
@@ -11,18 +11,18 @@ const {
 loader.lazyRequireGetter(
   this,
   "BrowsingContextTargetFront",
   "devtools/shared/fronts/targets/browsing-context",
   true
 );
 
 class AddonTargetFront extends FrontClassWithSpec(addonTargetSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     this.client = client;
 
     this.traits = {};
   }
 
   form(json) {
     this.actorID = json.actor;
--- a/devtools/shared/fronts/thread.js
+++ b/devtools/shared/fronts/thread.js
@@ -29,18 +29,18 @@ loader.lazyRequireGetter(
  * is a front to the thread actor created in the server side, hiding the
  * protocol details in a traditional JavaScript API.
  *
  * @param client DebuggerClient
  * @param actor string
  *        The actor ID for this thread.
  */
 class ThreadFront extends FrontClassWithSpec(threadSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
     this.client = client;
     this._pauseGrips = {};
     this._threadGrips = {};
     this._state = "paused";
     this._beforePaused = this._beforePaused.bind(this);
     this._beforeResumed = this._beforeResumed.bind(this);
     this._beforeDetached = this._beforeDetached.bind(this);
     this.before("paused", this._beforePaused);
--- a/devtools/shared/fronts/webconsole.js
+++ b/devtools/shared/fronts/webconsole.js
@@ -13,25 +13,22 @@ const {
   registerFront,
 } = require("devtools/shared/protocol");
 const { webconsoleSpec } = require("devtools/shared/specs/webconsole");
 
 /**
  * A WebConsoleClient is used as a front end for the WebConsoleActor that is
  * created on the server, hiding implementation details.
  *
- * @param object debuggerClient
+ * @param object client
  *        The DebuggerClient instance we live for.
- * @param object response
- *        The response packet received from the "startListeners" request sent to
- *        the WebConsoleActor.
  */
 class WebConsoleFront extends FrontClassWithSpec(webconsoleSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
     this._client = client;
     this.traits = {};
     this._longStrings = {};
     this.events = [];
 
     // Attribute name from which to retrieve the actorID out of the target actor's form
     this.formAttributeName = "consoleActor";
     /**
--- a/devtools/shared/fronts/websocket.js
+++ b/devtools/shared/fronts/websocket.js
@@ -1,26 +1,27 @@
 /* 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 {
   FrontClassWithSpec,
   registerFront,
 } = require("devtools/shared/protocol");
 const { webSocketSpec } = require("devtools/shared/specs/websocket");
 
 /**
  * A WebSocketFront is used as a front end for the WebSocketActor that is
  * created on the server, hiding implementation details.
  */
 class WebSocketFront extends FrontClassWithSpec(webSocketSpec) {
-  constructor(client) {
-    super(client);
+  constructor(client, targetFront, parentFront) {
+    super(client, targetFront, parentFront);
 
     this._onWebSocketOpened = this._onWebSocketOpened.bind(this);
     this._onWebSocketClosed = this._onWebSocketClosed.bind(this);
     this._onFrameSent = this._onFrameSent.bind(this);
     this._onFrameReceived = this._onFrameReceived.bind(this);
 
     // Attribute name from which to retrieve the actorID
     // out of the target actor's form
--- a/devtools/shared/protocol/Front.js
+++ b/devtools/shared/protocol/Front.js
@@ -25,29 +25,38 @@ function defer() {
     reject: reject,
     promise: promise,
   };
 }
 
 /**
  * Base class for client-side actor fronts.
  *
- * @param optional conn
- *   Either a DebuggerServerConnection or a DebuggerClient.  Must have
+ * @param [DebuggerClient|null] conn
+ *   The conn must either be DebuggerClient or null. Must have
  *   addActorPool, removeActorPool, and poolFor.
  *   conn can be null if the subclass provides a conn property.
+ * @param [Target|null] target
+ *   If we are instantiating a target-scoped front, this is a reference to the front's
+ *   Target instance, otherwise this is null.
+ * @param [Front|null] parentFront
+ *   The parent front. This is only available if the Front being initialized is a child
+ *   of a parent front.
  * @constructor
  */
 class Front extends Pool {
-  constructor(conn = null) {
+  constructor(conn = null, targetFront = null, parentFront = null) {
     super(conn);
     this.actorID = null;
     // The targetFront attribute represents the debuggable context. Only target-scoped
     // fronts and their children fronts will have the targetFront attribute set.
-    this.targetFront = null;
+    this.targetFront = targetFront;
+    // The parentFront attribute points to its parent front. Only children of
+    // target-scoped fronts will have the parentFront attribute set.
+    this.parentFront = parentFront;
     this._requests = [];
 
     // Front listener functions registered via `onFront` get notified
     // of new fronts via this dedicated EventEmitter object.
     this._frontListeners = new EventEmitter();
 
     // List of optional listener for each event, that is processed immediatly on packet
     // receival, before emitting event via EventEmitter on the Front.
@@ -70,16 +79,17 @@ class Front extends Pool {
         "\n\nRequest stack:\n" +
         stack.formattedStack;
       deferred.reject(new Error(msg));
     }
     super.destroy();
     this.clearEvents();
     this.actorID = null;
     this.targetFront = null;
+    this.parentFront = null;
     this._frontListeners = null;
     this._beforeListeners = null;
   }
 
   manage(front) {
     if (!front.actorID) {
       throw new Error(
         "Can't manage front without an actor ID.\n" +
--- a/devtools/shared/protocol/types.js
+++ b/devtools/shared/protocol/types.js
@@ -320,25 +320,24 @@ types.addActorType = function(name) {
         // If front isn't instantiated yet, create one.
         // Try lazy loading front if not already loaded.
         // The front module will synchronously call `FrontClassWithSpec` and
         // augment `type` with the `frontClass` attribute.
         if (!type.frontClass) {
           lazyLoadFront(name);
         }
 
+        const parentFront = ctx.marshallPool();
+        const targetFront = parentFront.targetFront;
+
         // Use intermediate Class variable to please eslint requiring
         // a capital letter for all constructors.
         const Class = type.frontClass;
-        front = new Class(ctx.conn);
+        front = new Class(ctx.conn, targetFront, parentFront);
         front.actorID = actorID;
-        const parentFront = ctx.marshallPool();
-        // If this is a child of a target-scoped front, propagate the target front to the
-        // child front that it manages.
-        front.targetFront = parentFront.targetFront;
         parentFront.manage(front);
       }
 
       // When the type `${name}#actorid` is used, `v` is a string refering to the
       // actor ID. We only set the actorID just before and so do not need anything else.
       if (detail != "actorid") {
         v = identityWrite(v);
         front.form(v, ctx);
@@ -497,26 +496,24 @@ exports.registerFront = function(cls) {
  * @param [Target|null] target
  *    If we are instantiating a target-scoped front, this is a reference to the front's
  *    Target instance, otherwise this is null.
  */
 function getFront(client, typeName, form, target = null) {
   const type = types.getType(typeName);
   if (!type) {
     throw new Error(`No spec for front type '${typeName}'.`);
-  }
-  if (!type.frontClass) {
+  } else if (!type.frontClass) {
     lazyLoadFront(typeName);
   }
+
   // Use intermediate Class variable to please eslint requiring
   // a capital letter for all constructors.
   const Class = type.frontClass;
-  const instance = new Class(client);
-  // Set the targetFront for target-scoped fronts.
-  instance.targetFront = target;
+  const instance = new Class(client, target, target);
   const { formAttributeName } = instance;
   if (!formAttributeName) {
     throw new Error(`Can't find the form attribute name for ${typeName}`);
   }
   // Retrive the actor ID from root or target actor's form
   instance.actorID = form[formAttributeName];
   if (!instance.actorID) {
     throw new Error(
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -16,16 +16,17 @@
 #endif
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Components.h"
+#include "mozilla/DebugOnly.h"
 #include "mozilla/Encoding.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/Logging.h"
 #include "mozilla/MediaFeatureChange.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/PresShell.h"
@@ -8928,80 +8929,75 @@ nsresult nsDocShell::PerformRetargeting(
   }
 
   // Else we ran out of memory, or were a popup and got blocked,
   // or something.
 
   return rv;
 }
 
-nsresult nsDocShell::MaybeHandleSameDocumentNavigation(
-    nsDocShellLoadState* aLoadState, bool* aWasSameDocument) {
+bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+                                          SameDocumentNavigationState& aState) {
   MOZ_ASSERT(aLoadState);
-  MOZ_ASSERT(aWasSameDocument);
-  *aWasSameDocument = false;
   if (!(aLoadState->LoadType() == LOAD_NORMAL ||
         aLoadState->LoadType() == LOAD_STOP_CONTENT ||
         LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(),
                             LOAD_FLAGS_REPLACE_HISTORY) ||
         aLoadState->LoadType() == LOAD_HISTORY ||
         aLoadState->LoadType() == LOAD_LINK)) {
-    return NS_OK;
-  }
-  nsresult rv;
+    return false;
+  }
+
   nsCOMPtr<nsIURI> currentURI = mCurrentURI;
 
-  nsAutoCString curHash, newHash;
-  bool curURIHasRef = false, newURIHasRef = false;
-
-  nsresult rvURINew = aLoadState->URI()->GetRef(newHash);
+  nsresult rvURINew = aLoadState->URI()->GetRef(aState.mNewHash);
   if (NS_SUCCEEDED(rvURINew)) {
-    rvURINew = aLoadState->URI()->GetHasRef(&newURIHasRef);
-  }
-
-  bool sameExceptHashes = false;
+    rvURINew = aLoadState->URI()->GetHasRef(&aState.mNewURIHasRef);
+  }
+
   if (currentURI && NS_SUCCEEDED(rvURINew)) {
-    nsresult rvURIOld = currentURI->GetRef(curHash);
+    nsresult rvURIOld = currentURI->GetRef(aState.mCurrentHash);
     if (NS_SUCCEEDED(rvURIOld)) {
-      rvURIOld = currentURI->GetHasRef(&curURIHasRef);
+      rvURIOld = currentURI->GetHasRef(&aState.mCurrentURIHasRef);
     }
     if (NS_SUCCEEDED(rvURIOld)) {
       if (NS_FAILED(currentURI->EqualsExceptRef(aLoadState->URI(),
-                                                &sameExceptHashes))) {
-        sameExceptHashes = false;
-      }
-    }
-  }
-
-  if (!sameExceptHashes && sURIFixup && currentURI && NS_SUCCEEDED(rvURINew)) {
+                                                &aState.mSameExceptHashes))) {
+        aState.mSameExceptHashes = false;
+      }
+    }
+  }
+
+  if (!aState.mSameExceptHashes && sURIFixup && currentURI &&
+      NS_SUCCEEDED(rvURINew)) {
     // Maybe aLoadState->URI() came from the exposable form of currentURI?
     nsCOMPtr<nsIURI> currentExposableURI;
-    rv = sURIFixup->CreateExposableURI(currentURI,
-                                       getter_AddRefs(currentExposableURI));
-    NS_ENSURE_SUCCESS(rv, rv);
-    nsresult rvURIOld = currentExposableURI->GetRef(curHash);
+    DebugOnly<nsresult> rv = sURIFixup->CreateExposableURI(
+        currentURI, getter_AddRefs(currentExposableURI));
+    MOZ_ASSERT(NS_SUCCEEDED(rv), "CreateExposableURI should not fail, ever!");
+    nsresult rvURIOld = currentExposableURI->GetRef(aState.mCurrentHash);
     if (NS_SUCCEEDED(rvURIOld)) {
-      rvURIOld = currentExposableURI->GetHasRef(&curURIHasRef);
+      rvURIOld = currentExposableURI->GetHasRef(&aState.mCurrentURIHasRef);
     }
     if (NS_SUCCEEDED(rvURIOld)) {
-      if (NS_FAILED(currentExposableURI->EqualsExceptRef(aLoadState->URI(),
-                                                         &sameExceptHashes))) {
-        sameExceptHashes = false;
-      }
-    }
-  }
-
-  bool historyNavBetweenSameDoc = false;
+      if (NS_FAILED(currentExposableURI->EqualsExceptRef(
+              aLoadState->URI(), &aState.mSameExceptHashes))) {
+        aState.mSameExceptHashes = false;
+      }
+    }
+  }
+
   if (mOSHE && aLoadState->SHEntry()) {
     // We're doing a history load.
 
-    mOSHE->SharesDocumentWith(aLoadState->SHEntry(), &historyNavBetweenSameDoc);
+    mOSHE->SharesDocumentWith(aLoadState->SHEntry(),
+                              &aState.mHistoryNavBetweenSameDoc);
 
 #ifdef DEBUG
-    if (historyNavBetweenSameDoc) {
+    if (aState.mHistoryNavBetweenSameDoc) {
       nsCOMPtr<nsIInputStream> currentPostData = mOSHE->GetPostData();
       NS_ASSERTION(currentPostData == aLoadState->PostDataStream(),
                    "Different POST data for entries for the same page?");
     }
 #endif
   }
 
   // A same document navigation happens when we navigate between two SHEntries
@@ -9014,23 +9010,31 @@ nsresult nsDocShell::MaybeHandleSameDocu
   //  b) we're navigating to a new shentry whose URI differs from the
   //     current URI only in its hash, the new hash is non-empty, and
   //     we're not doing a POST.
   //
   // The restriction that the SHEntries in (a) must be different ensures
   // that history.go(0) and the like trigger full refreshes, rather than
   // same document navigations.
   bool doSameDocumentNavigation =
-      (historyNavBetweenSameDoc && mOSHE != aLoadState->SHEntry()) ||
+      (aState.mHistoryNavBetweenSameDoc && mOSHE != aLoadState->SHEntry()) ||
       (!aLoadState->SHEntry() && !aLoadState->PostDataStream() &&
-       sameExceptHashes && newURIHasRef);
-
-  if (!doSameDocumentNavigation) {
-    return NS_OK;
-  }
+       aState.mSameExceptHashes && aState.mNewURIHasRef);
+
+  return doSameDocumentNavigation;
+}
+
+nsresult nsDocShell::HandleSameDocumentNavigation(
+    nsDocShellLoadState* aLoadState, SameDocumentNavigationState& aState) {
+#ifdef DEBUG
+  SameDocumentNavigationState state;
+  MOZ_ASSERT(IsSameDocumentNavigation(aLoadState, state));
+#endif
+
+  nsCOMPtr<nsIURI> currentURI = mCurrentURI;
 
   // Save the position of the scrollers.
   nsPoint scrollPos = GetCurScrollPos();
 
   // Reset mLoadType to its original value once we exit this block, because this
   // same document navigation might have started after a normal, network load,
   // and we don't want to clobber its load type. See bug 737307.
   AutoRestore<uint32_t> loadTypeResetter(mLoadType);
@@ -9179,18 +9183,18 @@ nsresult nsDocShell::MaybeHandleSameDocu
   RefPtr<nsGlobalWindowInner> win =
       scriptGlobal ? scriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
 
   // ScrollToAnchor doesn't necessarily cause us to scroll the window;
   // the function decides whether a scroll is appropriate based on the
   // arguments it receives.  But even if we don't end up scrolling,
   // ScrollToAnchor performs other important tasks, such as informing
   // the presShell that we have a new hash.  See bug 680257.
-  rv = ScrollToAnchor(curURIHasRef, newURIHasRef, newHash,
-                      aLoadState->LoadType());
+  nsresult rv = ScrollToAnchor(aState.mCurrentURIHasRef, aState.mNewURIHasRef,
+                               aState.mNewHash, aLoadState->LoadType());
   NS_ENSURE_SUCCESS(rv, rv);
 
   /* restore previous position of scroller(s), if we're moving
    * back in history (bug 59774)
    */
   nscoord bx = 0;
   nscoord by = 0;
   bool needsScrollPosUpdate = false;
@@ -9204,35 +9208,35 @@ nsresult nsDocShell::MaybeHandleSameDocu
 
   // Dispatch the popstate and hashchange events, as appropriate.
   //
   // The event dispatch below can cause us to re-enter script and
   // destroy the docshell, nulling out mScriptGlobal. Hold a stack
   // reference to avoid null derefs. See bug 914521.
   if (win) {
     // Fire a hashchange event URIs differ, and only in their hashes.
-    bool doHashchange = sameExceptHashes && (curURIHasRef != newURIHasRef ||
-                                             !curHash.Equals(newHash));
-
-    if (historyNavBetweenSameDoc || doHashchange) {
+    bool doHashchange = aState.mSameExceptHashes &&
+                        (aState.mCurrentURIHasRef != aState.mNewURIHasRef ||
+                         !aState.mCurrentHash.Equals(aState.mNewHash));
+
+    if (aState.mHistoryNavBetweenSameDoc || doHashchange) {
       win->DispatchSyncPopState();
     }
 
     if (needsScrollPosUpdate && win->HasActiveDocument()) {
       SetCurScrollPosEx(bx, by);
     }
 
     if (doHashchange) {
       // Note that currentURI hasn't changed because it's on the
       // stack, so we can just use it directly as the old URI.
       win->DispatchAsyncHashchange(currentURI, aLoadState->URI());
     }
   }
 
-  *aWasSameDocument = true;
   return NS_OK;
 }
 
 nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
                                   nsIDocShell** aDocShell,
                                   nsIRequest** aRequest) {
   MOZ_ASSERT(aLoadState, "need a load state!");
   MOZ_ASSERT(aLoadState->TriggeringPrincipal(),
@@ -9270,24 +9274,32 @@ nsresult nsDocShell::InternalLoad(nsDocS
 
   // If we have a target to move to, do that now.
   if (!aLoadState->Target().IsEmpty()) {
     return PerformRetargeting(aLoadState, aDocShell, aRequest);
   }
 
   // If we don't have a target, we're loading into ourselves, and our load
   // delegate may want to intercept that load.
-  bool handled;
-  rv = MaybeHandleLoadDelegate(
-      aLoadState, nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, &handled);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  if (handled) {
-    return NS_OK;
+  SameDocumentNavigationState sameDocumentNavigationState;
+  bool sameDocument =
+      IsSameDocumentNavigation(aLoadState, sameDocumentNavigationState);
+  // LoadDelegate has already had chance to delegate loads which ended up to
+  // session history, so no need to re-delegate here, and we don't want fragment
+  // navigations to go through load delegate.
+  if (!sameDocument && !(aLoadState->LoadType() & LOAD_CMD_HISTORY)) {
+    bool handled;
+    rv = MaybeHandleLoadDelegate(
+        aLoadState, nsIBrowserDOMWindow::OPEN_CURRENTWINDOW, &handled);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    if (handled) {
+      return NS_OK;
+    }
   }
 
   // If a source docshell has been passed, check to see if we are sandboxed
   // from it as the result of an iframe or CSP sandbox.
   if (aLoadState->SourceDocShell() &&
       aLoadState->SourceDocShell()->IsSandboxedFrom(this)) {
     return NS_ERROR_DOM_INVALID_ACCESS_ERR;
   }
@@ -9386,20 +9398,19 @@ nsresult nsDocShell::InternalLoad(nsDocS
 
   mAllowKeywordFixup =
       aLoadState->HasLoadFlags(INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP);
   mURIResultedInDocument = false;  // reset the clock...
 
   // See if this is actually a load between two history entries for the same
   // document. If the process fails, or if we successfully navigate within the
   // same document, return.
-  bool wasSameDocument;
-  rv = MaybeHandleSameDocumentNavigation(aLoadState, &wasSameDocument);
-  if (NS_FAILED(rv) || wasSameDocument) {
-    return rv;
+  if (sameDocument) {
+    return HandleSameDocumentNavigation(aLoadState,
+                                        sameDocumentNavigationState);
   }
 
   // mContentViewer->PermitUnload can destroy |this| docShell, which
   // causes the next call of CanSavePresentation to crash.
   // Hold onto |this| until we return, to prevent a crash from happening.
   // (bug#331040)
   nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
 
@@ -9596,16 +9607,28 @@ nsresult nsDocShell::InternalLoad(nsDocS
       mHistoryID = aLoadState->SHEntry()->DocshellID();
     }
   }
 
   mSavingOldViewer = savePresentation;
 
   // If we have a saved content viewer in history, restore and show it now.
   if (aLoadState->SHEntry() && (mLoadType & LOAD_CMD_HISTORY)) {
+    // https://html.spec.whatwg.org/#history-traversal:
+    // To traverse the history
+    // "If entry has a different Document object than the current entry, then
+    // run the following substeps: Remove any tasks queued by the history
+    // traversal task source..."
+    // Same document object case was handled already above with
+    // HandleSameDocumentNavigation call.
+    RefPtr<ChildSHistory> shistory = GetRootSessionHistory();
+    if (shistory) {
+      shistory->RemovePendingHistoryNavigations();
+    }
+
     // It's possible that the previous viewer of mContentViewer is the
     // viewer that will end up in aLoadState->SHEntry() when it gets closed.  If
     // that's the case, we need to go ahead and force it into its shentry so we
     // can restore it.
     if (mContentViewer) {
       nsCOMPtr<nsIContentViewer> prevViewer =
           mContentViewer->GetPreviousViewer();
       if (prevViewer) {
@@ -11256,16 +11279,24 @@ nsresult nsDocShell::UpdateURLAndHistory
   NS_ENSURE_TRUE(mOSHE || aReplace, NS_ERROR_FAILURE);
   nsCOMPtr<nsISHEntry> oldOSHE = mOSHE;
 
   mLoadType = LOAD_PUSHSTATE;
 
   nsCOMPtr<nsISHEntry> newSHEntry;
   if (!aReplace) {
     // Step 2.
+
+    // Step 2.2, "Remove any tasks queued by the history traversal task
+    // source..."
+    RefPtr<ChildSHistory> shistory = GetRootSessionHistory();
+    if (shistory) {
+      shistory->RemovePendingHistoryNavigations();
+    }
+
     // Save the current scroll position (bug 590573).  Step 2.3.
     nsPoint scrollPos = GetCurScrollPos();
     mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y);
 
     bool scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
     nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
 
     // Since we're not changing which page we have loaded, pass
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -1029,22 +1029,35 @@ class nsDocShell final : public nsDocLoa
 
   // In cases where we have a LoadURIDelegate (loading external links via
   // GeckoView), a load may need to be handled through the delegate. aWindowType
   // is either nsIBrowserDOMWindow::OPEN_CURRENTWINDOW or
   // nsIBrowserDOMWindow::OPEN_NEWWINDOW.
   nsresult MaybeHandleLoadDelegate(nsDocShellLoadState* aLoadState,
                                    uint32_t aWindowType, bool* aDidHandleLoad);
 
-  // Check to see if we're loading a prior history entry in the same document.
-  // If so, handle the scrolling or other action required instead of continuing
-  // with new document navigation.
+  struct SameDocumentNavigationState {
+    nsAutoCString mCurrentHash;
+    nsAutoCString mNewHash;
+    bool mCurrentURIHasRef = false;
+    bool mNewURIHasRef = false;
+    bool mSameExceptHashes = false;
+    bool mHistoryNavBetweenSameDoc = false;
+  };
+
+  // Check to see if we're loading a prior history entry or doing a fragment
+  // navigation in the same document.
+  bool IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+                                SameDocumentNavigationState& aState);
+
+  // ... If so, handle the scrolling or other action required instead of
+  // continuing with new document navigation.
   MOZ_CAN_RUN_SCRIPT
-  nsresult MaybeHandleSameDocumentNavigation(nsDocShellLoadState* aLoadState,
-                                             bool* aWasSameDocument);
+  nsresult HandleSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+                                        SameDocumentNavigationState& aState);
 
  private:  // data members
   static nsIURIFixup* sURIFixup;
 
 #ifdef DEBUG
   // We're counting the number of |nsDocShells| to help find leaks
   static unsigned long gNumberOfDocShells;
 #endif /* DEBUG */
--- a/docshell/shistory/ChildSHistory.cpp
+++ b/docshell/shistory/ChildSHistory.cpp
@@ -48,16 +48,31 @@ void ChildSHistory::Go(int32_t aOffset, 
   index += aOffset;
   if (!index.isValid()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
   aRv = mHistory->GotoIndex(index.value());
 }
 
+void ChildSHistory::AsyncGo(int32_t aOffset) {
+  if (!CanGo(aOffset)) {
+    return;
+  }
+
+  RefPtr<PendingAsyncHistoryNavigation> asyncNav =
+      new PendingAsyncHistoryNavigation(this, aOffset);
+  mPendingNavigations.insertBack(asyncNav);
+  NS_DispatchToCurrentThread(asyncNav.forget());
+}
+
+void ChildSHistory::RemovePendingHistoryNavigations() {
+  mPendingNavigations.clear();
+}
+
 void ChildSHistory::EvictLocalContentViewers() {
   mHistory->EvictAllContentViewers();
 }
 
 nsISHistory* ChildSHistory::LegacySHistory() { return mHistory; }
 
 ParentSHistory* ChildSHistory::GetParentIfSameProcess() {
   if (XRE_IsContentProcess()) {
--- a/docshell/shistory/ChildSHistory.h
+++ b/docshell/shistory/ChildSHistory.h
@@ -18,16 +18,18 @@
  */
 
 #ifndef mozilla_dom_ChildSHistory_h
 #define mozilla_dom_ChildSHistory_h
 
 #include "nsCOMPtr.h"
 #include "mozilla/ErrorResult.h"
 #include "nsWrapperCache.h"
+#include "nsThreadUtils.h"
+#include "mozilla/LinkedList.h"
 
 class nsSHistory;
 class nsDocShell;
 class nsISHistory;
 class nsIWebNavigation;
 class nsIGlobalObject;
 
 namespace mozilla {
@@ -57,29 +59,55 @@ class ChildSHistory : public nsISupports
 
   /**
    * The CanGo and Go methods are called with an offset from the current index.
    * Positive numbers go forward in history, while negative numbers go
    * backwards.
    */
   bool CanGo(int32_t aOffset);
   void Go(int32_t aOffset, ErrorResult& aRv);
+  void AsyncGo(int32_t aOffset);
+
+  void RemovePendingHistoryNavigations();
 
   /**
    * Evicts all content viewers within the current process.
    */
   void EvictLocalContentViewers();
 
   nsISHistory* LegacySHistory();
 
   ParentSHistory* GetParentIfSameProcess();
 
  private:
   virtual ~ChildSHistory();
 
+  class PendingAsyncHistoryNavigation
+      : public Runnable,
+        public mozilla::LinkedListElement<PendingAsyncHistoryNavigation> {
+   public:
+    PendingAsyncHistoryNavigation(ChildSHistory* aHistory, int32_t aOffset)
+        : Runnable("PendingAsyncHistoryNavigation"),
+          mHistory(aHistory),
+          mOffset(aOffset) {}
+
+    NS_IMETHOD Run() override {
+      if (isInList()) {
+        remove();
+        mHistory->Go(mOffset, IgnoreErrors());
+      }
+      return NS_OK;
+    }
+
+   private:
+    RefPtr<ChildSHistory> mHistory;
+    int32_t mOffset;
+  };
+
   RefPtr<nsDocShell> mDocShell;
   RefPtr<nsSHistory> mHistory;
+  mozilla::LinkedList<PendingAsyncHistoryNavigation> mPendingNavigations;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif /* mozilla_dom_ChildSHistory_h */
--- a/docshell/test/mochitest/historyframes.html
+++ b/docshell/test/mochitest/historyframes.html
@@ -62,16 +62,21 @@ function run_test() {
 
   test_basic_inner_navigation();
 }
 
 function end_test() {
   testWin.done();
 }
 
+var gTestContinuation = null;
+function continueAsync() {
+  setTimeout(function() { gTestContinuation.next(); })
+}
+
 function test_basic_inner_navigation() {
   // Navigate the inner frame a few times
   loadContent(URL1, function() {
     is(getURL(), URL1, "URL should be correct");
     is(getContent(), "Test1", "Page should be correct");
 
     loadContent(URL2, function() {
       is(getURL(), URL2, "URL should be correct");
@@ -81,50 +86,59 @@ function test_basic_inner_navigation() {
       waitForLoad(function() {
         is(getURL(), URL1, "URL should be correct");
         is(getContent(), "Test1", "Page should be correct");
 
         waitForLoad(function() {
           is(getURL(), URL2, "URL should be correct");
           is(getContent(), "Test2", "Page should be correct");
 
-          test_state_navigation();
+          gTestContinuation = test_state_navigation();
+          gTestContinuation.next();
         });
         window.history.forward();
       });
       window.history.back();
     });
   });
 }
 
-function test_state_navigation() {
+function* test_state_navigation() {
   window.location.hash = "STATE1";
 
   is(getURL(), URL2, "URL should be correct");
   is(getContent(), "Test2", "Page should be correct");
 
   window.location.hash = "STATE2";
 
   is(getURL(), URL2, "URL should be correct");
   is(getContent(), "Test2", "Page should be correct");
 
   window.history.back();
+  continueAsync();
+  yield;
 
   is(gState(), "STATE1", "State should be correct after going back");
   is(getURL(), URL2, "URL should be correct");
   is(getContent(), "Test2", "Page should be correct");
 
   window.history.forward();
+  continueAsync();
+  yield;
 
   is(gState(), "STATE2", "State should be correct after going forward");
   is(getURL(), URL2, "URL should be correct");
   is(getContent(), "Test2", "Page should be correct");
 
   window.history.back();
+  continueAsync();
+  yield;
   window.history.back();
+  continueAsync();
+  yield;
 
   is(gState(), "START", "State should be correct");
   is(getURL(), URL2, "URL should be correct");
   is(getContent(), "Test2", "Page should be correct");
 
   waitForLoad(function() {
     is(getURL(), URL1, "URL should be correct");
     is(getContent(), "Test1", "Page should be correct");
@@ -138,14 +152,16 @@ function test_state_navigation() {
     });
 
     window.history.back();
 
     is(gState(), "START", "State should be correct after going back twice");
   });
 
   window.history.back();
+  continueAsync();
+  yield;
   is(gState(), "START", "State should be correct");
 }
 </script>
 </pre>
 </body>
 </html>
--- a/docshell/test/mochitest/test_bug590573.html
+++ b/docshell/test/mochitest/test_bug590573.html
@@ -119,35 +119,47 @@ function pageLoad() {
     gTestContinuation = testBody();
   }
   var ret = gTestContinuation.next();
   if (ret.done) {
     SimpleTest.finish();
   }
 }
 
+function continueAsync() {
+  setTimeout(function() { gTestContinuation.next(); })
+}
+
 function* testBody() {
   is(popup.scrollY, 0, "test 1");
   popup.scroll(0, 100);
 
   popup.history.pushState("", "", "?pushed");
   is(Math.round(popup.scrollY), 100, "test 2");
   popup.scroll(0, 200); // set state-2's position to 200
 
   popup.history.back();
+  continueAsync();
+  yield;
   is(Math.round(popup.scrollY), 100, "test 3");
   popup.scroll(0, 150); // set original page's position to 150
 
   popup.history.forward();
+  continueAsync();
+  yield;
   is(Math.round(popup.scrollY), 200, "test 4");
 
   popup.history.back();
+  continueAsync();
+  yield;
   is(Math.round(popup.scrollY), 150, "test 5");
 
   popup.history.forward();
+  continueAsync();
+  yield;
   is(Math.round(popup.scrollY), 200, "test 6");
 
   // At this point, the history looks like:
   //   PATH                         POSITION
   //   file_bug590573_1.html        150       <-- oldest
   //   file_bug590573_1.html?pushed 200       <-- newest, current
 
   // Now test that the scroll position is persisted when we have real
@@ -182,18 +194,22 @@ function* testBody() {
   yield;
 
   is(popup.location.search, "?pushed");
   ok(popup.document.getElementById("div1"), "page should have div1.");
 
   is(Math.round(popup.scrollY), 200, "test 8");
 
   popup.history.back();
+  continueAsync();
+  yield;
   is(Math.round(popup.scrollY), 150, "test 9");
   popup.history.forward();
+  continueAsync();
+  yield;
 
   is(Math.round(popup.scrollY), 200, "test 10");
 
   // Spin one last time...
   setTimeout(pageLoad, 0);
   yield;
 
   page2PageShowCallbackEnabled = true;
--- a/docshell/test/mochitest/test_bug680257.html
+++ b/docshell/test/mochitest/test_bug680257.html
@@ -12,33 +12,48 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=680257">Mozilla Bug 680257</a>
 
 <script type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 var popup = window.open("file_bug680257.html");
 
+var gTestContinuation = null;
+function continueAsync() {
+  setTimeout(function() { gTestContinuation.next(); })
+}
+
 // The popup will call into popupLoaded() once it loads.
 function popupLoaded() {
   // runTests() needs to be called from outside popupLoaded's onload handler.
   // Otherwise, the navigations we do in runTests won't create new SHEntries.
-  SimpleTest.executeSoon(runTests);
+  SimpleTest.executeSoon(function() {
+    if (!gTestContinuation) {
+      gTestContinuation = runTests();
+    }
+    gTestContinuation.next();
+  });
 }
 
-function runTests() {
+function* runTests() {
   checkPopupLinkStyle(false, "Initial");
 
   popup.location.hash = "a";
   checkPopupLinkStyle(true, "After setting hash");
 
   popup.history.back();
+  continueAsync();
+  yield;
+
   checkPopupLinkStyle(false, "After going back");
 
   popup.history.forward();
+  continueAsync();
+  yield;
   checkPopupLinkStyle(true, "After going forward");
 
   popup.close();
   SimpleTest.finish();
 }
 
 function checkPopupLinkStyle(isTarget, desc) {
   var link = popup.document.getElementById("a");
--- a/docshell/test/navigation/file_bug1300461.html
+++ b/docshell/test/navigation/file_bug1300461.html
@@ -35,17 +35,16 @@
           opener.ok(!webNav.canGoForward, "check canGoForward");
           setTimeout(() => window.location = "file_bug1300461_back.html", 0);
         },
         function() {
           opener.is(shistory.count, 2, "check history length");
           opener.is(shistory.index, 0, "check history index");
           opener.ok(webNav.canGoForward, "check canGoForward");
           window.history.forward();
-          opener.is(shistory.legacySHistory.requestedIndex, 1, "check requestedIndex");
         },
         function() {
           opener.is(shistory.count, 2, "check history length");
           opener.is(shistory.index, 0, "check history index");
           opener.ok(webNav.canGoForward, "check canGoForward");
           opener.info("file_bug1300461.html tests finished");
           opener.nextTest();
           window.close();
--- a/docshell/test/navigation/file_scrollRestoration.html
+++ b/docshell/test/navigation/file_scrollRestoration.html
@@ -65,49 +65,65 @@
             opener.is(window.scrollY, 0, "Shouldn't have kept the old scroll position.");
             opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration mode as before fragment navigation.");
             history.scrollRestoration = "auto";
             document.getElementById("bottom").scrollIntoView();
             history.pushState({ state: "state1" }, "state1");
             history.pushState({ state: "state2" }, "state2");
             window.scrollTo(0, 0);
             history.back();
+            setTimeout(test);
+            break;
+          }
+          case 7: {
             opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled back to the state1's position");
             opener.is(history.state.state, "state1", "Unexpected state.");
 
             history.scrollRestoration = "manual";
             document.getElementById("bottom").scrollIntoView();
             history.pushState({ state: "state3" }, "state3");
             history.pushState({ state: "state4" }, "state4");
             window.scrollTo(0, 0);
             history.back();
+            setTimeout(test);
+            break;
+          }
+          case 8: {
             opener.is(Math.round(window.scrollY), 0, "Shouldn't have scrolled back to the state3's position");
             opener.is(history.state.state, "state3", "Unexpected state.");
 
             history.pushState({ state: "state5" }, "state5");
             history.scrollRestoration = "auto";
             document.getElementById("bottom").scrollIntoView();
             opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled to 'bottom'.");
             history.back();
+            setTimeout(test);
+            break;
+          }
+          case 9: {
             window.scrollTo(0, 0);
             history.forward();
+            setTimeout(test);
+            break;
+          }
+          case 10: {
             opener.isnot(Math.round(window.scrollY), 0, "Should have scrolled back to the state5's position");
 
             var ifr = document.createElement("iframe");
             ifr.src = "data:text/html,";
             document.body.appendChild(ifr);
             ifr.onload = test;
             break;
           }
-          case 7: {
+          case 11: {
             oldHistoryObject = SpecialPowers.wrap(event.target).contentWindow.history;
             event.target.src = "about:blank";
             break;
           }
-          case 8: {
+          case 12: {
             try {
               oldHistoryObject.scrollRestoration;
               opener.ok(false, "Should have thrown an exception.");
             } catch (ex) {
               opener.isnot(ex, null, "Did get an exception");
             }
             try {
               oldHistoryObject.scrollRestoration = "auto";
--- a/dom/base/nsHistory.cpp
+++ b/dom/base/nsHistory.cpp
@@ -13,18 +13,18 @@
 #include "mozilla/dom/DocumentInlines.h"
 #include "nsIDocShell.h"
 #include "nsIWebNavigation.h"
 #include "nsIURI.h"
 #include "nsReadableUtils.h"
 #include "nsContentUtils.h"
 #include "nsISHistory.h"
 #include "mozilla/dom/Location.h"
-#include "mozilla/Preferences.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/StaticPrefs_dom.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 //
 //  History class implementation
 //
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsHistory)
@@ -154,17 +154,21 @@ void nsHistory::Go(int32_t aDelta, Error
   RefPtr<ChildSHistory> session_history = GetSessionHistory();
   if (!session_history) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   // Ignore the return value from Go(), since returning errors from Go() can
   // lead to exceptions and a possible leak of history length
-  session_history->Go(aDelta, IgnoreErrors());
+  if (StaticPrefs::dom_window_history_async()) {
+    session_history->AsyncGo(aDelta);
+  } else {
+    session_history->Go(aDelta, IgnoreErrors());
+  }
 }
 
 void nsHistory::Back(ErrorResult& aRv) {
   nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
   if (!win || !win->HasActiveDocument()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
 
     return;
@@ -172,17 +176,21 @@ void nsHistory::Back(ErrorResult& aRv) {
 
   RefPtr<ChildSHistory> sHistory = GetSessionHistory();
   if (!sHistory) {
     aRv.Throw(NS_ERROR_FAILURE);
 
     return;
   }
 
-  sHistory->Go(-1, IgnoreErrors());
+  if (StaticPrefs::dom_window_history_async()) {
+    sHistory->AsyncGo(-1);
+  } else {
+    sHistory->Go(-1, IgnoreErrors());
+  }
 }
 
 void nsHistory::Forward(ErrorResult& aRv) {
   nsCOMPtr<nsPIDOMWindowInner> win(do_QueryReferent(mInnerWindow));
   if (!win || !win->HasActiveDocument()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
 
     return;
@@ -190,17 +198,21 @@ void nsHistory::Forward(ErrorResult& aRv
 
   RefPtr<ChildSHistory> sHistory = GetSessionHistory();
   if (!sHistory) {
     aRv.Throw(NS_ERROR_FAILURE);
 
     return;
   }
 
-  sHistory->Go(1, IgnoreErrors());
+  if (StaticPrefs::dom_window_history_async()) {
+    sHistory->AsyncGo(1);
+  } else {
+    sHistory->Go(1, IgnoreErrors());
+  }
 }
 
 void nsHistory::PushState(JSContext* aCx, JS::Handle<JS::Value> aData,
                           const nsAString& aTitle, const nsAString& aUrl,
                           ErrorResult& aRv) {
   PushOrReplaceState(aCx, aData, aTitle, aUrl, aRv, false);
 }
 
--- a/dom/tests/mochitest/general/historyframes.html
+++ b/dom/tests/mochitest/general/historyframes.html
@@ -78,16 +78,21 @@ function continue_test() {
     gTestContinuation = test_body();
   }
   var ret = gTestContinuation.next();
   if (ret.done) {
     testWin.done();
   }
 }
 
+var gTestContinuation = null;
+function continueAsync() {
+  setTimeout(function() { gTestContinuation.next(); })
+}
+
 function* test_basic_inner_navigation() {
   // Navigate the inner frame a few times
   yield loadContent(URL1);
   is(getURL(), URL1, "URL should be correct");
   is(getContent(), "Test1", "Page should be correct");
 
   yield loadContent(URL2);
 
@@ -113,42 +118,54 @@ function* test_state_navigation() {
   is(getContent(), "Test2", "Page should be correct");
 
   window.history.pushState("STATE2", window.location);
 
   is(getURL(), URL2, "URL should be correct");
   is(getContent(), "Test2", "Page should be correct");
 
   window.history.back();
+  continueAsync();
+  yield;
 
   is(gState, "STATE1", "State should be correct");
   is(getURL(), URL2, "URL should be correct");
   is(getContent(), "Test2", "Page should be correct");
 
   window.history.forward();
+  continueAsync();
+  yield;
 
   is(gState, "STATE2", "State should be correct");
   is(getURL(), URL2, "URL should be correct");
   is(getContent(), "Test2", "Page should be correct");
 
   window.history.back();
+  continueAsync();
+  yield;
   window.history.back();
+  continueAsync();
+  yield;
 
   is(gState, "START", "State should be correct");
   is(getURL(), URL2, "URL should be correct");
   is(getContent(), "Test2", "Page should be correct");
 
   window.history.back();
+  continueAsync();
+  yield;
   is(gState, "START", "State should be correct");
   yield waitForLoad();
 
   is(getURL(), URL1, "URL should be correct");
   is(getContent(), "Test1", "Page should be correct");
 
   window.history.back();
+  continueAsync();
+  yield;
   is(gState, "START", "State should be correct after going back twice");
   yield waitForLoad();
 
   is(gState, "START", "State should be correct");
   is(getURL(), START, "URL should be correct");
   is(getContent(), "Start", "Page should be correct");
 }
 </script>
--- a/dom/xslt/xslt/txEXSLTFunctions.cpp
+++ b/dom/xslt/xslt/txEXSLTFunctions.cpp
@@ -153,17 +153,17 @@ enum class txEXSLTType {
   LOWEST,
 
   // http://exslt.org/regular-expressions
   MATCH,
   REPLACE,
   TEST,
 
   // http://exslt.org/sets
-  DIFFERENCE,
+  DIFFERENCE_,  // not DIFFERENCE to avoid a conflict with a winuser.h macro
   DISTINCT,
   HAS_SAME_NODE,
   INTERSECTION,
   LEADING,
   TRAILING,
 
   // http://exslt.org/strings
   CONCAT,
@@ -279,17 +279,17 @@ nsresult txEXSLTFunctionCall::evaluate(t
 
       AppendASCIItoUTF16(MakeStringSpan(sTypes[exprResult->getResultType()]),
                          strRes->mValue);
 
       NS_ADDREF(*aResult = strRes);
 
       return NS_OK;
     }
-    case txEXSLTType::DIFFERENCE:
+    case txEXSLTType::DIFFERENCE_:
     case txEXSLTType::INTERSECTION: {
       RefPtr<txNodeSet> nodes1;
       rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes1));
       NS_ENSURE_SUCCESS(rv, rv);
 
       RefPtr<txNodeSet> nodes2;
       rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodes2));
       NS_ENSURE_SUCCESS(rv, rv);
@@ -788,17 +788,17 @@ extern bool TX_InitEXSLTFunction() {
 
   EXSLT_FUNCS("http://exslt.org/regular-expressions", txEXSLTRegExFunctionCall,
               (MATCH, 2, 3, Expr::NODESET_RESULT, nsGkAtoms::match),
               (REPLACE, 4, 4, Expr::STRING_RESULT, nsGkAtoms::replace),
               (TEST, 2, 3, Expr::BOOLEAN_RESULT, nsGkAtoms::test))
 
   EXSLT_FUNCS(
       "http://exslt.org/sets", txEXSLTFunctionCall,
-      (DIFFERENCE, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::difference),
+      (DIFFERENCE_, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::difference),
       (DISTINCT, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::distinct),
       (HAS_SAME_NODE, 2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::hasSameNode),
       (INTERSECTION, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::intersection),
       (LEADING, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::leading),
       (TRAILING, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::trailing))
 
   EXSLT_FUNCS("http://exslt.org/strings", txEXSLTFunctionCall,
               (CONCAT, 1, 1, Expr::STRING_RESULT, nsGkAtoms::concat),
--- a/gfx/gl/GLConsts.py
+++ b/gfx/gl/GLConsts.py
@@ -17,17 +17,17 @@ Step 2:
 Step 3:
   Do not add the downloaded XML in the patch
 
 Step 4:
   Enjoy =)
 '''
 
 # includes
-from typing import List # mypy!
+from typing import List  # mypy!
 
 import pathlib
 import sys
 import xml.etree.ElementTree
 
 # -
 
 (_, XML_DIR_STR) = sys.argv
@@ -57,38 +57,37 @@ HEADER = b'''
 FOOTER = b'''
 #endif // GLCONSTS_H_
 
 // clang-format on
 '''[1:]
 
 # -
 
+
 def format_lib_constant(lib, name, value):
     # lib would be 'GL', 'EGL', 'GLX' or 'WGL'
     # name is the name of the const (example: MAX_TEXTURE_SIZE)
     # value is the value of the const (example: 0xABCD)
 
     define = '#define LOCAL_' + lib + '_' + name
     whitespace = 60 - len(define)
     if whitespace < 0:
         whitespace = whitespace % 8
 
     return define + ' ' * whitespace + ' ' + value
 
-# -
 
 class GLConst:
     def __init__(self, lib, name, value, type):
         self.lib = lib
         self.name = name
         self.value = value
         self.type = type
 
-# -
 
 class GLDatabase:
     LIBS = ['GL', 'EGL', 'GLX', 'WGL']
 
     def __init__(self):
         self.consts = {}
         self.libs = set(GLDatabase.LIBS)
         self.vendors = set(['EXT', 'ATI'])
@@ -128,27 +127,28 @@ class GLDatabase:
                 type = enum.get('type')
 
                 if not type:
                     # if no type specified, we get the namespace's default type
                     type = namespaceType
 
                 self.consts[lib + '_' + name] = GLConst(lib, name, value, type)
 
+
 # -
 
 db = GLDatabase()
 db.load_xml(XML_DIR / 'gl.xml')
 db.load_xml(XML_DIR / 'glx.xml')
 db.load_xml(XML_DIR / 'wgl.xml')
 db.load_xml(XML_DIR / 'egl.xml')
 
 # -
 
-lines: List[str] = []
+lines: List[str] = []  # noqa: E999 (bug 1573737)
 
 keys = sorted(db.consts.keys())
 
 for lib in db.LIBS:
     lines.append('// ' + lib)
 
     for k in keys:
         const = db.consts[k]
@@ -164,9 +164,9 @@ for lib in db.LIBS:
 # -
 
 b_lines: List[bytes] = [HEADER] + [x.encode() for x in lines] + [FOOTER]
 b_data: bytes = b'\n'.join(b_lines)
 
 dest = pathlib.Path('GLConsts.h')
 dest.write_bytes(b_data)
 
-print(f'Wrote {len(b_data)} bytes.') # Some indication that we're successful.
+print(f'Wrote {len(b_data)} bytes.')  # Some indication that we're successful.
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -859,25 +859,20 @@ class NavigationDelegateTest : BaseSessi
                 assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
             }
         })
 
         sessionRule.session.goBack()
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
-            @AssertCalled(count = 1, order = [1])
+            @AssertCalled(count = 0, order = [1])
             override fun onLoadRequest(session: GeckoSession,
                                        request: LoadRequest):
                                        GeckoResult<AllowOrDeny>? {
-                assertThat("URI should match", request.uri, endsWith(HELLO_HTML_PATH))
-                assertThat("Trigger URL should be null", request.triggerUri,
-                           nullValue())
-                assertThat("Target should match", request.target,
-                           equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
                 return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onLocationChange(session: GeckoSession, url: String?) {
                 assertThat("URL should match", url, endsWith(HELLO_HTML_PATH))
             }
 
@@ -896,25 +891,20 @@ class NavigationDelegateTest : BaseSessi
                 return null
             }
         })
 
         sessionRule.session.goForward()
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
-            @AssertCalled(count = 1, order = [1])
+            @AssertCalled(count = 0, order = [1])
             override fun onLoadRequest(session: GeckoSession,
                                        request: LoadRequest):
                                        GeckoResult<AllowOrDeny>? {
-                assertThat("URI should match", request.uri, endsWith(HELLO2_HTML_PATH))
-                assertThat("Trigger URL should be null", request.triggerUri,
-                           nullValue())
-                assertThat("Target should match", request.target,
-                           equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
                 return null
             }
 
             @AssertCalled(count = 1, order = [2])
             override fun onLocationChange(session: GeckoSession, url: String?) {
                 assertThat("URL should match", url, endsWith(HELLO2_HTML_PATH))
             }
 
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -2375,16 +2375,21 @@
   mirror: always
 
 # Is support for Window.event enabled?
 - name: dom.window.event.enabled
   type: bool
   value: true
   mirror: always
 
+- name: dom.window.history.async
+  type: bool
+  value: true
+  mirror: always
+
 # Enable the "noreferrer" feature argument for window.open()
 - name: dom.window.open.noreferrer.enabled
   type: bool
   value: true
   mirror: always
 
 - name: dom.worker.canceling.timeoutMilliseconds
   type: RelaxedAtomicUint32
--- a/mozglue/misc/StackWalk.cpp
+++ b/mozglue/misc/StackWalk.cpp
@@ -110,24 +110,26 @@ CRITICAL_SECTION gDbgHelpCS;
 // continue unwinding that thread even if other threads request suppressions
 // in the meantime, because we can't deadlock with those other threads.
 //
 // XXX: This global variable is a larger-than-necessary hammer. A more scoped
 // solution would be to maintain a counter per thread, but then it would be
 // more difficult for WalkStackMain64 to read the suspended thread's counter.
 static Atomic<size_t> sStackWalkSuppressions;
 
+void SuppressStackWalking() { ++sStackWalkSuppressions; }
+
+void DesuppressStackWalking() { --sStackWalkSuppressions; }
+
 MFBT_API
-AutoSuppressStackWalking::AutoSuppressStackWalking() {
-  ++sStackWalkSuppressions;
-}
+AutoSuppressStackWalking::AutoSuppressStackWalking() { SuppressStackWalking(); }
 
 MFBT_API
 AutoSuppressStackWalking::~AutoSuppressStackWalking() {
-  --sStackWalkSuppressions;
+  DesuppressStackWalking();
 }
 
 static uint8_t* sJitCodeRegionStart;
 static size_t sJitCodeRegionSize;
 uint8_t* sMsMpegJitCodeRegionStart;
 size_t sMsMpegJitCodeRegionSize;
 
 MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t aSize) {
--- a/mozglue/misc/StackWalk_windows.h
+++ b/mozglue/misc/StackWalk_windows.h
@@ -16,14 +16,19 @@
  *
  * See comment in StackWalk.cpp
  */
 struct MOZ_RAII AutoSuppressStackWalking {
   MFBT_API AutoSuppressStackWalking();
   MFBT_API ~AutoSuppressStackWalking();
 };
 
+#if defined(IMPL_MFBT)
+void SuppressStackWalking();
+void DesuppressStackWalking();
+#endif  // defined(IMPL_MFBT)
+
 MFBT_API void RegisterJitCodeRegion(uint8_t* aStart, size_t size);
 
 MFBT_API void UnregisterJitCodeRegion(uint8_t* aStart, size_t size);
 #endif  // _M_AMD64
 
 #endif  // mozilla_StackWalk_windows_h
--- a/old-configure.in
+++ b/old-configure.in
@@ -795,17 +795,17 @@ case "$target" in
             # we use it to avoid warnings about things that are unused
             # in some compilation units, but used in many others.  This
             # warning insists on complaining about the latter case, which
             # is annoying, and rather noisy.
             CXXFLAGS="$CXXFLAGS -Wno-used-but-marked-unused"
         fi
         # Silence VS2017 15.5+ TR1 deprecation warnings hit by older gtest versions
         CXXFLAGS="$CXXFLAGS -D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING"
-        LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib secur32.lib"
+        LIBS="$LIBS user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib secur32.lib"
         MOZ_DEBUG_LDFLAGS='-DEBUG'
         WARNINGS_AS_ERRORS='-WX'
         # Use a higher optimization level for clang-cl, so we can come closer
         # to MSVC's performance numbers (see bug 1443590).
         if test -n "$CLANG_CL"; then
             MOZ_OPTIMIZE_FLAGS='-O2'
         else
             MOZ_OPTIMIZE_FLAGS='-O1 -Oi'
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[scroll-restoration-fragment-scrolling-cross-origin.html]
-  expected:
-    if (os == "android") and e10s: ERROR
-  [Manual scroll restoration should take precedent over scrolling to fragment in cross origin navigation]
-    expected:
-      if (os == "android") and e10s: TIMEOUT
-      FAIL
-
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/001.html.ini
+++ b/testing/web-platform/meta/html/browsers/history/the-history-interface/001.html.ini
@@ -1,16 +1,13 @@
 [001.html]
   [pushState must not be allowed to create cross-origin URLs (about:blank)]
     expected: FAIL
 
   [pushState must not be allowed to create cross-origin URLs (data:URI)]
     expected: FAIL
 
-  [pushState must remove any tasks queued by the history traversal task source]
-    expected: FAIL
-
   [history.state should be a separate clone of the object, not a reference to the object passed to the event handler]
     expected: FAIL
 
   [pushState must be able to use an error object as data]
     expected: FAIL
 
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/002.html.ini
+++ b/testing/web-platform/meta/html/browsers/history/the-history-interface/002.html.ini
@@ -1,19 +1,13 @@
 [002.html]
   [replaceState must not be allowed to create cross-origin URLs (about:blank)]
     expected: FAIL
 
   [replaceState must not be allowed to create cross-origin URLs (data:URI)]
     expected: FAIL
 
-  [replaceState must not remove any tasks queued by the history traversal task source]
-    expected: FAIL
-
-  [.go must queue a task with the history traversal task source (run asynchronously)]
-    expected: FAIL
-
   [history.state should be a separate clone of the object, not a reference to the object passed to the event handler]
     expected: FAIL
 
   [replaceState must be able to use an error object as data]
     expected: FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/004.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[004.html]
-  [.go commands should be queued until the thread has ended]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_004.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[combination_history_004.html]
-  expected: TIMEOUT
-  [After calling of back method, check length]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_005.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[combination_history_005.html]
-  expected: TIMEOUT
-  [After calling of forward method, check length]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_006.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[combination_history_006.html]
-  expected: TIMEOUT
-  [After calling of go method, check length]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/combination_history_007.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[combination_history_007.html]
-  expected: TIMEOUT
-  [After calling of back and pushState method, check length]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/history_back.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[history_back.html]
-  expected: TIMEOUT
-  [history back]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/history_forward.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[history_forward.html]
-  expected: TIMEOUT
-  [history forward]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/history_go_minus.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[history_go_minus.html]
-  expected: TIMEOUT
-  [history go minus]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/history_go_plus.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[history_go_plus.html]
-  expected: TIMEOUT
-  [history go plus]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_1.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[traverse_the_history_1.html]
-  [Multiple history traversals from the same task]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_2.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[traverse_the_history_2.html]
-  [Multiple history traversals, last would be aborted]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_3.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[traverse_the_history_3.html]
-  [Multiple history traversals, last would be aborted]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_4.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[traverse_the_history_4.html]
-  [Multiple history traversals, last would be aborted]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/html/browsers/history/the-history-interface/traverse_the_history_5.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[traverse_the_history_5.html]
-  [Multiple history traversals, last would be aborted]
-    expected: FAIL
-
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/resource-timing/nested-context-navigations-iframe.html.ini
@@ -0,0 +1,5 @@
+[nested-context-navigations-iframe.html]
+  [Test that iframe navigations are not observable by the parent, even after history navigations by the parent]
+    expected: FAIL
+  [Test that crossorigin iframe navigations are not observable by the parent, even after history navigations by the parent]
+    expected: FAIL
--- a/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html
+++ b/testing/web-platform/tests/html/browsers/browsing-the-web/history-traversal/persisted-user-state-restoration/scroll-restoration-fragment-scrolling-cross-origin.html
@@ -6,62 +6,68 @@
 <script src="/common/get-host-info.sub.js"></script>
 <style>
   iframe {
     height: 300px;
     width: 300px;
   }
 </style>
 <div id="log"></div>
-<iframe></iframe>
 <script>
   'use strict';
 
-  // The test does the following navigation steps for iframe
-  // 1. load page-with-fragment.html#fragment
-  // 2. load blank1
-  // 3. go back to page-with-fragment.html
-  async_test(function(t) {
-    var iframe = document.querySelector('iframe');
-    var hostInfo = get_host_info();
-    var basePath = location.pathname.substring(0, location.pathname.lastIndexOf('/'));
-    var localURL = hostInfo.HTTP_ORIGIN + basePath + '/resources/page-with-fragment.html#fragment';
-    var remoteURL = hostInfo.HTTP_REMOTE_ORIGIN + basePath + "/resources/blank1.html"
+  var next;
+  function frameOnload() {
+    if (next) {
+      next();
+      dump("next \n");
+    } else {
+      dump("no next \n");
+      // The test does the following navigation steps for iframe
+      // 1. load page-with-fragment.html#fragment
+      // 2. load blank1
+      // 3. go back to page-with-fragment.html
+      async_test(function(t) {
+        var iframe = document.querySelector('iframe');
+        var hostInfo = get_host_info();
+        var basePath = location.pathname.substring(0, location.pathname.lastIndexOf('/'));
+        var localURL = hostInfo.HTTP_ORIGIN + basePath + '/resources/page-with-fragment.html#fragment';
+        var remoteURL = hostInfo.HTTP_REMOTE_ORIGIN + basePath + "/resources/blank1.html"
+
+        var steps = [
+          function() {
+            assert_equals(iframe.contentWindow.location.href, localURL, 'should be on page-with-fragment page');
+            // wait one animation frame to ensure layout is run and fragment scrolling is complete
+            iframe.contentWindow.requestAnimationFrame(function() {
+              assert_approx_equals(iframe.contentWindow.scrollY, 800, 5, 'should scroll to fragment');
 
-    var steps = [
-      function() {
-        iframe.src = 'resources/page-with-fragment.html#fragment';
-      }, function() {
-        assert_equals(iframe.contentWindow.location.href, localURL, 'should be on page-with-fragment page');
-        // wait one animation frame to ensure layout is run and fragment scrolling is complete
-        iframe.contentWindow.requestAnimationFrame(function() {
-          assert_equals(iframe.contentWindow.scrollY, 800, 'should scroll to fragment');
+              iframe.contentWindow.history.scrollRestoration = 'manual';
+              assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual');
+              setTimeout(next, 0);
+            });
+          }, function() {
+            // navigate to a new page from a different origin
+            iframe.src = remoteURL;
+          }, function() {
+            // going back causes the iframe to traverse back
+            history.back();
+          }, function() {
+            // coming back from history, scrollRestoration should be set to manual and respected
+            assert_equals(iframe.contentWindow.location.href, localURL, 'should be back on page-with-fragment page');
+            iframe.contentWindow.requestAnimationFrame(t.step_func_done(function() {
+              assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual', 'navigating back should retain scrollRestoration value');
+              assert_equals(iframe.contentWindow.scrollX, 0, 'should not scroll to fragment');
+              assert_equals(iframe.contentWindow.scrollY, 0, 'should not scroll to fragment');
+            }));
+          }
+        ];
 
-          iframe.contentWindow.history.scrollRestoration = 'manual';
-          assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual');
-          setTimeout(next, 0);
+        var stepCount = 0;
+        next = t.step_func(function() {
+          steps[stepCount++]();
         });
-      }, function() {
-        // navigate to a new page from a different origin
-        iframe.src = remoteURL;
-      }, function() {
-        // going back causes the iframe to traverse back
-        history.back();
-      }, function() {
-        // coming back from history, scrollRestoration should be set to manual and respected
-        assert_equals(iframe.contentWindow.location.href, localURL, 'should be back on page-with-fragment page');
-        iframe.contentWindow.requestAnimationFrame(t.step_func_done(function() {
-          assert_equals(iframe.contentWindow.history.scrollRestoration, 'manual', 'navigating back should retain scrollRestoration value');
-          assert_equals(iframe.contentWindow.scrollX, 0, 'should not scroll to fragment');
-          assert_equals(iframe.contentWindow.scrollY, 0, 'should not scroll to fragment');
-        }));
-      }
-    ];
+        next();
+      }, 'Manual scroll restoration should take precedent over scrolling to fragment in cross origin navigation');
+    }
+  }
+</script>
+<iframe src="resources/page-with-fragment.html#fragment" onload="frameOnload()"></iframe>
 
-    var stepCount = 0;
-    var next = t.step_func(function() {
-      steps[stepCount++]();
-    });
-
-    iframe.onload = next;
-    next();
-  }, 'Manual scroll restoration should take precedent over scrolling to fragment in cross origin navigation');
-</script>
--- a/testing/web-platform/tests/referrer-policy/generic/referrer-policy-test-case.sub.js
+++ b/testing/web-platform/tests/referrer-policy/generic/referrer-policy-test-case.sub.js
@@ -124,16 +124,22 @@ function ReferrerPolicyTestCase(scenario
       url: urls.testUrl,
       policyDeliveries: [delivery]
     };
 
     let currentURL = location.toString();
     const expectedReferrer =
       referrerUrlResolver[scenario.referrer_url](currentURL);
 
+    function asyncResolve(result) {
+      return new Promise((resolve, reject) => {
+        step_timeout(() => resolve(result), 0);
+      });
+    }
+
     // Request in the top-level document.
     promise_test(_ => {
       return invokeRequest(subresource, [])
         .then(result => checkResult(expectedReferrer, result));
     }, testDescription);
 
     // `Referer` headers with length over 4k are culled down to an origin, so, let's test around
     // that boundary for tests that would otherwise return the complete URL.
@@ -141,39 +147,42 @@ function ReferrerPolicyTestCase(scenario
       promise_test(_ => {
         history.pushState(null, null, "/");
         history.replaceState(null, null, "A".repeat(4096 - location.href.length - 1));
         const expectedReferrer = location.href;
         // Ensure that we don't load the same URL as the previous test.
         subresource.url += "&-1";
         return invokeRequest(subresource, [])
           .then(result => checkResult(location.href, result))
-          .finally(_ => history.back());
+          .then(_ => history.back())
+          .then(asyncResolve);
       }, "`Referer` header with length < 4k is not stripped to an origin.");
 
       promise_test(_ => {
         history.pushState(null, null, "/");
         history.replaceState(null, null, "A".repeat(4096 - location.href.length));
         const expectedReferrer = location.href;
         // Ensure that we don't load the same URL as the previous test.
         subresource.url += "&0";
         return invokeRequest(subresource, [])
           .then(result => checkResult(expectedReferrer, result))
-          .finally(_ => history.back());
+          .then(_ => history.back())
+          .then(asyncResolve);
       }, "`Referer` header with length == 4k is not stripped to an origin.");
 
       promise_test(_ => {
         const originString = referrerUrlResolver["origin"](currentURL);
         history.pushState(null, null, "/");
         history.replaceState(null, null, "A".repeat(4096 - location.href.length + 1));
         // Ensure that we don't load the same URL as the previous test.
         subresource.url += "&+1";
         return invokeRequest(subresource, [])
           .then(result => checkResult(originString, result))
-          .finally(_ => history.back());
+          .then(_ => history.back())
+          .then(asyncResolve);
       }, "`Referer` header with length > 4k is stripped to an origin.");
     }
 
     // We test requests from inside iframes only for <img> tags.
     // This is just to preserve the previous test coverage.
     // TODO(hiroshige): Enable iframe tests for all subresource types.
     if (scenario.subresource !== "img-tag") {
       return;
--- a/toolkit/components/places/tests/sync/test_bookmark_abort_merging.js
+++ b/toolkit/components/places/tests/sync/test_bookmark_abort_merging.js
@@ -4,20 +4,19 @@
 var { AsyncShutdown } = ChromeUtils.import(
   "resource://gre/modules/AsyncShutdown.jsm"
 );
 
 add_task(async function test_abort_merging() {
   let buf = await openMirror("abort_merging");
 
   let controller = new AbortController();
-  let promiseWasMerged = buf.merge(controller.signal);
   controller.abort();
   await Assert.rejects(
-    promiseWasMerged,
+    buf.merge(controller.signal),
     /Operation aborted/,
     "Should abort merge when signaled"
   );
 
   // Even though the merger is already finalized on the Rust side, the DB
   // connection is still open on the JS side. Finalizing `buf` closes it.
   await buf.finalize();
   await PlacesUtils.bookmarks.eraseEverything();
rename from toolkit/library/gtest/static/TestUCRTDepends.cpp
rename to toolkit/library/gtest/TestUCRTDepends.cpp
--- a/toolkit/library/gtest/moz.build
+++ b/toolkit/library/gtest/moz.build
@@ -2,28 +2,27 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 FINAL_TARGET = 'dist/bin/gtest'
 
 if CONFIG['ENABLE_TESTS']:
-	USE_LIBS += [
-	    'gkrust-gtest',
-	]
+    USE_LIBS += [
+        'gkrust-gtest',
+    ]
+
+    if CONFIG['OS_ARCH'] == 'WINNT':
+        UNIFIED_SOURCES += [
+            'TestUCRTDepends.cpp',
+        ]
 
 USE_LIBS += [
     'static:xul',
-    # xul-gtest is an intermediate static library. It is used as FINAL_TARGET
-    # for gtest code.
-    # If the FINAL_TARGET were the library in this directory, then the gtest
-    # code would end up before static:xul, and before StaticXULComponentStart,
-    # which needs to stay first.
-    'xul-gtest',
 ]
 
 if CONFIG['OS_ARCH'] == 'Linux' and CONFIG['OS_TARGET'] != 'Android':
     GENERATED_FILES += ['symverscript']
     GENERATED_FILES['symverscript'].script = '/build/gen_symverscript.py'
     GENERATED_FILES['symverscript'].inputs = ['../symverscript.in']
     GENERATED_FILES['symverscript'].flags = [
         'xul%s' % CONFIG['MOZILLA_SYMBOLVERSION']
@@ -34,16 +33,10 @@ if CONFIG['OS_ARCH'] == 'Linux' and CONF
 # too old to support Python pretty-printers; if this changes, we could
 # make this 'ifdef GNU_CC'.
 if CONFIG['OS_ARCH'] == 'Linux':
     # Create a GDB Python auto-load file alongside the libxul shared library
     # in the build directory.
     DEFINES['topsrcdir'] = TOPSRCDIR
     OBJDIR_PP_FILES.toolkit.library.gtest += ['../libxul.so-gdb.py.in']
 
-# This needs to come after static:xul to avoid things like libfallible coming
-# before StaticXULComponentStart.
-Libxul('xul-gtest-real',
+Libxul('xul-gtest',
        output_category=None if CONFIG['LINK_GTEST_DURING_COMPILE'] else 'gtest')
-
-DIRS += [
-    'static',
-]
deleted file mode 100644
--- a/toolkit/library/gtest/static/moz.build
+++ /dev/null
@@ -1,14 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-Library('xul-gtest')
-
-if CONFIG['ENABLE_TESTS'] and CONFIG['OS_ARCH'] == 'WINNT':
-    UNIFIED_SOURCES += [
-        'TestUCRTDepends.cpp',
-    ]
-
-Libxul_defines()