Merge mozilla-central to mozilla-inbound. a=merge
authorDaniel Varga <dvarga@mozilla.com>
Wed, 27 Feb 2019 18:36:49 +0200
changeset 461638 76f51b56356621314c701b39e134f9e3d3ebb06a
parent 461637 8eb14440dc5b45373528a2feb57e03a45254fcc8 (current diff)
parent 461435 198cd4a81bf2afa7cc79360f90da7bc91218b76d (diff)
child 461639 8d678e501c5abb55bd58ec76fc8a1caa9d62615c
push id35626
push usercsabou@mozilla.com
push dateThu, 28 Feb 2019 11:31:08 +0000
treeherdermozilla-central@2ea0c1db7e60 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. a=merge
browser/base/content/test/general/browser_bug555767.js
browser/base/content/test/general/browser_bug655584.js
browser/components/urlbar/tests/browser/browser_switchTab_closesUrlbarPopup.js
browser/components/urlbar/tests/browser/browser_switchToTab_closes_newtab.js
devtools/client/debugger/new/src/client/firefox/workers.js
mobile/android/themes/core/about.css
mobile/android/themes/core/aboutMemory.css
mobile/android/themes/core/aboutSupport.css
toolkit/themes/mobile/global/about.css
toolkit/themes/mobile/global/aboutMemory.css
toolkit/themes/mobile/global/aboutSupport.css
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -149,18 +149,16 @@ skip-if = true # Bug 1478159
 subsuite = clipboard
 skip-if = true # bug 1393813
 # skip-if = e10s # Bug 1134458 - Find bar doesn't work correctly in a detached tab
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug537474.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug555224.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_bug555767.js]
-# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug559991.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug563588.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug565575.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug567306.js]
 subsuite = clipboard
@@ -203,18 +201,16 @@ skip-if = (verify && debug && (os == 'li
 [browser_bug623893.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug624734.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug633691.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug647886.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_bug655584.js]
-# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug664672.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug676619.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug710878.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_bug719271.js]
 skip-if = os == "win" && debug && e10s || (verify && debug && (os == 'linux')) # Bug 1315042
--- a/browser/components/customizableui/test/browser_947914_button_copy.js
+++ b/browser/components/customizableui/test/browser_947914_button_copy.js
@@ -22,16 +22,17 @@ add_task(async function() {
     info("Menu panel was opened");
 
     let copyButton = document.getElementById("copy-button");
     ok(copyButton, "Copy button exists in Panel Menu");
     ok(copyButton.getAttribute("disabled"), "Copy button is initially disabled");
 
     // copy text from URL bar
     gURLBar.value = testText;
+    gURLBar.valueIsTyped = true;
     gURLBar.focus();
     gURLBar.select();
     await document.getElementById("nav-bar").overflowable.show();
     info("Menu panel was opened");
 
     ok(!copyButton.hasAttribute("disabled"), "Copy button is enabled when selecting");
 
     await SimpleTest.promiseClipboardChange(testText, () => {
--- a/browser/components/customizableui/test/browser_947914_button_cut.js
+++ b/browser/components/customizableui/test/browser_947914_button_cut.js
@@ -21,16 +21,17 @@ add_task(async function() {
     info("Menu panel was opened");
 
     let cutButton = document.getElementById("cut-button");
     ok(cutButton, "Cut button exists in Panel Menu");
     ok(cutButton.hasAttribute("disabled"), "Cut button is disabled");
 
     // cut text from URL bar
     gURLBar.value = testText;
+    gURLBar.valueIsTyped = true;
     gURLBar.focus();
     gURLBar.select();
     await document.getElementById("nav-bar").overflowable.show();
     info("Menu panel was opened");
 
     ok(!cutButton.hasAttribute("disabled"), "Cut button is enabled when selecting");
     await SimpleTest.promiseClipboardChange(testText, () => {
       cutButton.click();
--- a/browser/components/customizableui/test/browser_editcontrols_update.js
+++ b/browser/components/customizableui/test/browser_editcontrols_update.js
@@ -19,32 +19,32 @@ function expectCommandUpdate(count, test
       supportsCommand(cmd) { return cmd == "cmd_delete"; },
       isCommandEnabled(cmd) {
         if (!count) {
           ok(false, "unexpected update");
           reject();
         }
 
         if (!--count) {
-          testWindow.gURLBar.controllers.removeControllerAt(0, overrideController);
+          testWindow.gURLBar.inputField.controllers.removeControllerAt(0, overrideController);
           testWindow.gBrowser.selectedBrowser.controllers.removeControllerAt(0, overrideController);
           resolve(true);
         }
       },
     };
 
     if (!count) {
       SimpleTest.executeSoon(() => {
-        testWindow.gURLBar.controllers.removeControllerAt(0, overrideController);
+        testWindow.gURLBar.inputField.controllers.removeControllerAt(0, overrideController);
         testWindow.gBrowser.selectedBrowser.controllers.removeControllerAt(0, overrideController);
         resolve(false);
       });
     }
 
-    testWindow.gURLBar.controllers.insertControllerAt(0, overrideController);
+    testWindow.gURLBar.inputField.controllers.insertControllerAt(0, overrideController);
     testWindow.gBrowser.selectedBrowser.controllers.insertControllerAt(0, overrideController);
   });
 }
 
 add_task(async function test_init() {
   // Put something on the clipboard to verify that the paste button is properly enabled during the test.
   let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
   await new Promise(resolve => {
--- a/browser/components/customizableui/test/browser_insert_before_moved_node.js
+++ b/browser/components/customizableui/test/browser_insert_before_moved_node.js
@@ -10,17 +10,17 @@ add_task(async function() {
     CustomizableUI.addWidgetToArea("real-button", toolbar);
     CustomizableUI.addWidgetToArea("moved-button-not-here", toolbar);
     let placements = CustomizableUI.getWidgetIdsInArea(toolbar);
     Assert.deepEqual(placements.slice(-2), ["real-button", "moved-button-not-here"],
       "Should have correct placements");
     let otherButton = document.createXULElement("toolbarbutton");
     otherButton.id = "moved-button-not-here";
     if (toolbar == "nav-bar") {
-      gURLBar.parentNode.appendChild(otherButton);
+      gURLBar.textbox.parentNode.appendChild(otherButton);
     } else {
       gBrowser.tabContainer.appendChild(otherButton);
     }
     CustomizableUI.destroyWidget("real-button");
     CustomizableUI.createWidget({id: "real-button", label: "test real button"});
 
     let button = document.getElementById("real-button");
     ok(button, "Button should exist");
@@ -29,9 +29,8 @@ add_task(async function() {
       is(button.parentNode, expectedContainer, "Button should be in the toolbar");
     }
 
     CustomizableUI.destroyWidget("real-button");
     otherButton.remove();
     CustomizableUI.reset();
   }
 });
-
--- a/browser/components/extensions/test/browser/browser_ext_addon_debugging_netmonitor.js
+++ b/browser/components/extensions/test/browser/browser_ext_addon_debugging_netmonitor.js
@@ -1,39 +1,78 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-// There are shutdown issues for which multiple rejections are left uncaught.
-// See bug 1018184 for resolving these issues.
-PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
+const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+
+const {DebuggerClient} = require("devtools/shared/client/debugger-client");
+const {DebuggerServer} = require("devtools/server/main");
+const {gDevTools} = require("devtools/client/framework/devtools");
+const {Toolbox} = require("devtools/client/framework/toolbox");
 
-ChromeUtils.defineModuleGetter(this, "BrowserToolboxProcess",
-                               "resource://devtools/client/framework/ToolboxProcess.jsm");
+async function setupToolboxTest(extensionId) {
+  DebuggerServer.init();
+  DebuggerServer.registerAllActors();
+  const transport = DebuggerServer.connectPipe();
+  const client = new DebuggerClient(transport);
+  await client.connect();
+  const addonFront = await client.mainRoot.getAddon({id: extensionId});
+  const target = await addonFront.connect();
+  const toolbox = await gDevTools.showToolbox(target, null, Toolbox.HostType.WINDOW);
 
-async function setupToolboxProcessTest(toolboxProcessScript) {
-  // Enable addon debugging.
-  await SpecialPowers.pushPrefEnv({
-    "set": [
-      // Force enabling of addons debugging
-      ["devtools.chrome.enabled", true],
-      ["devtools.debugger.remote-enabled", true],
-      // Disable security prompt
-      ["devtools.debugger.prompt-connection", false],
-      // Enable Browser toolbox test script execution via env variable
-      ["devtools.browser-toolbox.allow-unsafe-script", true],
-    ],
+  async function waitFor(condition) {
+    while (!condition()) {
+      // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+      await new Promise(done => window.setTimeout(done, 1000));
+    }
+  }
+
+  const console = await toolbox.selectTool("webconsole");
+  const {hud} = console;
+  const {jsterm} = hud;
+
+  const netmonitor = await toolbox.selectTool("netmonitor");
+
+  const expectedURL = "http://mochi.test:8888/?test_netmonitor=1";
+
+  // Call a function defined in the target extension to make it
+  // fetch from an expected http url.
+  await jsterm.execute(`doFetchHTTPRequest("${expectedURL}");`);
+
+  await waitFor(() => {
+    return !netmonitor.panelWin.document.querySelector(".request-list-empty-notice");
   });
 
-  let env = Cc["@mozilla.org/process/environment;1"]
-              .getService(Ci.nsIEnvironment);
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", `(${toolboxProcessScript})();`);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
+  let {store} = netmonitor.panelWin;
+
+  // NOTE: we need to filter the requests to the ones that we expect until
+  // the network monitor is not yet filtering out the requests that are not
+  // coming from an extension window or a descendent of an extension window,
+  // in both oop and non-oop extension mode (filed as Bug 1442621).
+  function filterRequest(request) {
+    return request.url === expectedURL;
+  }
+
+  let requests;
+
+  await waitFor(() => {
+    requests = Array.from(store.getState().requests.requests.values())
+                    .filter(filterRequest);
+
+    return requests.length > 0;
   });
+
+  // Call a function defined in the target extension to make assertions
+  // on the network requests collected by the netmonitor panel.
+  await jsterm.execute(`testNetworkRequestReceived(${JSON.stringify(requests)});`);
+
+  const onToolboxClosed = gDevTools.once("toolbox-destroyed");
+  await toolbox.destroy();
+  return onToolboxClosed;
 }
 
 add_task(async function test_addon_debugging_netmonitor_panel() {
   const EXTENSION_ID = "test-monitor-panel@mozilla";
 
   function background() {
     let expectedURL;
     window.doFetchHTTPRequest = async function(urlToFetch) {
@@ -61,75 +100,18 @@ add_task(async function test_addon_debug
         gecko: {id: EXTENSION_ID},
       },
     },
   });
 
   await extension.startup();
   await extension.awaitMessage("ready");
 
-  // Be careful, this JS function is going to be executed in the addon toolbox,
-  // which lives in another process. So do not try to use any scope variable!
-  const toolboxProcessScript = async function() {
-    /* eslint-disable no-undef */
-    async function waitFor(condition) {
-      while (!condition()) {
-        // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
-        await new Promise(done => window.setTimeout(done, 1000));
-      }
-    }
-
-    const console = await toolbox.selectTool("webconsole");
-    const {hud} = console;
-    const {jsterm} = hud;
-
-    const netmonitor = await toolbox.selectTool("netmonitor");
-
-    const expectedURL = "http://mochi.test:8888/?test_netmonitor=1";
-
-    // Call a function defined in the target extension to make it
-    // fetch from an expected http url.
-    await jsterm.execute(`doFetchHTTPRequest("${expectedURL}");`);
-
-    await waitFor(() => {
-      return !netmonitor.panelWin.document.querySelector(".request-list-empty-notice");
-    });
-
-    let {store} = netmonitor.panelWin;
-
-    // NOTE: we need to filter the requests to the ones that we expect until
-    // the network monitor is not yet filtering out the requests that are not
-    // coming from an extension window or a descendent of an extension window,
-    // in both oop and non-oop extension mode (filed as Bug 1442621).
-    function filterRequest(request) {
-      return request.url === expectedURL;
-    }
-
-    let requests;
-
-    await waitFor(() => {
-      requests = Array.from(store.getState().requests.requests.values())
-                      .filter(filterRequest);
-
-      return requests.length > 0;
-    });
-
-    // Call a function defined in the target extension to make assertions
-    // on the network requests collected by the netmonitor panel.
-    await jsterm.execute(`testNetworkRequestReceived(${JSON.stringify(requests)});`);
-
-    await toolbox.destroy();
-    /* eslint-enable no-undef */
-  };
-
-  await setupToolboxProcessTest(toolboxProcessScript);
-  const browserToolboxProcess = new BrowserToolboxProcess({
-    addonID: EXTENSION_ID,
-  });
-
-  let onToolboxClose = browserToolboxProcess.once("close");
-  await extension.awaitFinish("netmonitor_request_logged");
-  await onToolboxClose;
+  const onToolboxClose = setupToolboxTest(EXTENSION_ID);
+  await Promise.all([
+    extension.awaitFinish("netmonitor_request_logged"),
+    onToolboxClose,
+  ]);
 
   info("Addon Toolbox closed");
 
   await extension.unload();
 });
--- a/browser/components/urlbar/tests/browser/browser.ini
+++ b/browser/components/urlbar/tests/browser/browser.ini
@@ -39,16 +39,18 @@ subsuite = clipboard
 [browser_populateAfterPushState.js]
 [browser_redirect_error.js]
 support-files = redirect_error.sjs
 [browser_remotetab.js]
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
 subsuite = clipboard
 [browser_search_favicon.js]
 skip-if = true # Bug 1526222 - Doesn't currently work with QuantumBar
+[browser_switchTab_closesUrlbarPopup.js]
+[browser_switchToTab_closes_newtab.js]
 [browser_switchToTabHavingURI_aOpenParams.js]
 [browser_tabMatchesInAwesomebar_perwindowpb.js]
 skip-if = os == 'linux' # Bug 1104755 (Intermittent failure)
 [browser_tabMatchesInAwesomebar.js]
 support-files =
   moz.png
 [browser_urlbar_blanking.js]
 support-files =
rename from browser/base/content/test/general/browser_bug655584.js
rename to browser/components/urlbar/tests/browser/browser_switchTab_closesUrlbarPopup.js
--- a/browser/base/content/test/general/browser_bug655584.js
+++ b/browser/components/urlbar/tests/browser/browser_switchTab_closesUrlbarPopup.js
@@ -1,23 +1,43 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-// Bug 655584 - awesomebar suggestions don't update after tab is closed
+/**
+ * Checks that switching tabs closes the urlbar popup.
+ */
+
+"use strict";
 
 add_task(async function() {
-  var tab1 = BrowserTestUtils.addTab(gBrowser);
-  var tab2 = BrowserTestUtils.addTab(gBrowser);
+  let tab1 = BrowserTestUtils.addTab(gBrowser);
+  let tab2 = BrowserTestUtils.addTab(gBrowser);
+
+  registerCleanupFunction(async () => {
+    await PlacesUtils.history.clear();
+    BrowserTestUtils.removeTab(tab1);
+    BrowserTestUtils.removeTab(tab2);
+  });
+
+  // Add a couple of dummy entries to ensure the history popup will open.
+  await PlacesTestUtils.addVisits([
+    { uri: makeURI("http://example.com/foo") },
+    { uri: makeURI("http://example.com/foo/bar") },
+  ]);
 
   // When urlbar in a new tab is focused, and a tab switch occurs,
   // the urlbar popup should be closed
   await BrowserTestUtils.switchTab(gBrowser, tab2);
   gURLBar.focus(); // focus the urlbar in the tab we will switch to
   await BrowserTestUtils.switchTab(gBrowser, tab1);
-  gURLBar.openPopup();
-  await BrowserTestUtils.switchTab(gBrowser, tab2);
-  ok(!gURLBar.popupOpen, "urlbar focused in tab to switch to, close popup");
-
-  // cleanup
-  gBrowser.removeCurrentTab();
-  gBrowser.removeCurrentTab();
+  // Now open the popup by the history marker.
+  await UrlbarTestUtils.promisePopupOpen(window, () => {
+    let historyDropMarker =
+      window.document.getAnonymousElementByAttribute(gURLBar.textbox, "anonid", "historydropmarker");
+    EventUtils.synthesizeMouseAtCenter(historyDropMarker, {}, window);
+  });
+  // Check that the popup closes when we switch tab.
+  await UrlbarTestUtils.promisePopupClose(window, () => {
+    return BrowserTestUtils.switchTab(gBrowser, tab2);
+  });
+  Assert.ok(true, "Popup was successfully closed");
 });
rename from browser/base/content/test/general/browser_bug555767.js
rename to browser/components/urlbar/tests/browser/browser_switchToTab_closes_newtab.js
--- a/browser/base/content/test/general/browser_bug555767.js
+++ b/browser/components/urlbar/tests/browser/browser_switchToTab_closes_newtab.js
@@ -1,53 +1,47 @@
-    /* This Source Code Form is subject to the terms of the Mozilla Public
-     * License, v. 2.0. If a copy of the MPL was not distributed with this
-     * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-    add_task(async function listener() {
-      let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
-      let tabSelected = false;
+/**
+ * This tests that switch to tab from a blank tab switches and then closes
+ * the blank tab.
+ */
 
-      // Open the base tab
-      let baseTab = BrowserTestUtils.addTab(gBrowser, testURL);
+"use strict";
 
-      // Wait for the tab to be fully loaded so matching happens correctly
-      await promiseTabLoaded(baseTab);
-      if (baseTab.linkedBrowser.currentURI.spec == "about:blank")
-        return;
-      baseTab.linkedBrowser.removeEventListener("load", listener, true);
+add_task(async function test_switchToTab_closes() {
+  let testURL = "http://example.org/browser/browser/components/urlbar/tests/browser/dummy_page.html";
 
-      let testTab = BrowserTestUtils.addTab(gBrowser);
+  // Open the base tab
+  let baseTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, testURL);
 
-      // Select the testTab
-      gBrowser.selectedTab = testTab;
+  if (baseTab.linkedBrowser.currentURI.spec == "about:blank")
+    return;
 
-      // Set the urlbar to include the moz-action
-      gURLBar.value = "moz-action:switchtab," + JSON.stringify({url: testURL});
-      // Focus the urlbar so we can press enter
-      gURLBar.focus();
+  // Open a blank tab to start the test from.
+  let testTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
 
-      // Functions for TabClose and TabSelect
-      function onTabClose(aEvent) {
-        gBrowser.tabContainer.removeEventListener("TabClose", onTabClose);
-        // Make sure we get the TabClose event for testTab
-        is(aEvent.originalTarget, testTab, "Got the TabClose event for the right tab");
-        // Confirm that we did select the tab
-        ok(tabSelected, "Confirming that the tab was selected");
-        gBrowser.removeTab(baseTab);
-        finish();
-      }
-      function onTabSelect(aEvent) {
-        gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect);
-        // Make sure we got the TabSelect event for baseTab
-        is(aEvent.originalTarget, baseTab, "Got the TabSelect event for the right tab");
-        // Confirm that the selected tab is in fact base tab
-        is(gBrowser.selectedTab, baseTab, "We've switched to the correct tab");
-        tabSelected = true;
-      }
+  // Functions for TabClose and TabSelect
+  let tabClosePromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer,
+    "TabClose", false, event => {
+      return event.originalTarget == testTab;
+    });
+  let tabSelectPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer,
+    "TabSelect", false, event => {
+      return event.originalTarget == baseTab;
+    });
+
+  await UrlbarTestUtils.promiseAutocompleteResultPopup(window, "dummy", waitForFocus);
 
-      // Add the TabClose, TabSelect event listeners before we press enter
-      gBrowser.tabContainer.addEventListener("TabClose", onTabClose);
-      gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect);
+  // The first result is the heuristic, the second will be the switch to tab.
+  let element = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
+  EventUtils.synthesizeMouseAtCenter(element, {}, window);
+
+  await Promise.all([tabSelectPromise, tabClosePromise]);
 
-      // Press enter!
-      EventUtils.synthesizeKey("KEY_Enter");
-    });
+  // Confirm that the selected tab is now the base tab
+  Assert.equal(gBrowser.selectedTab, baseTab,
+    "Should have switched to the correct tab");
+
+  gBrowser.removeTab(baseTab);
+});
--- a/browser/components/urlbar/tests/legacy/browser.ini
+++ b/browser/components/urlbar/tests/legacy/browser.ini
@@ -78,16 +78,18 @@ skip-if = (os == "linux" || os == "mac")
 subsuite = clipboard
 [../browser/browser_populateAfterPushState.js]
 [../browser/browser_redirect_error.js]
 support-files = ../browser/redirect_error.sjs
 [../browser/browser_remotetab.js]
 [../browser/browser_removeUnsafeProtocolsFromURLBarPaste.js]
 subsuite = clipboard
 [../browser/browser_search_favicon.js]
+[../browser/browser_switchTab_closesUrlbarPopup.js]
+[../browser/browser_switchToTab_closes_newtab.js]
 [../browser/browser_switchToTabHavingURI_aOpenParams.js]
 [../browser/browser_tabMatchesInAwesomebar_perwindowpb.js]
 skip-if = os == 'linux' # Bug 1104755
 [../browser/browser_tabMatchesInAwesomebar.js]
 support-files =
   ../browser/moz.png
 [../browser/browser_urlbarAboutHomeLoading.js]
 [../browser/browser_urlbarCopying.js]
--- a/build/windows_toolchain.py
+++ b/build/windows_toolchain.py
@@ -230,17 +230,17 @@ def format_manifest(manifest):
     return b'\n'.join(sha256_lines)
 
 
 def write_zip(zip_path, prefix=None):
     """Write toolchain data to a zip file."""
     if isinstance(prefix, unicode): # noqa Special case for Python 2
         prefix = prefix.encode('utf-8')
 
-    with JarWriter(file=zip_path, optimize=False, compress_level=5) as zip:
+    with JarWriter(file=zip_path, compress_level=5) as zip:
         manifest = {}
         for p, data, mode in resolve_files_and_hash(manifest):
             print(p)
             if prefix:
                 p = mozpath.join(prefix, p)
 
             zip.add(p, data, mode=mode)
 
--- a/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
+++ b/devtools/client/aboutdebugging-new/src/actions/debug-targets.js
@@ -7,18 +7,17 @@
 const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
 const { gDevToolsBrowser } = require("devtools/client/framework/devtools-browser");
 const { remoteClientManager } =
   require("devtools/client/shared/remote-debugging/remote-client-manager");
 
 const { l10n } = require("../modules/l10n");
 
 const {
-  debugLocalAddon,
-  debugRemoteAddon,
+  debugAddon,
   openTemporaryExtension,
   uninstallAddon,
 } = require("../modules/extensions-helper");
 
 const {
   getCurrentClient,
   getCurrentRuntime,
 } = require("../modules/runtimes-state-helper");
@@ -56,22 +55,17 @@ function inspectDebugTarget(type, id) {
           const remoteId = remoteClientManager.getRemoteId(runtime.id, runtime.type);
           window.open(`about:devtools-toolbox?type=tab&id=${id}&remoteId=${remoteId}`);
         } else if (runtimeType === RUNTIMES.THIS_FIREFOX) {
           window.open(`about:devtools-toolbox?type=tab&id=${id}`);
         }
         break;
       }
       case DEBUG_TARGETS.EXTENSION: {
-        if (runtimeType === RUNTIMES.NETWORK || runtimeType === RUNTIMES.USB) {
-          const devtoolsClient = runtimeDetails.clientWrapper.client;
-          await debugRemoteAddon(id, devtoolsClient);
-        } else if (runtimeType === RUNTIMES.THIS_FIREFOX) {
-          debugLocalAddon(id);
-        }
+        await debugAddon(id, runtimeDetails.clientWrapper.client);
         break;
       }
       case DEBUG_TARGETS.WORKER: {
         // Open worker toolbox in new window.
         const devtoolsClient = runtimeDetails.clientWrapper.client;
         const front = devtoolsClient.getActor(id);
         gDevToolsBrowser.openWorkerToolbox(front);
         break;
--- a/devtools/client/aboutdebugging-new/src/modules/extensions-helper.js
+++ b/devtools/client/aboutdebugging-new/src/modules/extensions-helper.js
@@ -1,73 +1,45 @@
 /* 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 { Cc, Ci } = require("chrome");
-loader.lazyImporter(this, "BrowserToolboxProcess",
-  "resource://devtools/client/framework/ToolboxProcess.jsm");
 loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
 
 const {Toolbox} = require("devtools/client/framework/toolbox");
 
 const {gDevTools} = require("devtools/client/framework/devtools");
 
-let browserToolboxProcess = null;
-let remoteAddonToolbox = null;
-function closeToolbox() {
-  if (browserToolboxProcess) {
-    browserToolboxProcess.close();
-  }
-
-  if (remoteAddonToolbox) {
-    remoteAddonToolbox.destroy();
-  }
-}
+let addonToolbox = null;
 
 /**
- * Start debugging an addon in the current instance of Firefox.
- *
- * @param {String} addonID
- *        String id of the addon to debug.
- */
-exports.debugLocalAddon = async function(addonID) {
-  // Close previous addon debugging toolbox.
-  closeToolbox();
-
-  browserToolboxProcess = BrowserToolboxProcess.init({
-    addonID,
-    onClose: () => {
-      browserToolboxProcess = null;
-    },
-  });
-};
-
-/**
- * Start debugging an addon in a remote instance of Firefox.
+ * Start debugging an addon.
  *
  * @param {String} id
  *        The addon id to debug.
  * @param {DebuggerClient} client
- *        Required for remote debugging.
+ *        Required for debugging.
  */
-exports.debugRemoteAddon = async function(id, client) {
+exports.debugAddon = async function(id, client) {
   const addonFront = await client.mainRoot.getAddon({ id });
 
   const target = await addonFront.connect();
 
   // Close previous addon debugging toolbox.
-  closeToolbox();
+  if (addonToolbox) {
+    addonToolbox.destroy();
+  }
 
   const hostType = Toolbox.HostType.WINDOW;
-  remoteAddonToolbox = await gDevTools.showToolbox(target, null, hostType);
-  remoteAddonToolbox.once("destroy", () => {
-    remoteAddonToolbox = null;
+  addonToolbox = await gDevTools.showToolbox(target, null, hostType);
+  addonToolbox.once("destroy", () => {
+    addonToolbox = null;
   });
 };
 
 /**
  * Uninstall the addon with the provided id.
  * Resolves when the addon shutdown has completed.
  */
 exports.uninstallAddon = async function(addonID) {
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_console.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_console.js
@@ -11,20 +11,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
-
 // This is a migration from:
 // https://searchfox.org/mozilla-central/source/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
 
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - when the debug button is clicked on a webextension, the opened toolbox
  *   has a working webconsole with the background page as default target;
  */
@@ -40,39 +36,35 @@ add_task(async function testWebExtension
                     this.browser.runtime.getManifest());
       };
     },
     id: ADDON_ID,
     name: ADDON_NAME,
   }, document);
   const target = findDebugTargetByText(ADDON_NAME, document);
 
-  info("Setup the toolbox test function as environment variable");
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + toolboxTestScript);
-  registerCleanupFunction(() => env.set("MOZ_TOOLBOX_TEST_SCRIPT", ""));
-
-  info("Click inspect to open the addon toolbox, wait for toolbox close event");
-  const onToolboxClose = BrowserToolboxProcess.once("close");
+  info("Open a toolbox to debug the addon");
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   const inspectButton = target.querySelector(".js-debug-target-inspect-button");
   inspectButton.click();
-  await onToolboxClose;
+  const toolbox = await onToolboxReady;
+  toolboxTestScript(toolbox);
 
   // The test script will not close the toolbox and will timeout if it fails, so reaching
   // this point in the test is enough to assume the test was successful.
+  info("Wait for the toolbox to close");
+  await onToolboxClose;
   ok(true, "Addon toolbox closed");
 
   await removeTemporaryExtension(ADDON_NAME, document);
   await removeTab(tab);
 });
 
-// Be careful, this JS function is going to be executed in the addon toolbox,
-// which lives in another process. So do not try to use any scope variable!
-function toolboxTestScript() {
-  /* eslint-disable no-undef */
+function toolboxTestScript(toolbox) {
   function findMessages(hud, text, selector = ".message") {
     const messages = hud.ui.outputNode.querySelectorAll(selector);
     const elements = Array.prototype.filter.call(
       messages,
       (el) => el.textContent.includes(text)
     );
     return elements;
   }
@@ -87,13 +79,12 @@ function toolboxTestScript() {
     .then(async console => {
       const { hud } = console;
       const { jsterm } = hud;
       const onMessage = waitFor(() => {
         return findMessages(hud, "Background page function called").length > 0;
       });
       await jsterm.execute("myWebExtensionAddonFunction()");
       await onMessage;
-      await toolbox.destroy();
+      await toolbox.closeToolbox();
     })
     .catch(e => dump("Exception from browser toolbox process: " + e + "\n"));
-  /* eslint-enable no-undef */
 }
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_inspector.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_inspector.js
@@ -11,20 +11,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
-
 // This is a migration from:
 // https://searchfox.org/mozilla-central/source/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
 
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - the webextension developer toolbox has a working Inspector panel, with the
  *   background page as default target;
  */
@@ -37,39 +33,35 @@ add_task(async function testWebExtension
     background: function() {
       document.body.innerText = "Background Page Body Test Content";
     },
     id: ADDON_ID,
     name: ADDON_NAME,
   }, document);
   const target = findDebugTargetByText(ADDON_NAME, document);
 
-  info("Setup the toolbox test function as environment variable");
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + toolboxTestScript);
-  registerCleanupFunction(() => env.set("MOZ_TOOLBOX_TEST_SCRIPT", ""));
-
-  info("Click inspect to open the addon toolbox, wait for toolbox close event");
-  const onToolboxClose = BrowserToolboxProcess.once("close");
+  info("Open a toolbox to debug the addon");
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   const inspectButton = target.querySelector(".js-debug-target-inspect-button");
   inspectButton.click();
-  await onToolboxClose;
+  const toolbox = await onToolboxReady;
+  toolboxTestScript(toolbox);
 
   // The test script will not close the toolbox and will timeout if it fails, so reaching
   // this point in the test is enough to assume the test was successful.
+  info("Wait for the toolbox to close");
+  await onToolboxClose;
   ok(true, "Addon toolbox closed");
 
   await removeTemporaryExtension(ADDON_NAME, document);
   await removeTab(tab);
 });
 
-// Be careful, this JS function is going to be executed in the addon toolbox,
-// which lives in another process. So do not try to use any scope variable!
-function toolboxTestScript() {
-  /* eslint-disable no-undef */
+async function toolboxTestScript(toolbox) {
   toolbox.selectTool("inspector")
     .then(inspector => {
       return inspector.walker.querySelector(inspector.walker.rootNode, "body");
     })
     .then((nodeActor) => {
       if (!nodeActor) {
         throw new Error("nodeActor not found");
       }
@@ -89,16 +81,15 @@ function toolboxTestScript() {
         throw new Error(
           `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
         );
       }
 
       dump("Got the expected inline text content in the selected node\n");
       return Promise.resolve();
     })
-    .then(() => toolbox.destroy())
+    .then(() => toolbox.closeToolbox())
     .catch((error) => {
       dump("Error while running code in the browser toolbox process:\n");
       dump(error + "\n");
       dump("stack:\n" + error.stack + "\n");
     });
-  /* eslint-enable no-undef */
 }
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_nobg.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_nobg.js
@@ -8,20 +8,16 @@ Services.scriptloader.loadSubScript(CHRO
 // There are shutdown issues for which multiple rejections are left uncaught.
 // See bug 1018184 for resolving these issues.
 const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 const ADDON_NOBG_ID = "test-devtools-webextension-nobg@mozilla.org";
 const ADDON_NOBG_NAME = "test-devtools-webextension-nobg";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm");
-
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - the webextension developer toolbox is connected to a fallback page when the
  *   background page is not available (and in the fallback page document body contains
  *   the expected message, which warns the user that the current page is not a real
  *   webextension context);
  */
 add_task(async function testWebExtensionsToolboxNoBackgroundPage() {
@@ -31,88 +27,62 @@ add_task(async function testWebExtension
 
   await installTemporaryExtensionFromXPI({
     // Do not pass any `background` script.
     id: ADDON_NOBG_ID,
     name: ADDON_NOBG_NAME,
   }, document);
   const target = findDebugTargetByText(ADDON_NOBG_NAME, document);
 
-  info("Setup the toolbox test function as environment variable");
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + toolboxTestScript);
-  env.set("MOZ_TOOLBOX_TEST_ADDON_NOBG_NAME", ADDON_NOBG_NAME);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
-    env.set("MOZ_TOOLBOX_TEST_ADDON_NOBG_NAME", "");
-  });
-
-  info("Click inspect to open the addon toolbox, wait for toolbox close event");
-  const onToolboxClose = BrowserToolboxProcess.once("close");
+  info("Open a toolbox to debug the addon");
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   const inspectButton = target.querySelector(".js-debug-target-inspect-button");
   inspectButton.click();
-  await onToolboxClose;
+  const toolbox = await onToolboxReady;
+  toolboxTestScript(toolbox);
 
   // The test script will not close the toolbox and will timeout if it fails, so reaching
   // this point in the test is enough to assume the test was successful.
+  info("Wait for the toolbox to close");
+  await onToolboxClose;
   ok(true, "Addon toolbox closed");
 
   await removeTemporaryExtension(ADDON_NOBG_NAME, document);
   await removeTab(tab);
 });
 
-// Be careful, this JS function is going to be executed in the addon toolbox,
-// which lives in another process. So do not try to use any scope variable!
-function toolboxTestScript() {
-  /* eslint-disable no-undef */
-  // This is webextension toolbox process. So we can't access mochitest framework.
-  const waitUntil = async function(predicate, interval = 10) {
-    if (await predicate()) {
-      return true;
-    }
-    return new Promise(resolve => {
-      toolbox.win.setTimeout(function() {
-        waitUntil(predicate, interval).then(() => resolve(true));
-      }, interval);
-    });
-  };
-
-  // We pass the expected target name as an environment variable because this method
-  // runs in a different process and cannot access the const defined in this test file.
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-  const expectedName = env.get("MOZ_TOOLBOX_TEST_ADDON_NOBG_NAME");
-
+async function toolboxTestScript(toolbox) {
   const targetName = toolbox.target.name;
   const isAddonTarget = toolbox.target.isAddon;
-  if (!(isAddonTarget && targetName === expectedName)) {
-    dump(`Expected target name "${expectedName}", got ${targetName}`);
+  if (!(isAddonTarget && targetName === ADDON_NOBG_NAME)) {
+    dump(`Expected target name "${ADDON_NOBG_NAME}", got ${targetName}`);
     throw new Error("Toolbox doesn't have the expected target");
   }
 
   toolbox.selectTool("inspector").then(async inspector => {
     let nodeActor;
 
     dump(`Wait the fallback window to be fully loaded\n`);
-    await waitUntil(async () => {
+    await asyncWaitUntil(async () => {
       nodeActor = await inspector.walker.querySelector(inspector.walker.rootNode, "h1");
       return nodeActor && nodeActor.inlineTextChild;
     });
 
     dump("Got a nodeActor with an inline text child\n");
     const expectedValue = "Your addon does not have any document opened yet.";
     const actualValue = nodeActor.inlineTextChild._form.nodeValue;
 
     if (actualValue !== expectedValue) {
       throw new Error(
         `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
       );
     }
 
     dump("Got the expected inline text content in the selected node\n");
 
-    await toolbox.destroy();
+    await toolbox.closeToolbox();
   }).catch((error) => {
     dump("Error while running code in the browser toolbox process:\n");
     dump(error + "\n");
     dump("stack:\n" + error.stack + "\n");
   });
-  /* eslint-enable no-undef */
 }
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_popup.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_addons_debug_popup.js
@@ -11,20 +11,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
-
 // This is a migration from:
 // https://searchfox.org/mozilla-central/source/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
 
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - when the debug button is clicked on a webextension, the opened toolbox
  *   has a working webconsole with the background page as default target;
  * - the webextension developer toolbox has a working Inspector panel, with the
@@ -115,25 +111,23 @@ add_task(async function testWebExtension
       },
     },
     id: ADDON_ID,
     name: ADDON_NAME,
   }, document);
 
   const target = findDebugTargetByText(ADDON_NAME, document);
 
-  info("Setup the toolbox test function as environment variable");
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + toolboxTestScript);
-  registerCleanupFunction(() => env.set("MOZ_TOOLBOX_TEST_SCRIPT", ""));
-
-  info("Click inspect to open the addon toolbox");
-  const onToolboxClose = BrowserToolboxProcess.once("close");
+  info("Open a toolbox to debug the addon");
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   const inspectButton = target.querySelector(".js-debug-target-inspect-button");
   inspectButton.click();
+  const toolbox = await onToolboxReady;
+  toolboxTestScript(toolbox);
 
   info("Wait until the addon popup is opened from the test script");
   await onReadyForOpenPopup;
 
   const widgetId = ADDON_ID.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
   const browserActionId = widgetId + "-browser-action";
   const browserActionEl = window.document.getElementById(browserActionId);
 
@@ -156,20 +150,17 @@ add_task(async function testWebExtension
   // The test script will not close the toolbox and will timeout if it fails, so reaching
   // this point in the test is enough to assume the test was successful.
   ok(true, "Addon toolbox closed");
 
   await removeTemporaryExtension(ADDON_NAME, document);
   await removeTab(tab);
 });
 
-// Be careful, this JS function is going to be executed in the addon toolbox,
-// which lives in another process. So do not try to use any scope variable!
-function toolboxTestScript() {
-  /* eslint-disable no-undef */
+async function toolboxTestScript(toolbox) {
   let jsterm;
   const popupFramePromise = new Promise(resolve => {
     const listener = data => {
       if (data.frames.some(({url}) => url && url.endsWith("popup.html"))) {
         toolbox.target.off("frame-update", listener);
         resolve();
       }
     };
@@ -205,54 +196,40 @@ function toolboxTestScript() {
         // Wait the new frame update (once the extension popup has been opened).
         popupFramePromise,
       ]);
 
       dump(`Clicking the frame list button\n`);
       const btn = toolbox.doc.getElementById("command-button-frames");
       btn.click();
 
-      // This is webextension toolbox process. So we can't access mochitest framework.
-      const waitUntil = function(predicate, interval = 10) {
-        if (predicate()) {
-          return Promise.resolve(true);
-        }
-        return new Promise(resolve => {
-          toolbox.win.setTimeout(function() {
-            waitUntil(predicate, interval).then(() => resolve(true));
-          }, interval);
-        });
-      };
-      await waitUntil(() => btn.style.pointerEvents === "none");
-      dump(`Clicked the frame list button\n`);
-
       const menuList = toolbox.doc.getElementById("toolbox-frame-menu");
       const frames = Array.from(menuList.querySelectorAll(".command"));
 
       if (frames.length != 2) {
         throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
       }
 
       const popupFrameBtn = frames.filter((frame) => {
         return frame.querySelector(".label").textContent.endsWith("popup.html");
       }).pop();
 
       if (!popupFrameBtn) {
         throw Error("Extension Popup frame not found in the listed frames");
       }
 
       const waitForNavigated = toolbox.target.once("navigate");
-
       popupFrameBtn.click();
-
+      // Clicking the menu item may do highlighting.
+      await waitUntil(() => toolbox.highlighter);
+      await Promise.race([toolbox.highlighter.once("node-highlight"), wait(1000)]);
       await waitForNavigated;
 
       await jsterm.execute("myWebExtensionPopupAddonFunction()");
 
-      await toolbox.destroy();
+      await toolbox.closeToolbox();
     })
     .catch((error) => {
       dump("Error while running code in the browser toolbox process:\n");
       dump(error + "\n");
       dump("stack:\n" + error.stack + "\n");
     });
-  /* eslint-enable no-undef */
 }
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_telemetry_runtime_updates.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_telemetry_runtime_updates.js
@@ -121,10 +121,10 @@ async function addUsbRuntime(runtime, mo
 
   info("Wait for the runtime to appear in the sidebar");
   await waitUntil(() => findSidebarItemByText(runtime.shortName, doc));
 }
 
 async function removeUsbRuntime(runtime, mocks, doc) {
   mocks.removeRuntime(runtime.id);
   mocks.emitUSBUpdate();
-  await waitUntil(() => !findSidebarItemByText(runtime.shortName, document));
+  await waitUntil(() => !findSidebarItemByText(runtime.shortName, doc));
 }
--- a/devtools/client/aboutdebugging/components/addons/Target.js
+++ b/devtools/client/aboutdebugging/components/addons/Target.js
@@ -5,18 +5,17 @@
 /* eslint-env browser */
 
 "use strict";
 
 const { Component } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const {
-  debugLocalAddon,
-  debugRemoteAddon,
+  debugAddon,
   getExtensionUuid,
   isTemporaryID,
   parseFileUri,
   uninstallAddon,
 } = require("../../modules/addon");
 const Services = require("Services");
 
 loader.lazyRequireGetter(this, "DebuggerClient",
@@ -170,23 +169,18 @@ class AddonTarget extends Component {
   constructor(props) {
     super(props);
     this.debug = this.debug.bind(this);
     this.uninstall = this.uninstall.bind(this);
     this.reload = this.reload.bind(this);
   }
 
   debug() {
-    const { client, connect, target } = this.props;
-
-    if (connect.type === "REMOTE") {
-      debugRemoteAddon(target.addonID, client);
-    } else if (connect.type === "LOCAL") {
-      debugLocalAddon(target.addonID);
-    }
+    const { client, target } = this.props;
+    debugAddon(target.addonID, client);
   }
 
   uninstall() {
     const { target } = this.props;
     uninstallAddon(target.addonID);
   }
 
   async reload() {
--- a/devtools/client/aboutdebugging/modules/addon.js
+++ b/devtools/client/aboutdebugging/modules/addon.js
@@ -2,18 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 loader.lazyImporter(this, "AddonManagerPrivate", "resource://gre/modules/AddonManager.jsm");
 
 const {
-  debugLocalAddon,
-  debugRemoteAddon,
+  debugAddon,
   getExtensionUuid,
   openTemporaryExtension,
   parseFileUri,
   uninstallAddon,
 } = require("devtools/client/aboutdebugging-new/src/modules/extensions-helper");
 
 /**
  * Most of the implementation for this module has been moved to
@@ -25,14 +24,13 @@ exports.isTemporaryID = function(addonID
   return AddonManagerPrivate.isTemporaryInstallID(addonID);
 };
 
 /**
  * See JSDoc in devtools/client/aboutdebugging-new/src/modules/extensions-helper for all
  * the methods exposed below.
  */
 
-exports.debugLocalAddon = debugLocalAddon;
-exports.debugRemoteAddon = debugRemoteAddon;
+exports.debugAddon = debugAddon;
 exports.getExtensionUuid = getExtensionUuid;
 exports.openTemporaryExtension = openTemporaryExtension;
 exports.parseFileUri = parseFileUri;
 exports.uninstallAddon = uninstallAddon;
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
@@ -9,20 +9,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm");
-
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - when the debug button is clicked on a webextension, the opened toolbox
  *   has a working webconsole with the background page as default target;
  */
 add_task(async function testWebExtensionsToolboxWebConsole() {
   const addonFile = ExtensionTestCommon.generateXPI({
     background: function() {
@@ -39,58 +35,50 @@ add_task(async function testWebExtension
     },
   });
   registerCleanupFunction(() => addonFile.remove(false));
 
   const {
     tab, document, debugBtn,
   } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, addonFile);
 
-  // Be careful, this JS function is going to be executed in the addon toolbox,
-  // which lives in another process. So do not try to use any scope variable!
-  const env = Cc["@mozilla.org/process/environment;1"]
-              .getService(Ci.nsIEnvironment);
-  const testScript = function() {
-    /* eslint-disable no-undef */
-    function findMessages(hud, text, selector = ".message") {
-      const messages = hud.ui.outputNode.querySelectorAll(selector);
-      const elements = Array.prototype.filter.call(
-        messages,
-        (el) => el.textContent.includes(text)
-      );
-      return elements;
-    }
-
-    async function waitFor(condition) {
-      while (!condition()) {
-        await new Promise(done => window.setTimeout(done, 1000));
-      }
-    }
-
-    toolbox.selectTool("webconsole")
-      .then(async console => {
-        const { hud } = console;
-        const { jsterm } = hud;
-        const onMessage = waitFor(() => {
-          return findMessages(hud, "Background page function called").length > 0;
-        });
-        await jsterm.execute("myWebExtensionAddonFunction()");
-        await onMessage;
-        await toolbox.destroy();
-      })
-      .catch(e => dump("Exception from browser toolbox process: " + e + "\n"));
-    /* eslint-enable no-undef */
-  };
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
-  });
-
-  const onToolboxClose = BrowserToolboxProcess.once("close");
-
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   debugBtn.click();
+  const toolbox = await onToolboxReady;
+  testScript(toolbox);
 
   await onToolboxClose;
   ok(true, "Addon toolbox closed");
 
   await uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
   await closeAboutDebugging(tab);
 });
+
+const testScript = function(toolbox) {
+  function findMessages(hud, text, selector = ".message") {
+    const messages = hud.ui.outputNode.querySelectorAll(selector);
+    const elements = Array.prototype.filter.call(
+      messages,
+      (el) => el.textContent.includes(text)
+    );
+    return elements;
+  }
+
+  async function waitFor(condition) {
+    while (!condition()) {
+      await new Promise(done => window.setTimeout(done, 1000));
+    }
+  }
+
+  toolbox.selectTool("webconsole")
+         .then(async console => {
+           const { hud } = console;
+           const { jsterm } = hud;
+           const onMessage = waitFor(() => {
+             return findMessages(hud, "Background page function called").length > 0;
+           });
+           await jsterm.execute("myWebExtensionAddonFunction()");
+           await onMessage;
+           await toolbox.destroy();
+         })
+         .catch(e => dump("Exception from browser toolbox process: " + e + "\n"));
+};
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
@@ -8,20 +8,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm");
-
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - the webextension developer toolbox has a working Inspector panel, with the
  *   background page as default target;
  */
 add_task(async function testWebExtensionsToolboxInspector() {
   const addonFile = ExtensionTestCommon.generateXPI({
     background: function() {
@@ -35,65 +31,58 @@ add_task(async function testWebExtension
     },
   });
   registerCleanupFunction(() => addonFile.remove(false));
 
   const {
     tab, document, debugBtn,
   } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, addonFile);
 
-  // Be careful, this JS function is going to be executed in the addon toolbox,
-  // which lives in another process. So do not try to use any scope variable!
-  const env = Cc["@mozilla.org/process/environment;1"]
-        .getService(Ci.nsIEnvironment);
-  const testScript = function() {
-    /* eslint-disable no-undef */
-    toolbox.selectTool("inspector")
-      .then(inspector => {
-        return inspector.walker.querySelector(inspector.walker.rootNode, "body");
-      })
-      .then((nodeActor) => {
-        if (!nodeActor) {
-          throw new Error("nodeActor not found");
-        }
-
-        dump("Got a nodeActor\n");
-
-        if (!(nodeActor.inlineTextChild)) {
-          throw new Error("inlineTextChild not found");
-        }
-
-        dump("Got a nodeActor with an inline text child\n");
-
-        const expectedValue = "Background Page Body Test Content";
-        const actualValue = nodeActor.inlineTextChild._form.nodeValue;
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
+  debugBtn.click();
+  const toolbox = await onToolboxReady;
+  testScript(toolbox);
 
-        if (String(actualValue).trim() !== String(expectedValue).trim()) {
-          throw new Error(
-            `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
-          );
-        }
-
-        dump("Got the expected inline text content in the selected node\n");
-        return Promise.resolve();
-      })
-      .then(() => toolbox.destroy())
-      .catch((error) => {
-        dump("Error while running code in the browser toolbox process:\n");
-        dump(error + "\n");
-        dump("stack:\n" + error.stack + "\n");
-      });
-    /* eslint-enable no-undef */
-  };
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
-  });
-
-  const onToolboxClose = BrowserToolboxProcess.once("close");
-  debugBtn.click();
   await onToolboxClose;
-
   ok(true, "Addon toolbox closed");
 
   await uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
   await closeAboutDebugging(tab);
 });
+
+const testScript = function(toolbox) {
+  toolbox.selectTool("inspector")
+         .then(inspector => {
+           return inspector.walker.querySelector(inspector.walker.rootNode, "body");
+         })
+         .then((nodeActor) => {
+           if (!nodeActor) {
+             throw new Error("nodeActor not found");
+           }
+
+           dump("Got a nodeActor\n");
+
+           if (!(nodeActor.inlineTextChild)) {
+             throw new Error("inlineTextChild not found");
+           }
+
+           dump("Got a nodeActor with an inline text child\n");
+
+           const expectedValue = "Background Page Body Test Content";
+           const actualValue = nodeActor.inlineTextChild._form.nodeValue;
+
+           if (String(actualValue).trim() !== String(expectedValue).trim()) {
+             throw new Error(
+               `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
+             );
+           }
+
+           dump("Got the expected inline text content in the selected node\n");
+           return Promise.resolve();
+         })
+         .then(() => toolbox.destroy())
+         .catch((error) => {
+           dump("Error while running code in the browser toolbox process:\n");
+           dump(error + "\n");
+           dump("stack:\n" + error.stack + "\n");
+         });
+};
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
@@ -8,20 +8,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_NOBG_ID = "test-devtools-webextension-nobg@mozilla.org";
 const ADDON_NOBG_NAME = "test-devtools-webextension-nobg";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm");
-
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - the webextension developer toolbox is connected to a fallback page when the
  *   background page is not available (and in the fallback page document body contains
  *   the expected message, which warns the user that the current page is not a real
  *   webextension context);
  */
 add_task(async function testWebExtensionsToolboxNoBackgroundPage() {
@@ -34,69 +30,50 @@ add_task(async function testWebExtension
     },
   });
   registerCleanupFunction(() => addonFile.remove(false));
 
   const {
     tab, document, debugBtn,
   } = await setupTestAboutDebuggingWebExtension(ADDON_NOBG_NAME, addonFile);
 
-  // Be careful, this JS function is going to be executed in the addon toolbox,
-  // which lives in another process. So do not try to use any scope variable!
-  const env = Cc["@mozilla.org/process/environment;1"]
-        .getService(Ci.nsIEnvironment);
-  const testScript = function() {
-    /* eslint-disable no-undef */
-    // This is webextension toolbox process. So we can't access mochitest framework.
-    const waitUntil = async function(predicate, interval = 10) {
-      if (await predicate()) {
-        return true;
-      }
-      return new Promise(resolve => {
-        toolbox.win.setTimeout(function() {
-          waitUntil(predicate, interval).then(() => resolve(true));
-        }, interval);
-      });
-    };
-
-    toolbox.selectTool("inspector").then(async inspector => {
-      let nodeActor;
-
-      dump(`Wait the fallback window to be fully loaded\n`);
-      await waitUntil(async () => {
-        nodeActor = await inspector.walker.querySelector(inspector.walker.rootNode, "h1");
-        return nodeActor && nodeActor.inlineTextChild;
-      });
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
+  debugBtn.click();
+  const toolbox = await onToolboxReady;
+  testScript(toolbox);
 
-      dump("Got a nodeActor with an inline text child\n");
-      const expectedValue = "Your addon does not have any document opened yet.";
-      const actualValue = nodeActor.inlineTextChild._form.nodeValue;
-
-      if (actualValue !== expectedValue) {
-        throw new Error(
-          `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
-        );
-      }
-
-      dump("Got the expected inline text content in the selected node\n");
-
-      await toolbox.destroy();
-    }).catch((error) => {
-      dump("Error while running code in the browser toolbox process:\n");
-      dump(error + "\n");
-      dump("stack:\n" + error.stack + "\n");
-    });
-    /* eslint-enable no-undef */
-  };
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
-  });
-
-  const onToolboxClose = BrowserToolboxProcess.once("close");
-  debugBtn.click();
   await onToolboxClose;
-
   ok(true, "Addon toolbox closed");
 
   await uninstallAddon({document, id: ADDON_NOBG_ID, name: ADDON_NOBG_NAME});
   await closeAboutDebugging(tab);
 });
+
+const testScript = function(toolbox) {
+  toolbox.selectTool("inspector").then(async inspector => {
+    let nodeActor;
+
+    dump(`Wait the fallback window to be fully loaded\n`);
+    await asyncWaitUntil(async () => {
+      nodeActor = await inspector.walker.querySelector(inspector.walker.rootNode, "h1");
+      return nodeActor && nodeActor.inlineTextChild;
+    });
+
+    dump("Got a nodeActor with an inline text child\n");
+    const expectedValue = "Your addon does not have any document opened yet.";
+    const actualValue = nodeActor.inlineTextChild._form.nodeValue;
+
+    if (actualValue !== expectedValue) {
+      throw new Error(
+        `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
+      );
+    }
+
+    dump("Got the expected inline text content in the selected node\n");
+
+    await toolbox.destroy();
+  }).catch((error) => {
+    dump("Error while running code in the browser toolbox process:\n");
+    dump(error + "\n");
+    dump("stack:\n" + error.stack + "\n");
+  });
+};
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
@@ -8,20 +8,16 @@ const { PromiseTestUtils } = scopedCuImp
 PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
 
 // Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
 requestLongerTimeout(2);
 
 const ADDON_ID = "test-devtools-webextension@mozilla.org";
 const ADDON_NAME = "test-devtools-webextension";
 
-const {
-  BrowserToolboxProcess,
-} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm");
-
 /**
  * This test file ensures that the webextension addon developer toolbox:
  * - when the debug button is clicked on a webextension, the opened toolbox
  *   has a working webconsole with the background page as default target;
  * - the webextension developer toolbox has a working Inspector panel, with the
  *   background page as default target;
  * - the webextension developer toolbox is connected to a fallback page when the
  *   background page is not available (and in the fallback page document body contains
@@ -117,122 +113,21 @@ add_task(async function testWebExtension
     // the web console.
     onPopupCustomMessage = waitForExtensionTestMessage("popupPageFunctionCalled");
   });
 
   const {
     tab, document, debugBtn,
   } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, addonFile);
 
-  // Be careful, this JS function is going to be executed in the addon toolbox,
-  // which lives in another process. So do not try to use any scope variable!
-  const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
-
-  const testScript = function() {
-    /* eslint-disable no-undef */
-
-    let jsterm;
-    const popupFramePromise = new Promise(resolve => {
-      const listener = data => {
-        if (data.frames.some(({url}) => url && url.endsWith("popup.html"))) {
-          toolbox.target.off("frame-update", listener);
-          resolve();
-        }
-      };
-      toolbox.target.on("frame-update", listener);
-    });
-
-    const waitForFrameListUpdate = toolbox.target.once("frame-update");
-
-    toolbox.selectTool("webconsole")
-      .then(async (console) => {
-        const clickNoAutoHideMenu = () => {
-          return new Promise(resolve => {
-            toolbox.doc.getElementById("toolbox-meatball-menu-button").click();
-            toolbox.doc.addEventListener("popupshown", () => {
-              const menuItem =
-                    toolbox.doc.getElementById("toolbox-meatball-menu-noautohide");
-              menuItem.click();
-              resolve();
-            }, { once: true });
-          });
-        };
-
-        dump(`Clicking the menu button\n`);
-        await clickNoAutoHideMenu();
-        dump(`Clicked the menu button\n`);
-
-        jsterm = console.hud.jsterm;
-        jsterm.execute("myWebExtensionShowPopup()");
-
-        await Promise.all([
-          // Wait the initial frame update (which list the background page).
-          waitForFrameListUpdate,
-          // Wait the new frame update (once the extension popup has been opened).
-          popupFramePromise,
-        ]);
-
-        dump(`Clicking the frame list button\n`);
-        const btn = toolbox.doc.getElementById("command-button-frames");
-        btn.click();
-
-        // This is webextension toolbox process. So we can't access mochitest framework.
-        const waitUntil = function(predicate, interval = 10) {
-          if (predicate()) {
-            return Promise.resolve(true);
-          }
-          return new Promise(resolve => {
-            toolbox.win.setTimeout(function() {
-              waitUntil(predicate, interval).then(() => resolve(true));
-            }, interval);
-          });
-        };
-        await waitUntil(() => btn.style.pointerEvents === "none");
-        dump(`Clicked the frame list button\n`);
-
-        const menuList = toolbox.doc.getElementById("toolbox-frame-menu");
-        const frames = Array.from(menuList.querySelectorAll(".command"));
-
-        if (frames.length != 2) {
-          throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
-        }
-
-        const popupFrameBtn = frames.filter((frame) => {
-          return frame.querySelector(".label").textContent.endsWith("popup.html");
-        }).pop();
-
-        if (!popupFrameBtn) {
-          throw Error("Extension Popup frame not found in the listed frames");
-        }
-
-        const waitForNavigated = toolbox.target.once("navigate");
-
-        popupFrameBtn.click();
-
-        await waitForNavigated;
-
-        await jsterm.execute("myWebExtensionPopupAddonFunction()");
-
-        await toolbox.destroy();
-      })
-      .catch((error) => {
-        dump("Error while running code in the browser toolbox process:\n");
-        dump(error + "\n");
-        dump("stack:\n" + error.stack + "\n");
-      });
-    /* eslint-enable no-undef */
-  };
-  env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
-  registerCleanupFunction(() => {
-    env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
-  });
-
-  const onToolboxClose = BrowserToolboxProcess.once("close");
-
+  const onToolboxReady = gDevTools.once("toolbox-ready");
+  const onToolboxClose = gDevTools.once("toolbox-destroyed");
   debugBtn.click();
+  const toolbox = await onToolboxReady;
+  testScript(toolbox);
 
   await onReadyForOpenPopup;
 
   const browserActionId = makeWidgetId(ADDON_ID) + "-browser-action";
   const browserActionEl = window.document.getElementById(browserActionId);
 
   ok(browserActionEl, "Got the browserAction button from the browser UI");
   browserActionEl.click();
@@ -240,17 +135,95 @@ add_task(async function testWebExtension
 
   const args = await onPopupCustomMessage;
   ok(true, "Received console message from the popup page function as expected");
   is(args[0], "popupPageFunctionCalled", "Got the expected console message");
   is(args[1] && args[1].name, ADDON_NAME,
      "Got the expected manifest from WebExtension API");
 
   await onToolboxClose;
-
   info("Addon toolbox closed");
 
   is(Services.prefs.getBoolPref("ui.popup.disable_autohide"), false,
      "disable_autohide should be reset to false when the toolbox is closed");
 
   await uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
   await closeAboutDebugging(tab);
 });
+
+const testScript = function(toolbox) {
+  let jsterm;
+  const popupFramePromise = new Promise(resolve => {
+    const listener = data => {
+      if (data.frames.some(({url}) => url && url.endsWith("popup.html"))) {
+        toolbox.target.off("frame-update", listener);
+        resolve();
+      }
+    };
+    toolbox.target.on("frame-update", listener);
+  });
+
+  const waitForFrameListUpdate = toolbox.target.once("frame-update");
+
+  toolbox.selectTool("webconsole")
+         .then(async (console) => {
+           const clickNoAutoHideMenu = () => {
+             return new Promise(resolve => {
+               toolbox.doc.getElementById("toolbox-meatball-menu-button").click();
+               toolbox.doc.addEventListener("popupshown", () => {
+                 const menuItem =
+                   toolbox.doc.getElementById("toolbox-meatball-menu-noautohide");
+                 menuItem.click();
+                 resolve();
+               }, { once: true });
+             });
+           };
+
+           dump(`Clicking the menu button\n`);
+           await clickNoAutoHideMenu();
+           dump(`Clicked the menu button\n`);
+
+           jsterm = console.hud.jsterm;
+           jsterm.execute("myWebExtensionShowPopup()");
+
+           await Promise.all([
+             // Wait the initial frame update (which list the background page).
+             waitForFrameListUpdate,
+             // Wait the new frame update (once the extension popup has been opened).
+             popupFramePromise,
+           ]);
+
+           dump(`Clicking the frame list button\n`);
+           const btn = toolbox.doc.getElementById("command-button-frames");
+           btn.click();
+
+           const menuList = toolbox.doc.getElementById("toolbox-frame-menu");
+           const frames = Array.from(menuList.querySelectorAll(".command"));
+
+           if (frames.length != 2) {
+             throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
+           }
+
+           const popupFrameBtn = frames.filter((frame) => {
+             return frame.querySelector(".label").textContent.endsWith("popup.html");
+           }).pop();
+
+           if (!popupFrameBtn) {
+             throw Error("Extension Popup frame not found in the listed frames");
+           }
+
+           const waitForNavigated = toolbox.target.once("navigate");
+           popupFrameBtn.click();
+           // Clicking the menu item may do highlighting.
+           await waitUntil(() => toolbox.highlighter);
+           await Promise.race([toolbox.highlighter.once("node-highlight"), wait(1000)]);
+           await waitForNavigated;
+
+           await jsterm.execute("myWebExtensionPopupAddonFunction()");
+
+           await toolbox.destroy();
+         })
+         .catch((error) => {
+           dump("Error while running code in the browser toolbox process:\n");
+           dump(error + "\n");
+           dump("stack:\n" + error.stack + "\n");
+         });
+};
--- a/devtools/client/debugger/new/src/client/firefox/workers.js
+++ b/devtools/client/debugger/new/src/client/firefox/workers.js
@@ -33,17 +33,17 @@ export async function updateWorkerClient
       if (workerClients[workerThread.actor].thread != workerThread) {
         throw new Error(`Multiple clients for actor ID: ${workerThread.actor}`);
       }
       newWorkerClients[workerThread.actor] = workerClients[workerThread.actor];
     } else {
       addThreadEventListeners(workerThread);
       workerThread.resume();
 
-      const consoleFront = await tabTarget.getFront("console");
+      const consoleFront = await workerTargetFront.getFront("console");
       await consoleFront.startListeners([]);
 
       newWorkerClients[workerThread.actor] = {
         url: workerTargetFront.url,
         thread: workerThread,
         console: consoleFront
       };
     }
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-windowless-workers.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-windowless-workers.js
@@ -11,16 +11,24 @@ function threadIsPaused(dbg, index) {
 }
 
 function threadIsSelected(dbg, index) {
   return findElement(dbg, "threadsPaneItem", index).classList.contains(
     "selected"
   );
 }
 
+function getLabel(dbg, index) {
+  return findElement(dbg, "expressionNode", index).innerText;
+}
+
+function getValue(dbg, index) {
+  return findElement(dbg, "expressionValue", index).innerText;
+}
+
 // Test basic windowless worker functionality: the main thread and worker can be
 // separately controlled from the same debugger.
 add_task(async function() {
   await pushPref("devtools.debugger.features.windowless-workers", true);
 
   const dbg = await initDebugger("doc-windowless-workers.html");
   const mainThread = dbg.toolbox.threadClient.actor;
 
@@ -40,20 +48,31 @@ add_task(async function() {
 
   info("Test pausing in a worker");
   await dbg.actions.selectThread(worker1Thread);
   assertNotPaused(dbg);
   await dbg.actions.breakOnNext();
   await waitForPaused(dbg, "simple-worker.js");
   assertPausedAtSourceAndLine(dbg, workerSource.id, 3);
 
+  await addExpression(dbg, "count");
+  is(getLabel(dbg, 1), "count");
+  const v = getValue(dbg, 1);
+  ok(v == "" + +v, "Value of count should be a number");
+
   info("Test stepping in a worker");
   await stepOver(dbg);
   assertPausedAtSourceAndLine(dbg, workerSource.id, 4);
 
+  // The watch expression should update with an incremented value.
+  await waitUntil(() => {
+    const v2 = getValue(dbg, 1);
+    return +v2 == +v + 1;
+  });
+
   info("Test resuming in a worker");
   await resume(dbg);
   assertNotPaused(dbg);
 
   info("Test stepping in the main thread");
   dbg.actions.selectThread(mainThread);
   await stepOver(dbg);
   assertPausedAtSourceAndLine(dbg, mainThreadSource.id, 11);
--- a/devtools/client/debugger/new/test/mochitest/helpers.js
+++ b/devtools/client/debugger/new/test/mochitest/helpers.js
@@ -1525,22 +1525,22 @@ async function getDebuggerSplitConsole(d
   await toolbox.openSplitConsole();
   return toolbox.getPanel("webconsole");
 }
 
 async function openConsoleContextMenu(hud, element) {
   const onConsoleMenuOpened = hud.ui.wrapper.once("menu-open");
   synthesizeContextMenuEvent(element);
   await onConsoleMenuOpened;
-  const doc = hud.ui.wrapper.owner.chromeWindow.document;
+  const doc = hud.chromeWindow.document;
   return doc.getElementById("webconsole-menu");
 }
 
 function hideConsoleContextMenu(hud) {
-  const doc = hud.ui.wrapper.owner.chromeWindow.document;
+  const doc = hud.chromeWindow.document;
   const popup = doc.getElementById("webconsole-menu");
   if (!popup) {
     return Promise.resolve();
   }
 
   const onPopupHidden = once(popup, "popuphidden");
   popup.hidePopup();
   return onPopupHidden;
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -34,16 +34,19 @@ loader.lazyRequireGetter(this, "Responsi
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
 const {MultiLocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new MultiLocalizationHelper(
   "devtools/client/locales/startup.properties",
   "devtools/startup/locales/key-shortcuts.properties"
 );
 
+// URL to direct people to the deprecated tools panel
+const DEPRECATION_URL = "https://developer.mozilla.org/en-US/docs/Tools/Deprecated_tools";
+
 var Tools = {};
 exports.Tools = Tools;
 
 // Definitions
 Tools.options = {
   id: "options",
   ordinal: 0,
   url: "chrome://devtools/content/framework/toolbox-options.xhtml",
@@ -171,16 +174,18 @@ Tools.styleEditor = {
 
   build: function(iframeWindow, toolbox) {
     return new StyleEditorPanel(iframeWindow, toolbox);
   },
 };
 
 Tools.shaderEditor = {
   id: "shadereditor",
+  deprecated: true,
+  deprecationURL: DEPRECATION_URL,
   ordinal: 5,
   visibilityswitch: "devtools.shadereditor.enabled",
   icon: "chrome://devtools/skin/images/tool-shadereditor.svg",
   url: "chrome://devtools/content/shadereditor/index.xul",
   label: l10n("ToolboxShaderEditor.label"),
   panelLabel: l10n("ToolboxShaderEditor.panelLabel"),
   tooltip: l10n("ToolboxShaderEditor.tooltip"),
 
@@ -196,16 +201,18 @@ Tools.shaderEditor = {
     }).require;
     const { ShaderEditorPanel } = browserRequire("devtools/client/shadereditor/panel");
     return new ShaderEditorPanel(toolbox);
   },
 };
 
 Tools.canvasDebugger = {
   id: "canvasdebugger",
+  deprecated: true,
+  deprecationURL: DEPRECATION_URL,
   ordinal: 6,
   visibilityswitch: "devtools.canvasdebugger.enabled",
   icon: "chrome://devtools/skin/images/tool-canvas.svg",
   url: "chrome://devtools/content/canvasdebugger/index.xul",
   label: l10n("ToolboxCanvasDebugger.label"),
   panelLabel: l10n("ToolboxCanvasDebugger.panelLabel"),
   tooltip: l10n("ToolboxCanvasDebugger.tooltip"),
 
@@ -335,16 +342,18 @@ Tools.storage = {
 
   build: function(iframeWindow, toolbox) {
     return new StoragePanel(iframeWindow, toolbox);
   },
 };
 
 Tools.webAudioEditor = {
   id: "webaudioeditor",
+  deprecated: true,
+  deprecationURL: DEPRECATION_URL,
   ordinal: 11,
   visibilityswitch: "devtools.webaudioeditor.enabled",
   icon: "chrome://devtools/skin/images/tool-webaudio.svg",
   url: "chrome://devtools/content/webaudioeditor/index.xul",
   label: l10n("ToolboxWebAudioEditor1.label"),
   panelLabel: l10n("ToolboxWebAudioEditor1.panelLabel"),
   tooltip: l10n("ToolboxWebAudioEditor1.tooltip"),
 
--- a/devtools/client/framework/ToolboxProcess.jsm
+++ b/devtools/client/framework/ToolboxProcess.jsm
@@ -30,48 +30,33 @@ var processes = new Set();
 
 /**
  * Constructor for creating a process that will hold a chrome toolbox.
  *
  * @param function onClose [optional]
  *        A function called when the process stops running.
  * @param function onRun [optional]
  *        A function called when the process starts running.
- * @param object options [optional]
- *        An object with properties for configuring BrowserToolboxProcess.
  */
-this.BrowserToolboxProcess = function BrowserToolboxProcess(onClose, onRun, options) {
+this.BrowserToolboxProcess = function BrowserToolboxProcess(onClose, onRun) {
   const emitter = new EventEmitter();
   this.on = emitter.on.bind(emitter);
   this.off = emitter.off.bind(emitter);
   this.once = emitter.once.bind(emitter);
   // Forward any events to the shared emitter.
   this.emit = function(...args) {
     emitter.emit(...args);
     BrowserToolboxProcess.emit(...args);
   };
 
-  // If first argument is an object, use those properties instead of
-  // all three arguments
-  if (typeof onClose === "object") {
-    if (onClose.onClose) {
-      this.once("close", onClose.onClose);
-    }
-    if (onClose.onRun) {
-      this.once("run", onClose.onRun);
-    }
-    this._options = onClose;
-  } else {
-    if (onClose) {
-      this.once("close", onClose);
-    }
-    if (onRun) {
-      this.once("run", onRun);
-    }
-    this._options = options || {};
+  if (onClose) {
+    this.once("close", onClose);
+  }
+  if (onRun) {
+    this.once("run", onRun);
   }
 
   this._telemetry = new Telemetry();
 
   this.close = this.close.bind(this);
   Services.obs.addObserver(this.close, "quit-application");
   this._initServer();
   this._initProfile();
@@ -81,37 +66,31 @@ this.BrowserToolboxProcess = function Br
 };
 
 EventEmitter.decorate(BrowserToolboxProcess);
 
 /**
  * Initializes and starts a chrome toolbox process.
  * @return object
  */
-BrowserToolboxProcess.init = function(onClose, onRun, options) {
+BrowserToolboxProcess.init = function(onClose, onRun) {
   if (!Services.prefs.getBoolPref("devtools.chrome.enabled") ||
       !Services.prefs.getBoolPref("devtools.debugger.remote-enabled")) {
     console.error("Could not start Browser Toolbox, you need to enable it.");
     return null;
   }
-  return new BrowserToolboxProcess(onClose, onRun, options);
+  return new BrowserToolboxProcess(onClose, onRun);
 };
 
 /**
  * Figure out if there are any open Browser Toolboxes that'll need to be restored.
  * @return bool
  */
 BrowserToolboxProcess.getBrowserToolboxSessionState = function() {
-  for (const process of processes.values()) {
-    // Don't worry about addon toolboxes, we only want to restore the Browser Toolbox.
-    if (!process._options || !process._options.addonID) {
-      return true;
-    }
-  }
-  return false;
+  return processes.size !== 0;
 };
 
 BrowserToolboxProcess.prototype = {
   /**
    * Initializes the debugger server.
    */
   _initServer: function() {
     if (this.debuggerServer) {
@@ -130,17 +109,17 @@ BrowserToolboxProcess.prototype = {
     this.loader.invisibleToDebugger = true;
     const { DebuggerServer } = this.loader.require("devtools/server/main");
     const { SocketListener } = this.loader.require("devtools/shared/security/socket");
     this.debuggerServer = DebuggerServer;
     dumpn("Created a separate loader instance for the DebuggerServer.");
 
     this.debuggerServer.init();
     // We mainly need a root actor and target actors for opening a toolbox, even
-    // against chrome/content/addon. But the "no auto hide" button uses the
+    // against chrome/content. But the "no auto hide" button uses the
     // preference actor, so also register the browser actors.
     this.debuggerServer.registerAllActors();
     this.debuggerServer.allowChromeProcess = true;
     dumpn("initialized and added the browser actors for the DebuggerServer.");
 
     const chromeDebuggingWebSocket =
       Services.prefs.getBoolPref("devtools.debugger.chrome-debugging-websocket");
     const socketOptions = {
@@ -252,19 +231,16 @@ BrowserToolboxProcess.prototype = {
       "-chrome", DBG_XUL,
     ];
     const environment = {
       // Disable safe mode for the new process in case this was opened via the
       // keyboard shortcut.
       MOZ_DISABLE_SAFE_MODE_KEY: "1",
       MOZ_BROWSER_TOOLBOX_PORT: String(this.port),
     };
-    if (this._options.addonID) {
-      environment.MOZ_BROWSER_TOOLBOX_ADDONID = String(this._options.addonID);
-    }
 
     // During local development, incremental builds can trigger the main process
     // to clear its startup cache with the "flag file" .purgecaches, but this
     // file is removed during app startup time, so we aren't able to know if it
     // was present in order to also clear the child profile's startup cache as
     // well.
     //
     // As an approximation of "isLocalBuild", check for an unofficial build.
@@ -336,17 +312,16 @@ BrowserToolboxProcess.prototype = {
       this.debuggerServer = null;
     }
 
     dumpn("Chrome toolbox is now closed...");
     this.emit("close", this);
     processes.delete(this);
 
     this._dbgProcess = null;
-    this._options = null;
     if (this.loader) {
       this.loader.destroy();
     }
     this.loader = null;
     this._telemetry = null;
   },
 };
 
--- a/devtools/client/framework/options-panel.css
+++ b/devtools/client/framework/options-panel.css
@@ -112,8 +112,31 @@
   width: 16px;
   height: 16px;
   vertical-align: sub;
   margin-inline-start: 5px;
   -moz-context-properties: fill;
   fill: var(--theme-toolbar-color);
   opacity: 0.6;
 }
+
+.deprecation-notice::before {
+  background-image: url("chrome://global/skin/icons/warning.svg");
+  content: '';
+  display: inline-block;
+  flex-shrink: 0;
+  height: 15px;
+  margin-inline-end: 5px;
+  width: 15px;
+}
+
+.deprecation-notice {
+  align-items: center;
+  background-color: hsl(54, 100%, 92%);
+  color: var(--yellow-80);
+  display: flex;
+  box-shadow: 0 0 0px 4px hsl(54, 100%, 92%);
+  margin-inline-start: 8px;
+}
+
+.deprecation-notice a:hover{
+  text-decoration: underline;
+}
--- a/devtools/client/framework/toolbox-options.js
+++ b/devtools/client/framework/toolbox-options.js
@@ -243,16 +243,30 @@ OptionsPanel.prototype = {
         checkboxInput.setAttribute("checked", "true");
       }
 
       checkboxInput.addEventListener("change",
         onCheckboxClick.bind(checkboxInput, this.telemetry, tool));
 
       checkboxLabel.appendChild(checkboxInput);
       checkboxLabel.appendChild(checkboxSpanLabel);
+
+      // TODO: remove in Firefox 68, with bug #1528296
+      if (tool.deprecated) {
+        const deprecationURL = this.panelDoc.createElement("a");
+        deprecationURL.title = deprecationURL.href = tool.deprecationURL;
+        deprecationURL.textContent = L10N.getFormatStr("options.deprecationNotice");
+        deprecationURL.target = "_blank";
+
+        const checkboxSpanDeprecated = this.panelDoc.createElement("span");
+        checkboxSpanDeprecated.className = "deprecation-notice";
+        checkboxLabel.appendChild(checkboxSpanDeprecated);
+        checkboxSpanDeprecated.appendChild(deprecationURL);
+      }
+
       return checkboxLabel;
     };
 
     // Clean up any existent default tools content.
     for (const label of defaultToolsBox.querySelectorAll("label")) {
       label.remove();
     }
 
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -60,17 +60,16 @@ function hideStatusMessage() {
   toggleStatusMessage(false);
 }
 
 var connect = async function() {
   // Initiate the connection
   const env = Cc["@mozilla.org/process/environment;1"]
     .getService(Ci.nsIEnvironment);
   const port = env.get("MOZ_BROWSER_TOOLBOX_PORT");
-  const addonID = env.get("MOZ_BROWSER_TOOLBOX_ADDONID");
 
   // A port needs to be passed in from the environment, for instance:
   //    MOZ_BROWSER_TOOLBOX_PORT=6080 ./mach run -chrome \
   //      chrome://devtools/content/framework/toolbox-process-window.html
   if (!port) {
     throw new Error("Must pass a port in an env variable with MOZ_BROWSER_TOOLBOX_PORT");
   }
 
@@ -82,24 +81,18 @@ var connect = async function() {
     port,
     webSocket,
   });
   gClient = new DebuggerClient(transport);
   appendStatusMessage("Start protocol client for connection");
   await gClient.connect();
 
   appendStatusMessage("Get root form for toolbox");
-  if (addonID) {
-    const addonFront = await gClient.mainRoot.getAddon({ id: addonID });
-    const addonTargetFront = await addonFront.connect();
-    await openToolbox(addonTargetFront);
-  } else {
-    const front = await gClient.mainRoot.getMainProcess();
-    await openToolbox(front);
-  }
+  const front = await gClient.mainRoot.getMainProcess();
+  await openToolbox(front);
 };
 
 // Certain options should be toggled since we can assume chrome debugging here
 function setPrefDefaults() {
   Services.prefs.setBoolPref("devtools.inspector.showUserAgentStyles", true);
   Services.prefs.setBoolPref("devtools.performance.ui.show-platform-data", true);
   Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
   Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1810,16 +1810,29 @@ Toolbox.prototype = {
 
       // If no parent yet, append the frame into default location.
       if (!iframe.parentNode) {
         const vbox = this.doc.getElementById("toolbox-panel-" + id);
         vbox.appendChild(iframe);
         vbox.visibility = "visible";
       }
 
+      // TODO: remove in Firefox 68, with bug #1528296
+      if (definition.deprecated) {
+        const deprecationURL = this.doc.createXULElement("label");
+        deprecationURL.textContent = L10N.getFormatStr("options.deprecationNotice");
+        deprecationURL.setAttribute("href", definition.deprecationURL);
+        deprecationURL.setAttribute("class", "text-link");
+
+        const deprecationNotice = this.doc.createXULElement("span");
+        deprecationNotice.className = "toolbox-panel_deprecation-notice";
+        deprecationNotice.appendChild(deprecationURL);
+        iframe.parentNode.prepend(deprecationNotice);
+      }
+
       const onLoad = async () => {
         if (id === "inspector") {
           await this._initInspector;
 
           // Stop loading the inspector if the toolbox is already being destroyed. This
           // can happen in unit tests where the tests are rapidly opening and closing the
           // toolbox and we encounter the scenario where the toolbox is closing as
           // the inspector is still loading.
--- a/devtools/client/inspector/markup/markup-context-menu.js
+++ b/devtools/client/inspector/markup/markup-context-menu.js
@@ -319,32 +319,31 @@ class MarkupContextMenu {
    * Use in Console.
    *
    * Takes the currently selected node in the inspector and assigns it to a
    * temp variable on the content window.  Also opens the split console and
    * autofills it with the temp variable.
    */
   _useInConsole() {
     this.toolbox.openSplitConsole().then(() => {
-      const panel = this.toolbox.getPanel("webconsole");
-      const jsterm = panel.hud.jsterm;
+      const {hud} = this.toolbox.getPanel("webconsole");
 
       const evalString = `{ let i = 0;
         while (window.hasOwnProperty("temp" + i) && i < 1000) {
           i++;
         }
         window["temp" + i] = $0;
         "temp" + i;
       }`;
 
       const options = {
         selectedNodeActor: this.selection.nodeFront.actorID,
       };
-      jsterm.requestEvaluation(evalString, options).then((res) => {
-        jsterm.setInputValue(res.result);
+      hud.jsterm.requestEvaluation(evalString, options).then((res) => {
+        hud.setInputValue(res.result);
         this.inspector.emit("console-var-ready");
       });
     });
   }
 
   _buildA11YMenuItem(menu) {
     if (!(this.selection.isElementNode() || this.selection.isTextNode()) ||
         !Services.prefs.getBoolPref("devtools.accessibility.enabled")) {
--- a/devtools/client/inspector/test/browser_inspector_menu-04-use-in-console.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-04-use-in-console.js
@@ -23,27 +23,27 @@ add_task(async function() {
     const menuItem = allMenuItems.find(i => i.id === "node-menu-useinconsole");
     menuItem.click();
 
     await inspector.once("console-var-ready");
 
     const hud = toolbox.getPanel("webconsole").hud;
     const jsterm = hud.jsterm;
 
-    is(jsterm.getInputValue(), "temp0", "first console variable is named temp0");
+    is(hud.getInputValue(), "temp0", "first console variable is named temp0");
 
     let result = await jsterm.execute();
     isnot(result.textContent.indexOf('<p id="console-var">'), -1,
           "variable temp0 references correct node");
 
     await selectNode("#console-var-multi", inspector);
     menuItem.click();
     await inspector.once("console-var-ready");
 
-    is(jsterm.getInputValue(), "temp1", "second console variable is named temp1");
+    is(hud.getInputValue(), "temp1", "second console variable is named temp1");
 
     result = await jsterm.execute();
     isnot(result.textContent.indexOf('<p id="console-var-multi">'), -1,
           "variable temp1 references correct node");
 
     hud.ui.wrapper.dispatchClearHistory();
   }
 });
--- a/devtools/client/locales/en-US/toolbox.properties
+++ b/devtools/client/locales/en-US/toolbox.properties
@@ -22,16 +22,21 @@ toolbox.defaultTitle=Developer Tools
 toolbox.label=Developer Tools
 
 # LOCALIZATION NOTE (options.toolNotSupportedMarker): This is the template
 # used to add a * marker to the label for the Options Panel tool checkbox for the
 # tool which is not supported for the current toolbox target.
 # The name of the tool: %1$S.
 options.toolNotSupportedMarker=%1$S *
 
+# LOCALIZATION NOTE (options.deprecationNotice): This is the template
+# is used to generate a deprecation notice for a panel.
+# This entire text is treated as a link to an MDN page.
+options.deprecationNotice=Deprecated. Learn More…
+
 # LOCALIZATION NOTE (scratchpad.keycode)
 # Used for opening scratchpad from the detached toolbox window
 # Needs to match scratchpad.keycode from browser.dtd
 scratchpad.keycode=VK_F4
 
 # LOCALIZATION NOTE (toolbox.pickButton.tooltip)
 # This is the tooltip of the element picker button in the toolbox toolbar.
 # %S is the keyboard shortcut that toggles the element picker.
--- a/devtools/client/shared/webpack/shims/jsterm-stub.js
+++ b/devtools/client/shared/webpack/shims/jsterm-stub.js
@@ -43,33 +43,33 @@ JSTerm.prototype = {
    * Sets the value of the input field (command line), and resizes the field to
    * fit its contents. This method is preferred over setting "inputNode.value"
    * directly, because it correctly resizes the field.
    *
    * @param string newValue
    *        The new value to set.
    * @returns void
    */
-  setInputValue(newValue) {
+  _setValue(newValue) {
     this.inputNode.value = newValue;
     // this.resizeInput();
   },
 
   /**
    * Gets the value from the input field
    * @returns string
    */
-  getInputValue() {
+  _getValue() {
     return this.inputNode.value || "";
   },
 
   execute(executeString) {
     return new Promise(resolve => {
       // attempt to execute the content of the inputNode
-      executeString = executeString || this.getInputValue();
+      executeString = executeString || this._getValue();
       if (!executeString) {
         return;
       }
 
       const message = new ConsoleCommand({
         messageText: executeString,
       });
       this.ui.proxy.dispatchMessageAdd(message);
@@ -90,17 +90,17 @@ JSTerm.prototype = {
       };
 
       const options = {
         frame: this.SELECTED_FRAME,
         selectedNodeActor: selectedNodeActor,
       };
 
       this.requestEvaluation(executeString, options).then(onResult, onResult);
-      this.setInputValue("");
+      this._setValue("");
     });
   },
 
   /**
    * Request a JavaScript string evaluation from the server.
    *
    * @param string str
    *        String to execute.
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -359,16 +359,38 @@
   -moz-box-flex: 1;
   visibility: collapse;
 }
 
 .toolbox-panel[selected] {
   visibility: visible;
 }
 
+.toolbox-panel_deprecation-notice::before {
+  background-image: url("chrome://global/skin/icons/warning.svg");
+  content: '';
+  display: inline-block;
+  flex-shrink: 0;
+  height: 15px;
+  margin-inline-end: 5px;
+  width: 15px;
+}
+
+.toolbox-panel_deprecation-notice {
+  align-items: center;
+  background-color: hsl(54, 100%, 92%);
+  box-shadow: 0 0 0px 4px hsl(54, 100%, 92%);
+  display: flex;
+  padding: 4px;
+}
+
+.toolbox-panel_deprecation-notice label {
+  color: var(--yellow-80);
+}
+
 /**
  * When panels are collapsed or hidden, making sure that they are also
  * inaccessible by keyboard. This is not the case by default because the are
  * predominantly hidden using visibility: collapse; style or collapsed
  * attribute.
  */
 .toolbox-panel *,
 #toolbox-panel-webconsole[collapsed] * {
--- a/devtools/client/webconsole/browser-console.js
+++ b/devtools/client/webconsole/browser-console.js
@@ -1,61 +1,59 @@
 /* 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";
 
 var Services = require("Services");
-loader.lazyRequireGetter(this, "extend", "devtools/shared/extend", true);
+var WebConsole = require("devtools/client/webconsole/webconsole");
+
 loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
-loader.lazyRequireGetter(this, "WebConsole", "devtools/client/webconsole/webconsole");
 
 /**
  * A BrowserConsole instance is an interactive console initialized *per target*
  * that displays console log data as well as provides an interactive terminal to
  * manipulate the target's document content.
  *
  * This object only wraps the iframe that holds the Browser Console UI. This is
  * meant to be an integration point between the Firefox UI and the Browser Console
  * UI and features.
  *
  * This object extends the WebConsole object located in webconsole.js
- *
- * @constructor
- * @param object target
- *        The target that the browser console will connect to.
- * @param nsIDOMWindow iframeWindow
- *        The window where the browser console UI is already loaded.
- * @param nsIDOMWindow chromeWindow
- *        The window of the browser console owner.
- * @param object hudService
- *        The parent HUD Service
  */
-function BrowserConsole(target, iframeWindow, chromeWindow, hudService) {
-  WebConsole.call(this, target, iframeWindow, chromeWindow, hudService);
-  this._telemetry = new Telemetry();
-}
+class BrowserConsole extends WebConsole {
+  /*
+  * @constructor
+  * @param object target
+  *        The target that the browser console will connect to.
+  * @param nsIDOMWindow iframeWindow
+  *        The window where the browser console UI is already loaded.
+  * @param nsIDOMWindow chromeWindow
+  *        The window of the browser console owner.
+  * @param object hudService
+  *        The parent HUD Service
+  */
+  constructor(target, iframeWindow, chromeWindow, hudService) {
+    super(target, iframeWindow, chromeWindow, hudService, true);
 
-BrowserConsole.prototype = extend(WebConsole.prototype, {
-  _browserConsole: true,
-  _bcInit: null,
-  _bcDestroyer: null,
-
-  $init: WebConsole.prototype.init,
+    this._telemetry = new Telemetry();
+    this._bcInitializer = null;
+    this._bcDestroyer = null;
+  }
 
   /**
    * Initialize the Browser Console instance.
    *
    * @return object
    *         A promise for the initialization.
    */
   init() {
-    if (this._bcInit) {
-      return this._bcInit;
+    if (this._bcInitializer) {
+      return this._bcInitializer;
     }
 
     // Only add the shutdown observer if we've opened a Browser Console window.
     ShutdownObserver.init(this.hudService);
 
     const window = this.iframeWindow;
 
     // Make sure that the closing of the Browser Console window destroys this
@@ -63,21 +61,19 @@ BrowserConsole.prototype = extend(WebCon
     window.addEventListener("unload", () => {
       this.destroy();
     }, {once: true});
 
     // browserconsole is not connected with a toolbox so we pass -1 as the
     // toolbox session id.
     this._telemetry.toolOpened("browserconsole", -1, this);
 
-    this._bcInit = this.$init();
-    return this._bcInit;
-  },
-
-  $destroy: WebConsole.prototype.destroy,
+    this._bcInitializer = super.init();
+    return this._bcInitializer;
+  }
 
   /**
    * Destroy the object.
    *
    * @return object
    *         A promise object that is resolved once the Browser Console is closed.
    */
   destroy() {
@@ -85,25 +81,25 @@ BrowserConsole.prototype = extend(WebCon
       return this._bcDestroyer;
     }
 
     this._bcDestroyer = (async () => {
       // browserconsole is not connected with a toolbox so we pass -1 as the
       // toolbox session id.
       this._telemetry.toolClosed("browserconsole", -1, this);
 
-      await this.$destroy();
+      await super.destroy();
       await this.target.destroy();
       this.hudService._browserConsoleID = null;
       this.chromeWindow.close();
     })();
 
     return this._bcDestroyer;
-  },
-});
+  }
+}
 
 /**
  * The ShutdownObserver listens for app shutdown and saves the current state
  * of the Browser Console for session restore.
  */
 var ShutdownObserver = {
   _initialized: false,
 
--- a/devtools/client/webconsole/components/App.js
+++ b/devtools/client/webconsole/components/App.js
@@ -239,17 +239,19 @@ class App extends Component {
           }),
           JSTerm({
             webConsoleUI,
             serviceContainer,
             onPaste: this.onPaste,
             codeMirrorEnabled: jstermCodeMirror,
           }),
           ReverseSearchInput({
-            webConsoleUI,
+            setInputValue: serviceContainer.setInputValue,
+            focusInput: serviceContainer.focusInput,
+            evaluateInput: serviceContainer.evaluateInput,
             initialValue: reverseSearchInitialValue,
           })
         ),
         SideBar({
           serviceContainer,
         }),
         ConfirmDialog({
           webConsoleUI,
--- a/devtools/client/webconsole/components/ConfirmDialog.js
+++ b/devtools/client/webconsole/components/ConfirmDialog.js
@@ -1,16 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
-loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "HTMLTooltip", "devtools/client/shared/widgets/tooltip/HTMLTooltip", true);
 loader.lazyRequireGetter(this, "createPortal", "devtools/client/shared/vendor/react-dom", true);
 
 // React & Redux
 const { Component } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
@@ -48,17 +47,17 @@ class ConfirmDialog extends Component {
 
     this.cancel = this.cancel.bind(this);
     this.confirm = this.confirm.bind(this);
     this.onLearnMoreClick = this.onLearnMoreClick.bind(this);
   }
 
   componentDidMount() {
     const doc = this.props.webConsoleUI.document;
-    const toolbox = gDevTools.getToolbox(this.props.webConsoleUI.owner.target);
+    const toolbox = this.props.webConsoleUI.wrapper.toolbox;
     const tooltipDoc = toolbox ? toolbox.doc : doc;
     // The popup will be attached to the toolbox document or HUD document in the case
     // such as the browser console which doesn't have a toolbox.
     this.tooltip = new HTMLTooltip(tooltipDoc, {
       className: "invoke-confirm",
     });
   }
 
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -9,17 +9,16 @@ const Services = require("Services");
 
 loader.lazyServiceGetter(this, "clipboardHelper",
                          "@mozilla.org/widget/clipboardhelper;1",
                          "nsIClipboardHelper");
 loader.lazyRequireGetter(this, "Debugger", "Debugger");
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup");
 loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
-loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
 loader.lazyRequireGetter(this, "Editor", "devtools/client/shared/sourceeditor/editor");
 loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
 loader.lazyRequireGetter(this, "saveScreenshot", "devtools/shared/screenshot/save");
 loader.lazyRequireGetter(this, "focusableSelector", "devtools/client/shared/focus", true);
 
 const l10n = require("devtools/client/webconsole/webconsole-l10n");
 
@@ -120,17 +119,17 @@ class JSTerm extends Component {
       onSelect: this.onAutocompleteSelect.bind(this),
       onClick: this.acceptProposedCompletion.bind(this),
       listId: "webConsole_autocompletePopupListBox",
       position: "bottom",
       autoSelect: true,
     };
 
     const doc = this.webConsoleUI.document;
-    const toolbox = gDevTools.getToolbox(this.webConsoleUI.owner.target);
+    const toolbox = this.webConsoleUI.wrapper.toolbox;
     const tooltipDoc = toolbox ? toolbox.doc : doc;
     // The popup will be attached to the toolbox document or HUD document in the case
     // such as the browser console which doesn't have a toolbox.
     this.autocompletePopup = new AutocompletePopup(tooltipDoc, autocompleteOptions);
 
     if (this.props.codeMirrorEnabled) {
       if (this.node) {
         const onArrowUp = () => {
@@ -197,17 +196,17 @@ class JSTerm extends Component {
           tabIndex: "0",
           viewportMargin: Infinity,
           disableSearchAddon: true,
           extraKeys: {
             "Enter": () => {
               // No need to handle shift + Enter as it's natively handled by CodeMirror.
 
               const hasSuggestion = this.hasAutocompletionSuggestion();
-              if (!hasSuggestion && !Debugger.isCompilableUnit(this.getInputValue())) {
+              if (!hasSuggestion && !Debugger.isCompilableUnit(this._getValue())) {
                 // incomplete statement
                 return "CodeMirror.Pass";
               }
 
               if (hasSuggestion) {
                 return this.acceptProposedCompletion();
               }
 
@@ -325,17 +324,17 @@ class JSTerm extends Component {
             },
 
             "Home": () => {
               if (this.autocompletePopup.isOpen) {
                 this.autocompletePopup.selectItemAtIndex(0);
                 return null;
               }
 
-              if (!this.getInputValue()) {
+              if (!this._getValue()) {
                 this.webConsoleUI.outputScroller.scrollTop = 0;
                 return null;
               }
 
               if (this.getAutoCompletionText()) {
                 this.clearCompletion();
               }
 
@@ -344,17 +343,17 @@ class JSTerm extends Component {
 
             "End": () => {
               if (this.autocompletePopup.isOpen) {
                 this.autocompletePopup.selectItemAtIndex(
                   this.autocompletePopup.itemCount - 1);
                 return null;
               }
 
-              if (!this.getInputValue()) {
+              if (!this._getValue()) {
                 const {outputScroller} = this.webConsoleUI;
                 outputScroller.scrollTop = outputScroller.scrollHeight;
                 return null;
               }
 
               if (this.getAutoCompletionText()) {
                 this.clearCompletion();
               }
@@ -403,17 +402,17 @@ class JSTerm extends Component {
       ? this.inputNode.getBoundingClientRect().height - this.inputNode.clientHeight
       : 0;
 
     // Update the character and chevron width needed for the popup offset calculations.
     this._inputCharWidth = this._getInputCharWidth();
     this._paddingInlineStart = this.editor ? null : this._getInputPaddingInlineStart();
 
     this.webConsoleUI.window.addEventListener("blur", this._blurEventHandler);
-    this.lastInputValue && this.setInputValue(this.lastInputValue);
+    this.lastInputValue && this._setValue(this.lastInputValue);
   }
 
   componentWillReceiveProps(nextProps) {
     this.imperativeUpdate(nextProps);
   }
 
   shouldComponentUpdate(nextProps) {
     // XXX: For now, everything is handled in an imperative way and we
@@ -535,17 +534,17 @@ class JSTerm extends Component {
         case "error":
           try {
             errorMessage = l10n.getStr(helperResult.message);
           } catch (ex) {
             errorMessage = helperResult.message;
           }
           break;
         case "help":
-          this.webConsoleUI.owner.openLink(HELP_URL);
+          this.webConsoleUI.hud.openLink(HELP_URL);
           break;
         case "copyValueToClipboard":
           clipboardHelper.copyString(helperResult.value);
           break;
         case "screenshotOutput":
           const { args, value } = helperResult;
           const results = await saveScreenshot(this.webConsoleUI.window, args, value);
           this.screenshotNotify(results);
@@ -573,51 +572,51 @@ class JSTerm extends Component {
     this.webConsoleUI.wrapper.dispatchMessagesAdd(wrappedResults);
   }
 
   /**
    * Execute a string. Execution happens asynchronously in the content process.
    *
    * @param {String} executeString
    *        The string you want to execute. If this is not provided, the current
-   *        user input is used - taken from |this.getInputValue()|.
+   *        user input is used - taken from |this._getValue()|.
    * @returns {Promise}
    *          Resolves with the message once the result is displayed.
    */
   async execute(executeString) {
     // attempt to execute the content of the inputNode
-    executeString = executeString || this.getInputValue();
+    executeString = executeString || this._getValue();
     if (!executeString) {
       return null;
     }
 
     // Append executed expression into the history list.
     this.props.appendToHistory(executeString);
 
     WebConsoleUtils.usageCount++;
-    this.setInputValue("");
+    this._setValue("");
     this.clearCompletion();
 
     let selectedNodeActor = null;
-    const inspectorSelection = this.webConsoleUI.owner.getInspectorSelection();
+    const inspectorSelection = this.webConsoleUI.hud.getInspectorSelection();
     if (inspectorSelection && inspectorSelection.nodeFront) {
       selectedNodeActor = inspectorSelection.nodeFront.actorID;
     }
 
     const { ConsoleCommand } = require("devtools/client/webconsole/types");
     const cmdMessage = new ConsoleCommand({
       messageText: executeString,
       timeStamp: Date.now(),
     });
     this.webConsoleUI.proxy.dispatchMessageAdd(cmdMessage);
 
     let mappedExpressionRes = null;
     try {
       mappedExpressionRes =
-        await this.webConsoleUI.owner.getMappedExpression(executeString);
+        await this.webConsoleUI.hud.getMappedExpression(executeString);
     } catch (e) {
       console.warn("Error when calling getMappedExpression", e);
     }
 
     executeString = mappedExpressionRes ? mappedExpressionRes.expression : executeString;
 
     const options = {
       selectedNodeActor,
@@ -713,17 +712,17 @@ class JSTerm extends Component {
    * Sets the value of the input field (command line), and resizes the field to
    * fit its contents. This method is preferred over setting "inputNode.value"
    * directly, because it correctly resizes the field.
    *
    * @param string newValue
    *        The new value to set.
    * @returns void
    */
-  setInputValue(newValue) {
+  _setValue(newValue) {
     newValue = newValue || "";
     this.lastInputValue = newValue;
 
     if (this.props.codeMirrorEnabled) {
       if (this.editor) {
         // In order to get the autocomplete popup to work properly, we need to set the
         // editor text and the cursor in the same operation. If we don't, the text change
         // is done before the cursor is moved, and the autocompletion call to the server
@@ -752,17 +751,17 @@ class JSTerm extends Component {
     this.resizeInput();
     this.emit("set-input-value");
   }
 
   /**
    * Gets the value from the input field
    * @returns string
    */
-  getInputValue() {
+  _getValue() {
     if (this.props.codeMirrorEnabled) {
       return this.editor ? this.editor.getText() || "" : "";
     }
 
     return this.inputNode ? this.inputNode.value || "" : "";
   }
 
   getSelectionStart() {
@@ -791,17 +790,17 @@ class JSTerm extends Component {
     this.setAutoCompletionText("");
   }
 
   /**
    * The inputNode "input" and "keyup" event handler.
    * @private
    */
   _inputEventHandler() {
-    const value = this.getInputValue();
+    const value = this._getValue();
     if (this.lastInputValue !== value) {
       this.resizeInput();
       this.props.autocompleteUpdate();
       this.lastInputValue = value;
     }
   }
 
   /**
@@ -818,17 +817,17 @@ class JSTerm extends Component {
   /**
    * The inputNode "keypress" event handler.
    *
    * @private
    * @param Event event
    */
   _keyPress(event) {
     const inputNode = this.inputNode;
-    const inputValue = this.getInputValue();
+    const inputValue = this._getValue();
     let inputUpdated = false;
 
     if (event.ctrlKey) {
       switch (event.charCode) {
         case 101:
           // control-e
           if (Services.appinfo.OS == "WINNT") {
             break;
@@ -903,17 +902,17 @@ class JSTerm extends Component {
         }
         this.clearCompletion();
         event.preventDefault();
       }
 
       return;
     } else if (event.keyCode == KeyCodes.DOM_VK_RETURN) {
       if (!this.autocompletePopup.isOpen && (
-        event.shiftKey || !Debugger.isCompilableUnit(this.getInputValue())
+        event.shiftKey || !Debugger.isCompilableUnit(this._getValue())
       )) {
         // shift return or incomplete statement
         return;
       }
     }
 
     switch (event.keyCode) {
       case KeyCodes.DOM_VK_ESCAPE:
@@ -1057,57 +1056,57 @@ class JSTerm extends Component {
       getValueFromHistory,
     } = this.props;
 
     if (!history.entries.length) {
       return false;
     }
 
     const newInputValue = getValueFromHistory(direction);
-    const expression = this.getInputValue();
+    const expression = this._getValue();
     updateHistoryPosition(direction, expression);
 
     if (newInputValue != null) {
-      this.setInputValue(newInputValue);
+      this._setValue(newInputValue);
       return true;
     }
 
     return false;
   }
 
   /**
    * Test for empty input.
    *
    * @return boolean
    */
   hasEmptyInput() {
-    return this.getInputValue() === "";
+    return this._getValue() === "";
   }
 
   /**
    * Test for multiline input.
    *
    * @return boolean
    *         True if CR or LF found in node value; else false.
    */
   hasMultilineInput() {
-    return /[\r\n]/.test(this.getInputValue());
+    return /[\r\n]/.test(this._getValue());
   }
 
   /**
    * Check if the caret is at a location that allows selecting the previous item
    * in history when the user presses the Up arrow key.
    *
    * @return boolean
    *         True if the caret is at a location that allows selecting the
    *         previous item in history when the user presses the Up arrow key,
    *         otherwise false.
    */
   canCaretGoPrevious() {
-    const inputValue = this.getInputValue();
+    const inputValue = this._getValue();
 
     if (this.editor) {
       const {line, ch} = this.editor.getCursor();
       return (line === 0 && ch === 0) || (line === 0 && ch === inputValue.length);
     }
 
     const node = this.inputNode;
     if (node.selectionStart != node.selectionEnd) {
@@ -1124,17 +1123,17 @@ class JSTerm extends Component {
    * history when the user presses the Down arrow key.
    *
    * @return boolean
    *         True if the caret is at a location that allows selecting the next
    *         item in history when the user presses the Down arrow key, otherwise
    *         false.
    */
   canCaretGoNext() {
-    const inputValue = this.getInputValue();
+    const inputValue = this._getValue();
     const multiline = /[\r\n]/.test(inputValue);
 
     if (this.editor) {
       const {line, ch} = this.editor.getCursor();
       return (!multiline && ch === 0) ||
         this.editor.getDoc()
           .getRange({line: 0, ch: 0}, {line, ch})
           .length === inputValue.length;
@@ -1187,17 +1186,17 @@ class JSTerm extends Component {
 
     if (items.length > 0) {
       const {preLabel, label} = items[0];
       let suffix = label.substring(preLabel.length);
       if (isElementAccess) {
         if (!matchProp) {
           suffix = label;
         }
-        const inputAfterCursor = this.getInputValue().substring(inputUntilCursor.length);
+        const inputAfterCursor = this._getValue().substring(inputUntilCursor.length);
         // If there's not a bracket after the cursor, add it to the completionText.
         if (!inputAfterCursor.trimLeft().startsWith("]")) {
           suffix = suffix + "]";
         }
       }
       this.setAutoCompletionText(suffix);
     }
 
@@ -1260,17 +1259,17 @@ class JSTerm extends Component {
       // If the user is performing an element access, we need to check if we should add
       // starting and ending quotes, as well as a closing bracket.
       if (isElementAccess) {
         const inputBeforeCursor = this.getInputValueBeforeCursor();
         if (inputBeforeCursor.trim().endsWith("[")) {
           suffix = label;
         }
 
-        const inputAfterCursor = this.getInputValue().substring(inputBeforeCursor.length);
+        const inputAfterCursor = this._getValue().substring(inputBeforeCursor.length);
         // If there's no closing bracket after the cursor, add it to the completionText.
         if (!inputAfterCursor.trimLeft().startsWith("]")) {
           suffix = suffix + "]";
         }
       }
       this.setAutoCompletionText(suffix);
     } else {
       this.setAutoCompletionText("");
@@ -1323,17 +1322,17 @@ class JSTerm extends Component {
       if (isElementAccess) {
         const inputBeforeCursor = this.getInputValueBeforeCursor();
         const lastOpeningBracketIndex = inputBeforeCursor.lastIndexOf("[");
         if (lastOpeningBracketIndex > -1) {
           numberOfCharsToReplaceCharsBeforeCursor =
             inputBeforeCursor.substring(lastOpeningBracketIndex + 1).length;
         }
 
-        const inputAfterCursor = this.getInputValue().substring(inputBeforeCursor.length);
+        const inputAfterCursor = this._getValue().substring(inputBeforeCursor.length);
         // If there's not a bracket after the cursor, add it.
         if (!inputAfterCursor.trimLeft().startsWith("]")) {
           completionText = completionText + "]";
         }
       }
     }
 
     this.clearCompletion();
@@ -1345,45 +1344,45 @@ class JSTerm extends Component {
 
   getInputValueBeforeCursor() {
     if (this.editor) {
       return this.editor.getDoc().getRange({line: 0, ch: 0}, this.editor.getCursor());
     }
 
     if (this.inputNode) {
       const cursor = this.inputNode.selectionStart;
-      return this.getInputValue().substring(0, cursor);
+      return this._getValue().substring(0, cursor);
     }
 
     return null;
   }
 
   /**
    * Insert a string into the console at the cursor location,
    * moving the cursor to the end of the string.
    *
    * @param {string} str
    * @param {int} numberOfCharsToReplaceCharsBeforeCursor - defaults to 0
    */
   insertStringAtCursor(str, numberOfCharsToReplaceCharsBeforeCursor = 0) {
-    const value = this.getInputValue();
+    const value = this._getValue();
     let prefix = this.getInputValueBeforeCursor();
     const suffix = value.replace(prefix, "");
 
     if (numberOfCharsToReplaceCharsBeforeCursor) {
       prefix =
         prefix.substring(0, prefix.length - numberOfCharsToReplaceCharsBeforeCursor);
     }
 
     // We need to retrieve the cursor before setting the new value.
     const editorCursor = this.editor && this.editor.getCursor();
 
     const scrollPosition = this.inputNode ? this.inputNode.parentElement.scrollTop : null;
 
-    this.setInputValue(prefix + str + suffix);
+    this._setValue(prefix + str + suffix);
 
     if (this.inputNode) {
       const newCursor = prefix.length + str.length;
       this.inputNode.selectionStart = this.inputNode.selectionEnd = newCursor;
       this.inputNode.parentElement.scrollTop = scrollPosition;
     } else if (this.editor) {
       // Set the cursor on the same line it was already at, after the autocompleted text
       this.editor.setCursor({
@@ -1452,17 +1451,17 @@ class JSTerm extends Component {
     if (this.editor) {
       const { ch, line } = this.editor.getCursor();
       const lineContent = this.editor.getLine(line);
       const textAfterCursor = lineContent.substring(ch);
       return textAfterCursor === "";
     }
 
     if (this.inputNode) {
-      const value = this.getInputValue();
+      const value = this._getValue();
       const textAfterCursor = value.substring(this.inputNode.selectionStart);
       return textAfterCursor.split("\n")[0] === "";
     }
 
     return false;
   }
 
   /**
--- a/devtools/client/webconsole/components/ReverseSearchInput.js
+++ b/devtools/client/webconsole/components/ReverseSearchInput.js
@@ -23,43 +23,45 @@ loader.lazyRequireGetter(this, "KeyCodes
 
 const Services = require("Services");
 const isMacOS = Services.appinfo.OS === "Darwin";
 
 class ReverseSearchInput extends Component {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
-      webConsoleUI: PropTypes.object.isRequired,
+      setInputValue: PropTypes.func.isRequired,
+      focusInput: PropTypes.func.isRequired,
+      evaluateInput: PropTypes.func.isRequired,
       reverseSearchResult: PropTypes.string,
       reverseSearchTotalResults: PropTypes.number,
       reverseSearchResultPosition: PropTypes.number,
       visible: PropTypes.bool,
       initialValue: PropTypes.string,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.onInputKeyDown = this.onInputKeyDown.bind(this);
   }
 
   componentDidUpdate(prevProps) {
-    const {jsterm} = this.props.webConsoleUI;
+    const {setInputValue, focusInput} = this.props;
     if (
       prevProps.reverseSearchResult !== this.props.reverseSearchResult
       && this.props.visible
       && this.props.reverseSearchTotalResults > 0
     ) {
-      jsterm.setInputValue(this.props.reverseSearchResult);
+      setInputValue(this.props.reverseSearchResult);
     }
 
     if (prevProps.visible === true && this.props.visible === false) {
-      jsterm.focus();
+      focusInput();
     }
 
     if (
       prevProps.visible === false &&
       this.props.visible === true &&
       this.props.initialValue
     ) {
       this.inputNode.value = this.props.initialValue;
@@ -71,25 +73,25 @@ class ReverseSearchInput extends Compone
       keyCode,
       key,
       ctrlKey,
       shiftKey,
     } = event;
 
     const {
       dispatch,
-      webConsoleUI,
+      evaluateInput,
       reverseSearchTotalResults,
     } = this.props;
 
     // On Enter, we trigger an execute.
     if (keyCode === KeyCodes.DOM_VK_RETURN) {
       event.stopPropagation();
       dispatch(actions.reverseSearchInputToggle());
-      webConsoleUI.jsterm.execute();
+      evaluateInput();
       return;
     }
 
     // On Escape (and Ctrl + c on OSX), we close the reverse search input.
     if (
       keyCode === KeyCodes.DOM_VK_ESCAPE || (
         isMacOS && ctrlKey === true && key.toLowerCase() === "c"
       )
--- a/devtools/client/webconsole/main.js
+++ b/devtools/client/webconsole/main.js
@@ -3,18 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
  /* global BrowserLoader */
 
 "use strict";
 
 const { BrowserLoader } = ChromeUtils.import("resource://devtools/client/shared/browser-loader.js");
 
-this.WebConsoleWrapper = function(parentNode, hud, toolbox, owner, document) {
+this.WebConsoleWrapper = function(parentNode, webConsoleUI, toolbox, document) {
   // Initialize module loader and load all modules of the new inline
   // preview feature. The entire code-base doesn't need any extra
   // privileges and runs entirely in content scope.
   const WebConsoleWrapper = BrowserLoader({
     baseURI: "resource://devtools/client/webconsole/",
     window,
   }).require("./webconsole-wrapper");
-  return new WebConsoleWrapper(parentNode, hud, toolbox, owner, document);
+  return new WebConsoleWrapper(parentNode, webConsoleUI, toolbox, document);
 };
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_add_edited_input_to_history.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_add_edited_input_to_history.js
@@ -16,54 +16,55 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const {jsterm} = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm} = hud;
 
-  ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
-  checkJsTermCursor(jsterm, 0, "Cursor is at expected position");
+  ok(!getInputValue(hud), "console input is empty");
+  checkInputCursorPosition(hud, 0, "Cursor is at expected position");
 
-  jsterm.setInputValue('"first item"');
+  setInputValue(hud, '"first item"');
   EventUtils.synthesizeKey("KEY_ArrowUp");
-  is(jsterm.getInputValue(), '"first item"', "null test history up");
+  is(getInputValue(hud), '"first item"', "null test history up");
   EventUtils.synthesizeKey("KEY_ArrowDown");
-  is(jsterm.getInputValue(), '"first item"', "null test history down");
+  is(getInputValue(hud), '"first item"', "null test history down");
 
   await jsterm.execute();
-  is(jsterm.getInputValue(), "", "cleared input line after submit");
+  is(getInputValue(hud), "", "cleared input line after submit");
 
-  jsterm.setInputValue('"editing input 1"');
+  setInputValue(hud, '"editing input 1"');
   EventUtils.synthesizeKey("KEY_ArrowUp");
-  is(jsterm.getInputValue(), '"first item"', "test history up");
+  is(getInputValue(hud), '"first item"', "test history up");
   EventUtils.synthesizeKey("KEY_ArrowDown");
-  is(jsterm.getInputValue(), '"editing input 1"',
+  is(getInputValue(hud), '"editing input 1"',
     "test history down restores in-progress input");
 
-  jsterm.setInputValue('"second item"');
+  setInputValue(hud, '"second item"');
   await jsterm.execute();
-  jsterm.setInputValue('"editing input 2"');
+  setInputValue(hud, '"editing input 2"');
   EventUtils.synthesizeKey("KEY_ArrowUp");
-  is(jsterm.getInputValue(), '"second item"', "test history up");
+  is(getInputValue(hud), '"second item"', "test history up");
   EventUtils.synthesizeKey("KEY_ArrowUp");
-  is(jsterm.getInputValue(), '"first item"', "test history up");
+  is(getInputValue(hud), '"first item"', "test history up");
   EventUtils.synthesizeKey("KEY_ArrowDown");
-  is(jsterm.getInputValue(), '"second item"', "test history down");
+  is(getInputValue(hud), '"second item"', "test history down");
   EventUtils.synthesizeKey("KEY_ArrowDown");
-  is(jsterm.getInputValue(), '"editing input 2"',
+  is(getInputValue(hud), '"editing input 2"',
      "test history down restores new in-progress input again");
 
   // Appending the same value again should not impact the history.
   // Let's also use some spaces around to check that the input value
   // is properly trimmed.
   await jsterm.execute('"second item"');
   await jsterm.execute('  "second item"    ');
   EventUtils.synthesizeKey("KEY_ArrowUp");
-  is(jsterm.getInputValue(), '"second item"',
+  is(getInputValue(hud), '"second item"',
     "test history up reaches duplicated entry just once");
   EventUtils.synthesizeKey("KEY_ArrowUp");
-  is(jsterm.getInputValue(), '"first item"',
+  is(getInputValue(hud), '"first item"',
     "test history up reaches the previous value");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js
@@ -14,33 +14,34 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
 
   await jsterm.execute("var testObject = {$$aaab: '', $$aaac: ''}");
 
   // Should work with bug 967468.
-  await testAutocomplete(jsterm, "Object.__d");
-  await testAutocomplete(jsterm, "testObject.$$a");
+  await testAutocomplete(hud, "Object.__d");
+  await testAutocomplete(hud, "testObject.$$a");
 
   // Here's when things go wrong in bug 967468.
-  await testAutocomplete(jsterm, "Object.__de");
-  await testAutocomplete(jsterm, "testObject.$$aa");
+  await testAutocomplete(hud, "Object.__de");
+  await testAutocomplete(hud, "testObject.$$aa");
 
   // Should work with bug 1207868.
   await jsterm.execute("let foobar = {a: ''}; const blargh = {a: 1};");
-  await testAutocomplete(jsterm, "foobar");
-  await testAutocomplete(jsterm, "blargh");
-  await testAutocomplete(jsterm, "foobar.a");
-  await testAutocomplete(jsterm, "blargh.a");
+  await testAutocomplete(hud, "foobar");
+  await testAutocomplete(hud, "blargh");
+  await testAutocomplete(hud, "foobar.a");
+  await testAutocomplete(hud, "blargh.a");
 }
 
-async function testAutocomplete(jsterm, inputString) {
-  await setInputValueForAutocompletion(jsterm, inputString);
-  const popup = jsterm.autocompletePopup;
+async function testAutocomplete(hud, inputString) {
+  await setInputValueForAutocompletion(hud, inputString);
+  const popup = hud.jsterm.autocompletePopup;
   ok(popup.itemCount > 0, `There's ${popup.itemCount} suggestions for '${inputString}'`);
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_accept_no_scroll.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_accept_no_scroll.js
@@ -17,36 +17,37 @@ const TEST_URI = `data:text/html;charset
       item1: "value1",
     }));
   </script>`;
 
 add_task(async function() {
   // Only run test with legacy JsTerm.
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
 
-  const { jsterm, ui } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm, ui } = hud;
   const { autocompletePopup: popup } = jsterm;
 
   info("Insert multiple new lines so the input overflows");
   const onPopUpOpen = popup.once("popup-opened");
   const lines = "\n".repeat(200);
-  jsterm.setInputValue(lines);
+  setInputValue(hud, lines);
 
   info("Fire the autocompletion popup");
   EventUtils.sendString("window.foobar.");
 
   await onPopUpOpen;
 
   const inputContainer = ui.window.document.querySelector(".jsterm-input-container");
   ok(inputContainer.scrollTop > 0, "The input overflows");
   const scrollTop = inputContainer.scrollTop;
 
   info("Hit Enter to accept the autocompletion");
   const onPopupClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Enter");
   await onPopupClose;
 
   ok(!popup.isOpen, "popup is not open after KEY_Enter");
-  is(jsterm.getInputValue(), lines + "window.foobar.item0",
+  is(getInputValue(hud), lines + "window.foobar.item0",
     "completion was successful after KEY_Enter");
   is(inputContainer.scrollTop, scrollTop,
     "The scrolling position stayed the same when accepting the completion");
 });
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_array_no_index.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_array_no_index.js
@@ -20,26 +20,25 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
-
+  const hud = await openNewTabAndConsole(TEST_URI);
   const {
     autocompletePopup: popup,
-  } = jsterm;
+  } = hud.jsterm;
 
   const onPopUpOpen = popup.once("popup-opened");
 
   info("wait for popup to show");
-  jsterm.setInputValue("foo");
+  setInputValue(hud, "foo");
   EventUtils.sendString(".");
 
   await onPopUpOpen;
 
   const popupItems = popup.getItems().map(e => e.label);
   is(popupItems.includes("0"), false, "Completing on an array doesn't show numbers.");
 
   info("press Escape to close the popup");
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_arrow_keys.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_arrow_keys.js
@@ -20,78 +20,79 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
   const { autocompletePopup: popup } = jsterm;
 
   const checkInput = (expected, assertionInfo) =>
-    checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
+    checkInputValueAndCursorPosition(hud, expected, assertionInfo);
 
   let onPopUpOpen = popup.once("popup-opened");
-  jsterm.setInputValue("window.foo");
+  setInputValue(hud, "window.foo");
   EventUtils.sendString(".");
   await onPopUpOpen;
 
   info("Trigger autocomplete popup opening");
   // checkInput is asserting the cursor position with the "|" char.
   checkInput("window.foo.|");
   is(popup.isOpen, true, "popup is open");
-  checkJsTermCompletionValue(jsterm, "           aa", "completeNode has expected value");
+  checkInputCompletionValue(hud, "           aa", "completeNode has expected value");
 
   info("Test that arrow left closes the popup and clears complete node");
   let onPopUpClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_ArrowLeft");
   await onPopUpClose;
   checkInput("window.foo|.");
   is(popup.isOpen, false, "popup is closed");
-  checkJsTermCompletionValue(jsterm, "", "completeNode is empty");
+  checkInputCompletionValue(hud, "", "completeNode is empty");
 
   info("Trigger autocomplete popup opening again");
   onPopUpOpen = popup.once("popup-opened");
-  jsterm.setInputValue("window.foo");
+  setInputValue(hud, "window.foo");
   EventUtils.sendString(".");
   await onPopUpOpen;
 
   checkInput("window.foo.|");
   is(popup.isOpen, true, "popup is open");
-  checkJsTermCompletionValue(jsterm, "           aa", "completeNode has expected value");
+  checkInputCompletionValue(hud, "           aa", "completeNode has expected value");
 
   info("Test that arrow right selects selected autocomplete item");
   onPopUpClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_ArrowRight");
   await onPopUpClose;
   checkInput("window.foo.aa|");
   is(popup.isOpen, false, "popup is closed");
-  checkJsTermCompletionValue(jsterm, "", "completeNode is empty");
+  checkInputCompletionValue(hud, "", "completeNode is empty");
 
   info("Test that Ctrl/Cmd + Left removes complete node");
-  await setInputValueForAutocompletion(jsterm, "window.foo.a");
-  const prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-  checkJsTermCompletionValue(jsterm, prefix + "a", "completeNode has expected value");
+  await setInputValueForAutocompletion(hud, "window.foo.a");
+  const prefix = getInputValue(hud).replace(/[\S]/g, " ");
+  checkInputCompletionValue(hud, prefix + "a", "completeNode has expected value");
 
   const isOSX = Services.appinfo.OS == "Darwin";
   EventUtils.synthesizeKey("KEY_ArrowLeft", {
     [isOSX ? "metaKey" : "ctrlKey"]: true,
   });
-  checkJsTermCompletionValue(jsterm, "",
+  checkInputCompletionValue(hud, "",
     "completeNode was cleared after Ctrl/Cmd + left");
 
   info("Test that Ctrl/Cmd + Right closes the popup if there's text after cursor");
-  jsterm.setInputValue(".");
+  setInputValue(hud, ".");
   EventUtils.synthesizeKey("KEY_ArrowLeft");
   onPopUpOpen = popup.once("popup-opened");
   EventUtils.sendString("win");
   await onPopUpOpen;
   ok(popup.isOpen, "popup is open");
 
   onPopUpClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_ArrowRight", {
     [isOSX ? "metaKey" : "ctrlKey"]: true,
   });
   await onPopUpClose;
-  is(jsterm.getInputValue(), "win.", "input value wasn't modified");
+  is(getInputValue(hud), "win.", "input value wasn't modified");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_await.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_await.js
@@ -14,25 +14,26 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
   const { autocompletePopup } = jsterm;
 
   info("Check that the await keyword is in the autocomplete");
-  await setInputValueForAutocompletion(jsterm, "aw");
-  checkJsTermCompletionValue(jsterm, "  ait", "completeNode has expected value");
+  await setInputValueForAutocompletion(hud, "aw");
+  checkInputCompletionValue(hud, "  ait", "completeNode has expected value");
 
   EventUtils.synthesizeKey("KEY_Tab");
-  is(jsterm.getInputValue(), "await", "'await' tab completion");
+  is(getInputValue(hud), "await", "'await' tab completion");
 
   const updated = jsterm.once("autocomplete-updated");
   EventUtils.sendString(" ");
   await updated;
 
   info("Check that the autocomplete popup is displayed");
   const onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.sendString("P");
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_cached_results.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_cached_results.js
@@ -15,26 +15,26 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
   const { autocompletePopup: popup } = jsterm;
 
-  const jstermComplete = (value, caretPosition) =>
-    setInputValueForAutocompletion(jsterm, value, caretPosition);
+  const jstermComplete = (value, pos) => setInputValueForAutocompletion(hud, value, pos);
 
   // Test if 'doc' gives 'document'
   await jstermComplete("doc");
-  is(jsterm.getInputValue(), "doc", "'docu' completion (input.value)");
-  checkJsTermCompletionValue(jsterm, "   ument", "'docu' completion (completeNode)");
+  is(getInputValue(hud), "doc", "'docu' completion (input.value)");
+  checkInputCompletionValue(hud, "   ument", "'docu' completion (completeNode)");
 
   // Test typing 'window.'.'
   await jstermComplete("window.");
   ok(popup.getItems().length > 0, "'window.' gave a list of suggestions");
 
   info("Add a property on the window object");
   await ContentTask.spawn(gBrowser.selectedBrowser, {}, () => {
     content.wrappedJSObject.window.docfoobar = true;
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_commands.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_commands.js
@@ -12,17 +12,18 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
   const { autocompletePopup } = jsterm;
 
   const onPopUpOpen = autocompletePopup.once("popup-opened");
 
   info(`Enter ":"`);
   jsterm.focus();
   EventUtils.sendString(":");
 
@@ -30,31 +31,31 @@ async function performTests() {
 
   const expectedCommands = [":help", ":screenshot"];
   is(getPopupItems(autocompletePopup).join("\n"), expectedCommands.join("\n"),
     "popup contains expected commands");
 
   let onAutocompleUpdated = jsterm.once("autocomplete-updated");
   EventUtils.sendString("s");
   await onAutocompleUpdated;
-  checkJsTermCompletionValue(jsterm, "  creenshot",
+  checkInputCompletionValue(hud, "  creenshot",
     "completion node has expected :screenshot value");
 
   EventUtils.synthesizeKey("KEY_Tab");
-  is(jsterm.getInputValue(), ":screenshot", "Tab key correctly completed :screenshot");
+  is(getInputValue(hud), ":screenshot", "Tab key correctly completed :screenshot");
 
   ok(!autocompletePopup.isOpen, "popup is closed after Tab");
 
   info("Test :hel completion");
-  jsterm.setInputValue(":he");
+  setInputValue(hud, ":he");
   onAutocompleUpdated = jsterm.once("autocomplete-updated");
   EventUtils.sendString("l");
 
   await onAutocompleUpdated;
-  checkJsTermCompletionValue(jsterm, "    p", "completion node has expected :help value");
+  checkInputCompletionValue(hud, "    p", "completion node has expected :help value");
 
   EventUtils.synthesizeKey("KEY_Tab");
-  is(jsterm.getInputValue(), ":help", "Tab key correctly completes :help");
+  is(getInputValue(hud), ":help", "Tab key correctly completes :help");
 }
 
 function getPopupItems(popup) {
   return popup.items.map(item => item.label);
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_control_space.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_control_space.js
@@ -25,25 +25,25 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   // await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
   info("web console opened");
 
-  const { autocompletePopup: popup } = jsterm;
+  const { autocompletePopup: popup } = hud.jsterm;
 
   let onPopUpOpen = popup.once("popup-opened");
 
   info("wait for completion: window.foo.");
-  jsterm.setInputValue("window.foo");
+  setInputValue(hud, "window.foo");
   EventUtils.sendString(".");
 
   await onPopUpOpen;
 
   const {itemCount} = popup;
   ok(popup.isOpen, "popup is open");
   ok(itemCount > 0, "popup has items");
 
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_crossdomain_iframe.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_crossdomain_iframe.js
@@ -24,21 +24,21 @@ async function performTests() {
 
   const onParentTitle = waitForMessage(hud, "iframe parent");
   jsterm.execute("document.title");
   await onParentTitle;
   ok(true, "root document's title is accessible");
 
   // Make sure we don't throw when trying to autocomplete
   const autocompleteUpdated = hud.jsterm.once("autocomplete-updated");
-  jsterm.setInputValue("window[0].document");
+  setInputValue(hud, "window[0].document");
   EventUtils.sendString(".");
   await autocompleteUpdated;
 
-  hud.jsterm.setInputValue("window[0].document.title");
+  setInputValue(hud, "window[0].document.title");
   const onPermissionDeniedMessage = waitForMessage(hud, "Permission denied");
   EventUtils.synthesizeKey("KEY_Enter");
   const permissionDenied = await onPermissionDeniedMessage;
   ok(permissionDenied.node.classList.contains("error"),
     "A message error is shown when trying to inspect window[0]");
 
   const onParentLocation = waitForMessage(hud, "test-iframe-parent.html");
   hud.jsterm.execute("window.location");
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_escape_key.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_escape_key.js
@@ -27,34 +27,35 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
   info("web console opened");
 
   const { autocompletePopup: popup } = jsterm;
 
   const onPopUpOpen = popup.once("popup-opened");
 
   info("wait for completion: window.foo.");
-  jsterm.setInputValue("window.foo");
+  setInputValue(hud, "window.foo");
   EventUtils.sendString(".");
 
   await onPopUpOpen;
 
   ok(popup.isOpen, "popup is open");
   ok(popup.itemCount, "popup has items");
 
   info("press Escape to close the popup");
   const onPopupClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Escape");
 
   await onPopupClose;
 
   ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
-  is(jsterm.getInputValue(), "window.foo.", "completion was cancelled");
-  ok(!getJsTermCompletionValue(jsterm), "completeNode is empty");
+  is(getInputValue(hud), "window.foo.", "completion was cancelled");
+  ok(!getInputCompletionValue(hud), "completeNode is empty");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_extraneous_closing_brackets.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_extraneous_closing_brackets.js
@@ -15,17 +15,17 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
 
   try {
-    await setInputValueForAutocompletion(jsterm, "document.getElementById)");
+    await setInputValueForAutocompletion(hud, "document.getElementById)");
     ok(true, "no error was thrown when an extraneous bracket was inserted");
   } catch (ex) {
     ok(false, "an error was thrown when an extraneous bracket was inserted");
   }
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_getters_cache.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_getters_cache.js
@@ -40,43 +40,43 @@ add_task(async function() {
 
 async function performTests() {
   const hud = await openNewTabAndConsole(TEST_URI);
   const { jsterm } = hud;
   const { autocompletePopup } = jsterm;
   const target = await TargetFactory.forTab(gBrowser.selectedTab);
   const toolbox = gDevTools.getToolbox(target);
 
-  let tooltip = await setInputValueForGetterConfirmDialog(toolbox, jsterm, "foo.bar.");
+  let tooltip = await setInputValueForGetterConfirmDialog(toolbox, hud, "foo.bar.");
   let labelEl = tooltip.querySelector(".confirm-label");
   is(labelEl.textContent, "Invoke getter foo.bar to retrieve the property list?",
     "Dialog has expected text content");
 
   info("Check that hitting Enter does invoke the getter and return its properties");
   let onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.synthesizeKey("KEY_Enter");
   await onPopUpOpen;
   ok(autocompletePopup.isOpen, "popup is open after Enter");
   is(getAutocompletePopupLabels(autocompletePopup).join("-"), "baz-bloop",
     "popup has expected items");
-  checkJsTermValueAndCursor(jsterm, "foo.bar.|");
+  checkInputValueAndCursorPosition(hud, "foo.bar.|");
   is(isConfirmDialogOpened(toolbox), false, "confirm tooltip is now closed");
 
   info("Close autocomplete popup");
   let onPopupClose = autocompletePopup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Escape");
   await onPopupClose;
 
   info("Ctrl+Space again to ensure the autocomplete is shown, not the confirm dialog");
   onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.synthesizeKey(" ", {ctrlKey: true});
   await onPopUpOpen;
   is(getAutocompletePopupLabels(autocompletePopup).join("-"), "baz-bloop",
     "popup has expected items");
-  checkJsTermValueAndCursor(jsterm, "foo.bar.|");
+  checkInputValueAndCursorPosition(hud, "foo.bar.|");
   is(isConfirmDialogOpened(toolbox), false, "confirm tooltip is not open");
 
   info("Type a space, then backspace and ensure the autocomplete popup is displayed");
   let onAutocompleteUpdate = jsterm.once("autocomplete-updated");
   EventUtils.synthesizeKey(" ");
   await onAutocompleteUpdate;
   is(autocompletePopup.isOpen, true, "Autocomplete popup is still opened");
   is(getAutocompletePopupLabels(autocompletePopup).join("-"), "baz-bloop",
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_getters_cancel.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_getters_cancel.js
@@ -27,17 +27,17 @@ add_task(async function() {
 });
 
 async function performTests() {
   const hud = await openNewTabAndConsole(TEST_URI);
   const { jsterm } = hud;
   const target = await TargetFactory.forTab(gBrowser.selectedTab);
   const toolbox = gDevTools.getToolbox(target);
 
-  let tooltip = await setInputValueForGetterConfirmDialog(toolbox, jsterm, "foo.rab.");
+  let tooltip = await setInputValueForGetterConfirmDialog(toolbox, hud, "foo.rab.");
   let labelEl = tooltip.querySelector(".confirm-label");
   is(labelEl.textContent, "Invoke getter foo.rab to retrieve the property list?",
     "Dialog has expected text content");
 
   info("Check that Escape closes the confirm tooltip");
   let onConfirmTooltipClosed = waitFor(() => !isConfirmDialogOpened(toolbox));
   EventUtils.synthesizeKey("KEY_Escape");
   await onConfirmTooltipClosed;
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_getters_confirm.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_getters_confirm.js
@@ -44,36 +44,36 @@ add_task(async function() {
 
 async function performTests() {
   const hud = await openNewTabAndConsole(TEST_URI);
   const { jsterm } = hud;
   const { autocompletePopup } = jsterm;
   const target = await TargetFactory.forTab(gBrowser.selectedTab);
   const toolbox = gDevTools.getToolbox(target);
 
-  let tooltip = await setInputValueForGetterConfirmDialog(toolbox, jsterm,
+  let tooltip = await setInputValueForGetterConfirmDialog(toolbox, hud,
     "window.foo.bar.");
   let labelEl = tooltip.querySelector(".confirm-label");
   is(labelEl.textContent, "Invoke getter window.foo.bar to retrieve the property list?",
     "Dialog has expected text content");
 
   info("Check that hitting Enter does invoke the getter and return its properties");
   let onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.synthesizeKey("KEY_Enter");
   await onPopUpOpen;
   ok(autocompletePopup.isOpen, "popup is open after Enter");
   is(getAutocompletePopupLabels(autocompletePopup).join("-"), "baz-bloop",
     "popup has expected items");
-  checkJsTermValueAndCursor(jsterm, "window.foo.bar.|");
+  checkInputValueAndCursorPosition(hud, "window.foo.bar.|");
   is(isConfirmDialogOpened(toolbox), false, "confirm tooltip is now closed");
 
   let onPopUpClose = autocompletePopup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Enter");
   await onPopUpClose;
-  checkJsTermValueAndCursor(jsterm, "window.foo.bar.baz|");
+  checkInputValueAndCursorPosition(hud, "window.foo.bar.baz|");
 
   info("Check that the invoke tooltip is displayed when performing an element access");
   EventUtils.sendString("[");
   await waitFor(() => isConfirmDialogOpened(toolbox));
 
   tooltip = getConfirmDialog(toolbox);
   labelEl = tooltip.querySelector(".confirm-label");
   is(labelEl.textContent,
@@ -82,57 +82,57 @@ async function performTests() {
 
   info("Check that hitting Tab does invoke the getter and return its properties");
   onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.synthesizeKey("KEY_Tab");
   await onPopUpOpen;
   ok(autocompletePopup.isOpen, "popup is open after Tab");
   is(getAutocompletePopupLabels(autocompletePopup).join("-"), `"hello"-"world"`,
     "popup has expected items");
-  checkJsTermValueAndCursor(jsterm, "window.foo.bar.baz[|");
+  checkInputValueAndCursorPosition(hud, "window.foo.bar.baz[|");
   is(isConfirmDialogOpened(toolbox), false, "confirm tooltip is now closed");
 
   onPopUpClose = autocompletePopup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Tab");
   await onPopUpClose;
-  checkJsTermValueAndCursor(jsterm, `window.foo.bar.baz["hello"]|`);
+  checkInputValueAndCursorPosition(hud, `window.foo.bar.baz["hello"]|`);
 
   info("Check that autocompletion work on a getter result");
   onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.sendString(".");
   await onPopUpOpen;
   ok(autocompletePopup.isOpen, "got items of getter result");
   ok(getAutocompletePopupLabels(autocompletePopup).includes("toExponential"),
     "popup has expected items");
 
-  tooltip = await setInputValueForGetterConfirmDialog(toolbox, jsterm, "window.foo.rab.");
+  tooltip = await setInputValueForGetterConfirmDialog(toolbox, hud, "window.foo.rab.");
   labelEl = tooltip.querySelector(".confirm-label");
   is(labelEl.textContent, "Invoke getter window.foo.rab to retrieve the property list?",
     "Dialog has expected text content");
 
   info("Check clicking the confirm button invokes the getter and return its properties");
   onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.synthesizeMouseAtCenter(tooltip.querySelector(".confirm-button"), {
     type: "mousedown",
   }, toolbox.win);
   await onPopUpOpen;
   ok(autocompletePopup.isOpen, "popup is open after clicking on the confirm button");
   ok(getAutocompletePopupLabels(autocompletePopup).includes("startsWith"),
     "popup has expected items");
-  checkJsTermValueAndCursor(jsterm, "window.foo.rab.|");
+  checkInputValueAndCursorPosition(hud, "window.foo.rab.|");
   is(isConfirmDialogOpened(toolbox), false, "confirm tooltip is now closed");
 
   info("Open the tooltip again");
-  tooltip = await setInputValueForGetterConfirmDialog(toolbox, jsterm, "window.foo.bar.");
+  tooltip = await setInputValueForGetterConfirmDialog(toolbox, hud, "window.foo.bar.");
   labelEl = tooltip.querySelector(".confirm-label");
   is(labelEl.textContent, "Invoke getter window.foo.bar to retrieve the property list?",
     "Dialog has expected text content");
 
   info("Check that Space invokes the getter and return its properties");
   onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.synthesizeKey(" ");
   await onPopUpOpen;
   ok(autocompletePopup.isOpen, "popup is open after space");
   is(getAutocompletePopupLabels(autocompletePopup).join("-"), "baz-bloop",
     "popup has expected items");
-  checkJsTermValueAndCursor(jsterm, "window.foo.bar.|");
+  checkInputValueAndCursorPosition(hud, "window.foo.bar.|");
   is(isConfirmDialogOpened(toolbox), false, "confirm tooltip is now closed");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_getters_learn_more_link.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_getters_learn_more_link.js
@@ -30,21 +30,20 @@ add_task(async function() {
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
   const hud = await openNewTabAndConsole(TEST_URI);
-  const { jsterm } = hud;
   const target = await TargetFactory.forTab(gBrowser.selectedTab);
   const toolbox = gDevTools.getToolbox(target);
 
-  const tooltip = await setInputValueForGetterConfirmDialog(toolbox, jsterm,
+  const tooltip = await setInputValueForGetterConfirmDialog(toolbox, hud,
     "window.foo.bar.");
   const labelEl = tooltip.querySelector(".confirm-label");
   is(labelEl.textContent, "Invoke getter window.foo.bar to retrieve the property list?",
     "Dialog has expected text content");
   const learnMoreEl = tooltip.querySelector(".learn-more-link");
   is(learnMoreEl.textContent, "Learn More", `There's a "Learn more" link`);
 
   info(`Check that clicking on the "Learn more" link navigates to the expected page`);
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_helpers.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_helpers.js
@@ -15,25 +15,26 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const {jsterm} = await openNewTabAndConsole(TEST_URI);
-  await testInspectAutoCompletion(jsterm, "i", true);
-  await testInspectAutoCompletion(jsterm, "window.", false);
-  await testInspectAutoCompletion(jsterm, "dump(i", true);
-  await testInspectAutoCompletion(jsterm, "window.dump(i", true);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  await testInspectAutoCompletion(hud, "i", true);
+  await testInspectAutoCompletion(hud, "window.", false);
+  await testInspectAutoCompletion(hud, "dump(i", true);
+  await testInspectAutoCompletion(hud, "window.dump(i", true);
 }
 
-async function testInspectAutoCompletion(jsterm, inputValue, expectInspect) {
-  jsterm.setInputValue("");
+async function testInspectAutoCompletion(hud, inputValue, expectInspect) {
+  setInputValue(hud, "");
+  const {jsterm} = hud;
   jsterm.focus();
   const updated = jsterm.once("autocomplete-updated");
   EventUtils.sendString(inputValue);
   await updated;
   is(getPopupItemsLabel(jsterm.autocompletePopup).includes("inspect"), expectInspect,
     `autocomplete results${expectInspect ? "" : " does not"} contain helper 'inspect'`);
 }
 
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_in_chrome_tab.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_in_chrome_tab.js
@@ -21,11 +21,11 @@ async function performTests() {
   ok(hud, "we have a console");
   ok(hud.iframeWindow, "we have the console UI window");
 
   const {jsterm} = hud;
   ok(jsterm, "we have a jsterm");
   ok(hud.outputNode, "we have an output node");
 
   // Test typing 'docu'.
-  await setInputValueForAutocompletion(jsterm, "docu");
-  checkJsTermCompletionValue(jsterm, "    ment", "'docu' completion");
+  await setInputValueForAutocompletion(hud, "docu");
+  checkInputCompletionValue(hud, "    ment", "'docu' completion");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_in_debugger_stackframe.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_in_debugger_stackframe.js
@@ -19,25 +19,26 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
   const {
     autocompletePopup: popup,
   } = jsterm;
 
   const target = await TargetFactory.forTab(gBrowser.selectedTab);
   const toolbox = gDevTools.getToolbox(target);
 
-  const jstermComplete = value => setInputValueForAutocompletion(jsterm, value);
+  const jstermComplete = value => setInputValueForAutocompletion(hud, value);
 
   // Test that document.title gives string methods. Native getters must execute.
   await jstermComplete("document.title.");
 
   const newItemsLabels = getPopupLabels(popup);
   ok(newItemsLabels.length > 0, "'document.title.' gave a list of suggestions");
   ok(newItemsLabels.includes("substr"), `results do contain "substr"`);
   ok(newItemsLabels.includes("toLowerCase"), `results do contain "toLowerCase"`);
@@ -45,17 +46,17 @@ async function performTests() {
 
   // Test if 'foo' gives 'foo1' but not 'foo2' or 'foo3'
   await jstermComplete("foo");
   is(getPopupLabels(popup).join("-"), "foo1-foo1Obj",
     `"foo" gave the expected suggestions`);
 
   // Test if 'foo1Obj.' gives 'prop1' and 'prop2'
   await jstermComplete("foo1Obj.");
-  checkJsTermCompletionValue(jsterm, "        prop1", "foo1Obj completion");
+  checkInputCompletionValue(hud, "        prop1", "foo1Obj completion");
   is(getPopupLabels(popup).join("-"), "prop1-prop2",
     `"foo1Obj." gave the expected suggestions`);
 
   // Test if 'foo1Obj.prop2.' gives 'prop21'
   await jstermComplete("foo1Obj.prop2.");
   ok(getPopupLabels(popup).includes("prop21"),
     `"foo1Obj.prop2." gave the expected suggestions`);
 
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_inside_text.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_inside_text.js
@@ -27,125 +27,127 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
   info("web console opened");
 
   const { autocompletePopup: popup } = jsterm;
 
-  await setInitialState(jsterm);
+  await setInitialState(hud);
 
   ok(popup.isOpen, "popup is open");
   is(popup.itemCount, 2, "popup.itemCount is correct");
   is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
-  ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
+  ok(!getInputCompletionValue(hud), "there is no completion text");
 
   info("Pressing arrow right");
   let onPopupClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_ArrowRight");
   await onPopupClose;
   ok(true, "popup was closed");
-  checkJsTermValueAndCursor(jsterm, "dump(window.testB)|", "input wasn't modified");
+  checkInputValueAndCursorPosition(hud, "dump(window.testB)|", "input wasn't modified");
 
-  await setInitialState(jsterm);
+  await setInitialState(hud);
   EventUtils.synthesizeKey("KEY_ArrowDown");
   is(popup.selectedIndex, 1, "popup.selectedIndex is correct");
-  ok(!getJsTermCompletionValue(jsterm), "completeNode.value is empty");
+  ok(!getInputCompletionValue(hud), "completeNode.value is empty");
 
   const items = popup.getItems().map(e => e.label);
   const expectedItems = ["testBugAA", "testBugBB"];
   is(items.join("-"), expectedItems.join("-"), "getItems returns the items we expect");
 
   info("press Tab and wait for popup to hide");
   onPopupClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Tab");
   await onPopupClose;
 
   // At this point the completion suggestion should be accepted.
   ok(!popup.isOpen, "popup is not open");
-  checkJsTermValueAndCursor(jsterm, "dump(window.testBugBB|)",
+  checkInputValueAndCursorPosition(hud, "dump(window.testBugBB|)",
     "completion was successful after VK_TAB");
-  ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
+  ok(!getInputCompletionValue(hud), "there is no completion text");
 
   info("Test ENTER key when popup is visible with a selected item");
-  await setInitialState(jsterm);
+  await setInitialState(hud);
   info("press Enter and wait for popup to hide");
   onPopupClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Enter");
   await onPopupClose;
 
   ok(!popup.isOpen, "popup is not open");
-  checkJsTermValueAndCursor(jsterm, "dump(window.testBugAA|)",
+  checkInputValueAndCursorPosition(hud, "dump(window.testBugAA|)",
     "completion was successful after Enter");
-  ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
+  ok(!getInputCompletionValue(hud), "there is no completion text");
 
   info("Test autocomplete inside parens");
-  jsterm.setInputValue("dump()");
+  setInputValue(hud, "dump()");
   EventUtils.synthesizeKey("KEY_ArrowLeft");
   let onAutocompleteUpdated = jsterm.once("autocomplete-updated");
   EventUtils.sendString("window.testBugA");
   await onAutocompleteUpdated;
   ok(popup.isOpen, "popup is open");
-  ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
+  ok(!getInputCompletionValue(hud), "there is no completion text");
 
   info("Matching the completion proposal should close the popup");
   onPopupClose = popup.once("popup-closed");
   EventUtils.sendString("A");
   await onPopupClose;
 
   info("Test TAB key when there is no autocomplete suggestion");
   ok(!popup.isOpen, "popup is not open");
-  ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
+  ok(!getInputCompletionValue(hud), "there is no completion text");
 
   EventUtils.synthesizeKey("KEY_Tab");
-  checkJsTermValueAndCursor(jsterm, "dump(window.testBugAA\t|)",
+  checkInputValueAndCursorPosition(hud, "dump(window.testBugAA\t|)",
     "completion was successful after Enter");
 
   info("Check that we don't show the popup when editing words");
-  await setInputValueForAutocompletion(jsterm, "estBug", 0);
+  await setInputValueForAutocompletion(hud, "estBug", 0);
   onAutocompleteUpdated = jsterm.once("autocomplete-updated");
   EventUtils.sendString("t");
   await onAutocompleteUpdated;
-  is(jsterm.getInputValue(), "testBug", "jsterm has expected value");
+  is(getInputValue(hud), "testBug", "jsterm has expected value");
   is(popup.isOpen, false, "popup is not open");
-  ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
+  ok(!getInputCompletionValue(hud), "there is no completion text");
 
-  await setInputValueForAutocompletion(jsterm, "__foo", 1);
+  await setInputValueForAutocompletion(hud, "__foo", 1);
   onAutocompleteUpdated = jsterm.once("autocomplete-updated");
   EventUtils.sendString("t");
   await onAutocompleteUpdated;
-  is(jsterm.getInputValue(), "_t_foo", "jsterm has expected value");
+  is(getInputValue(hud), "_t_foo", "jsterm has expected value");
   is(popup.isOpen, false, "popup is not open");
-  ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
+  ok(!getInputCompletionValue(hud), "there is no completion text");
 
-  await setInputValueForAutocompletion(jsterm, "$$bar", 1);
+  await setInputValueForAutocompletion(hud, "$$bar", 1);
   onAutocompleteUpdated = jsterm.once("autocomplete-updated");
   EventUtils.sendString("t");
   await onAutocompleteUpdated;
-  is(jsterm.getInputValue(), "$t$bar", "jsterm has expected value");
+  is(getInputValue(hud), "$t$bar", "jsterm has expected value");
   is(popup.isOpen, false, "popup is not open");
-  ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
+  ok(!getInputCompletionValue(hud), "there is no completion text");
 
-  await setInputValueForAutocompletion(jsterm, "99luftballons", 1);
+  await setInputValueForAutocompletion(hud, "99luftballons", 1);
   onAutocompleteUpdated = jsterm.once("autocomplete-updated");
   EventUtils.sendString("t");
   await onAutocompleteUpdated;
-  is(jsterm.getInputValue(), "9t9luftballons", "jsterm has expected value");
+  is(getInputValue(hud), "9t9luftballons", "jsterm has expected value");
   is(popup.isOpen, false, "popup is not open");
-  ok(!getJsTermCompletionValue(jsterm), "there is no completion text");
+  ok(!getInputCompletionValue(hud), "there is no completion text");
 }
 
-async function setInitialState(jsterm) {
+async function setInitialState(hud) {
+  const {jsterm} = hud;
   jsterm.focus();
-  jsterm.setInputValue("dump()");
+  setInputValue(hud, "dump()");
   EventUtils.synthesizeKey("KEY_ArrowLeft");
 
   const onPopUpOpen = jsterm.autocompletePopup.once("popup-opened");
   EventUtils.sendString("window.testB");
-  checkJsTermValueAndCursor(jsterm, "dump(window.testB|)");
+  checkInputValueAndCursorPosition(hud, "dump(window.testB|)");
   await onPopUpOpen;
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_native_getters.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_native_getters.js
@@ -15,24 +15,25 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm, ui } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm, ui } = hud;
 
   const { autocompletePopup: popup } = jsterm;
 
   ok(!popup.isOpen, "popup is not open");
   const onPopupOpen = popup.once("popup-opened");
 
-  jsterm.setInputValue("document.body");
+  setInputValue(hud, "document.body");
   EventUtils.sendString(".");
 
   await onPopupOpen;
 
   ok(popup.isOpen, "popup is open");
   const cacheMatches = ui.wrapper.getStore().getState().autocomplete.cache.matches;
   is(popup.itemCount, cacheMatches.length, "popup.itemCount is correct");
   ok(cacheMatches.includes("addEventListener"),
@@ -44,20 +45,20 @@ async function performTests() {
   const onPopupClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Escape");
 
   await onPopupClose;
 
   ok(!popup.isOpen, "popup is not open");
   const onAutoCompleteUpdated = jsterm.once("autocomplete-updated");
   const inputStr = "document.b";
-  jsterm.setInputValue(inputStr);
+  setInputValue(hud, inputStr);
   EventUtils.sendString("o");
 
   await onAutoCompleteUpdated;
 
   // Build the spaces that are placed in the input to place the autocompletion result at
   // the expected spot:
   // > document.bo        <-- input
   // > -----------dy      <-- autocomplete
   const spaces = " ".repeat(inputStr.length + 1);
-  checkJsTermCompletionValue(jsterm, spaces + "dy", "autocomplete shows document.body");
+  checkInputCompletionValue(hud, spaces + "dy", "autocomplete shows document.body");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_nav_and_tab_key.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_nav_and_tab_key.js
@@ -28,25 +28,26 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
   info("web console opened");
 
   const { autocompletePopup: popup } = jsterm;
 
   ok(!popup.isOpen, "popup is not open");
 
   const onPopUpOpen = popup.once("popup-opened");
-  jsterm.setInputValue("window.foo");
+  setInputValue(hud, "window.foo");
 
   // Shows the popup
   EventUtils.sendString(".");
   await onPopUpOpen;
 
   ok(popup.isOpen, "popup is open");
 
   const popupItems = popup.getItems().map(e => e.label);
@@ -59,32 +60,32 @@ async function performTests() {
 
   is(popup.itemCount, expectedPopupItems.length, "popup.itemCount is correct");
   is(popupItems.join("-"), expectedPopupItems.join("-"),
     "getItems returns the items we expect");
   is(popup.selectedIndex, 0, "Index of the first item is selected.");
 
   EventUtils.synthesizeKey("KEY_ArrowUp");
 
-  let prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
+  let prefix = getInputValue(hud).replace(/[\S]/g, " ");
   is(popup.selectedIndex, 3, "index 3 is selected");
   is(popup.selectedItem.label, "item3", "item3 is selected");
-  checkJsTermCompletionValue(jsterm, prefix + "item3", "completeNode.value holds item3");
+  checkInputCompletionValue(hud, prefix + "item3", "completeNode.value holds item3");
 
   EventUtils.synthesizeKey("KEY_ArrowUp");
 
   is(popup.selectedIndex, 2, "index 2 is selected");
   is(popup.selectedItem.label, "item2", "item2 is selected");
-  checkJsTermCompletionValue(jsterm, prefix + "item2", "completeNode.value holds item2");
+  checkInputCompletionValue(hud, prefix + "item2", "completeNode.value holds item2");
 
   EventUtils.synthesizeKey("KEY_ArrowDown");
 
   is(popup.selectedIndex, 3, "index 3 is selected");
   is(popup.selectedItem.label, "item3", "item3 is selected");
-  checkJsTermCompletionValue(jsterm, prefix + "item3", "completeNode.value holds item3");
+  checkInputCompletionValue(hud, prefix + "item3", "completeNode.value holds item3");
 
   let currentSelectionIndex = popup.selectedIndex;
 
   EventUtils.synthesizeKey("KEY_PageUp");
   ok(popup.selectedIndex < currentSelectionIndex, "Index is less after Page UP");
 
   currentSelectionIndex = popup.selectedIndex;
   EventUtils.synthesizeKey("KEY_PageDown");
@@ -99,26 +100,26 @@ async function performTests() {
   info("press Tab and wait for popup to hide");
   const onPopupClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Tab");
 
   await onPopupClose;
 
   // At this point the completion suggestion should be accepted.
   ok(!popup.isOpen, "popup is not open");
-  is(jsterm.getInputValue(), "window.foo.item3",
+  is(getInputValue(hud), "window.foo.item3",
      "completion was successful after KEY_Tab");
-  ok(!getJsTermCompletionValue(jsterm), "completeNode is empty");
+  ok(!getInputCompletionValue(hud), "completeNode is empty");
 
   info("Check that hitting Home hides the completion text when the popup is hidden");
-  await setInputValueForAutocompletion(jsterm, "window.foo.item0");
-  prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-  checkJsTermCompletionValue(jsterm, prefix + "0", "completeNode has expected value");
+  await setInputValueForAutocompletion(hud, "window.foo.item0");
+  prefix = getInputValue(hud).replace(/[\S]/g, " ");
+  checkInputCompletionValue(hud, prefix + "0", "completeNode has expected value");
   EventUtils.synthesizeKey("KEY_Home");
-  checkJsTermCompletionValue(jsterm, "", "completeNode was cleared after hitting Home");
+  checkInputCompletionValue(hud, "", "completeNode was cleared after hitting Home");
 
   info("Check that hitting End hides the completion text when the popup is hidden");
-  await setInputValueForAutocompletion(jsterm, "window.foo.item0");
-  prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-  checkJsTermCompletionValue(jsterm, prefix + "0", "completeNode has expected value");
+  await setInputValueForAutocompletion(hud, "window.foo.item0");
+  prefix = getInputValue(hud).replace(/[\S]/g, " ");
+  checkInputCompletionValue(hud, prefix + "0", "completeNode has expected value");
   EventUtils.synthesizeKey("KEY_End");
-  checkJsTermCompletionValue(jsterm, "", "completeNode was cleared after hitting End");
+  checkInputCompletionValue(hud, "", "completeNode was cleared after hitting End");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_paste_undo.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_paste_undo.js
@@ -20,52 +20,52 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const {jsterm, ui} = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm, ui} = hud;
   ui.clearOutput();
-  ok(!getJsTermCompletionValue(jsterm), "no completeNode.value");
+  ok(!getInputCompletionValue(hud), "no completeNode.value");
 
-  jsterm.setInputValue("doc");
+  setInputValue(hud, "doc");
 
   info("wait for completion value after typing 'docu'");
   let onAutocompleteUpdated = jsterm.once("autocomplete-updated");
   EventUtils.sendString("u");
   await onAutocompleteUpdated;
 
-  const completionValue = getJsTermCompletionValue(jsterm);
+  const completionValue = getInputCompletionValue(hud);
 
   info(`Copy "${stringToCopy}" in clipboard`);
   await waitForClipboardPromise(() =>
     clipboardHelper.copyString(stringToCopy), stringToCopy);
 
-  jsterm.setInputValue("docu");
+  setInputValue(hud, "docu");
   info("wait for completion update after clipboard paste");
   onAutocompleteUpdated = jsterm.once("autocomplete-updated");
   EventUtils.synthesizeKey("v", {accelKey: true});
 
   await onAutocompleteUpdated;
 
-  ok(!getJsTermCompletionValue(jsterm), "no completion value after paste");
+  ok(!getInputCompletionValue(hud), "no completion value after paste");
 
   info("wait for completion update after undo");
   onAutocompleteUpdated = jsterm.once("autocomplete-updated");
 
   EventUtils.synthesizeKey("z", {accelKey: true});
 
   await onAutocompleteUpdated;
 
-  checkJsTermCompletionValue(jsterm, completionValue,
-    "same completeNode.value after undo");
+  checkInputCompletionValue(hud, completionValue, "same completeNode.value after undo");
 
   info("wait for completion update after clipboard paste (ctrl-v)");
   onAutocompleteUpdated = jsterm.once("autocomplete-updated");
 
   EventUtils.synthesizeKey("v", {accelKey: true});
 
   await onAutocompleteUpdated;
-  ok(!getJsTermCompletionValue(jsterm), "no completion value after paste (ctrl-v)");
+  ok(!getInputCompletionValue(hud), "no completion value after paste (ctrl-v)");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_return_key.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_return_key.js
@@ -30,24 +30,25 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
   const { autocompletePopup: popup } = jsterm;
 
   let onPopUpOpen = popup.once("popup-opened");
 
   info("wait for completion suggestions: window.foobar.");
 
-  jsterm.setInputValue("window.fooba");
+  setInputValue(hud, "window.fooba");
   EventUtils.sendString("r.");
 
   await onPopUpOpen;
 
   ok(popup.isOpen, "popup is open");
 
   const expectedPopupItems = [
     "item0",
@@ -58,41 +59,40 @@ async function performTests() {
   ];
   is(popup.itemCount, expectedPopupItems.length, "popup.itemCount is correct");
   is(popup.selectedIndex, 0, "First index from top is selected");
 
   EventUtils.synthesizeKey("KEY_ArrowUp");
 
   is(popup.selectedIndex, expectedPopupItems.length - 1, "last index is selected");
   is(popup.selectedItem.label, "item33", "item33 is selected");
-  const prefix = jsterm.getInputValue().replace(/[\S]/g, " ");
-  checkJsTermCompletionValue(jsterm, prefix + "item33",
-    "completeNode.value holds item33");
+  const prefix = getInputValue(hud).replace(/[\S]/g, " ");
+  checkInputCompletionValue(hud, prefix + "item33", "completeNode.value holds item33");
 
   info("press Return to accept suggestion. wait for popup to hide");
   let onPopupClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Enter");
 
   await onPopupClose;
 
   ok(!popup.isOpen, "popup is not open after KEY_Enter");
-  is(jsterm.getInputValue(), "window.foobar.item33",
+  is(getInputValue(hud), "window.foobar.item33",
     "completion was successful after KEY_Enter");
-  ok(!getJsTermCompletionValue(jsterm), "completeNode is empty");
+  ok(!getInputCompletionValue(hud), "completeNode is empty");
 
   info("Test that hitting enter when the completeNode is empty closes the popup");
   onPopUpOpen = popup.once("popup-opened");
   info("wait for completion suggestions: window.foobar.item3");
-  jsterm.setInputValue("window.foobar.item");
+  setInputValue(hud, "window.foobar.item");
   EventUtils.sendString("3");
   await onPopUpOpen;
 
   is(popup.selectedItem.label, "item3", "item3 is selected");
-  ok(!getJsTermCompletionValue(jsterm), "completeNode is empty");
+  ok(!getInputCompletionValue(hud), "completeNode is empty");
 
   onPopupClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Enter");
   await onPopupClose;
 
   ok(!popup.isOpen, "popup is not open after KEY_Enter");
-  is(jsterm.getInputValue(), "window.foobar.item3",
+  is(getInputValue(hud), "window.foobar.item3",
     "completion was successful after KEY_Enter");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_return_key_no_selection.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_return_key_no_selection.js
@@ -25,44 +25,45 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
+  const hud = await openNewTabAndConsole(TEST_URI);
   const {
     jsterm,
     ui,
-  } = await openNewTabAndConsole(TEST_URI);
+  } = hud;
 
   const { autocompletePopup: popup } = jsterm;
 
   const onPopUpOpen = popup.once("popup-opened");
 
   info("wait for popup to show");
-  jsterm.setInputValue("window.testBu");
+  setInputValue(hud, "window.testBu");
   EventUtils.sendString("g");
 
   await onPopUpOpen;
 
   ok(popup.isOpen, "popup is open");
   is(popup.itemCount, 2, "popup.itemCount is correct");
   isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
 
   info("press Return and wait for popup to hide");
   const onPopUpClose = popup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Enter");
   await onPopUpClose;
 
   ok(!popup.isOpen, "popup is not open after KEY_Enter");
-  is(jsterm.getInputValue(), "window.testBugA",
+  is(getInputValue(hud), "window.testBugA",
     "input was completed with the first item of the popup");
-  ok(!getJsTermCompletionValue(jsterm), "completeNode is empty");
+  ok(!getInputCompletionValue(hud), "completeNode is empty");
 
   EventUtils.synthesizeKey("KEY_Enter");
-  is(jsterm.getInputValue(), "", "input is empty after KEY_Enter");
+  is(getInputValue(hud), "", "input is empty after KEY_Enter");
 
   const state = ui.wrapper.getStore().getState();
   const entries = getHistoryEntries(state);
   is(entries[entries.length - 1], "window.testBugA", "jsterm history is correct");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_will_navigate.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_autocomplete_will_navigate.js
@@ -26,33 +26,34 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
   info("web console opened");
 
   const { autocompletePopup: popup } = jsterm;
 
   const onPopUpOpen = popup.once("popup-opened");
 
   info("wait for completion: window.foo.");
-  jsterm.setInputValue("window.foo");
+  setInputValue(hud, "window.foo");
   EventUtils.sendString(".");
 
   await onPopUpOpen;
 
   ok(popup.isOpen, "popup is open");
   ok(popup.itemCount, "popup has items");
 
   info("reload the page to close the popup");
   const onPopupClose = popup.once("popup-closed");
   await refreshTab();
   await onPopupClose;
 
   ok(!popup.isOpen, "popup is not open after reloading the page");
-  is(jsterm.getInputValue(), "window.foo.", "completion was cancelled");
-  ok(!getJsTermCompletionValue(jsterm), "completeNode is empty");
+  is(getInputValue(hud), "window.foo.", "completion was cancelled");
+  ok(!getInputCompletionValue(hud), "completeNode is empty");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_await_helper_dollar_underscore.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_await_helper_dollar_underscore.js
@@ -1,14 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that $_ works as expected with top-level await expressions.
 
 "use strict";
+requestLongerTimeout(2);
 
 const TEST_URI = "data:text/html;charset=utf-8,top-level await + $_";
 
 add_task(async function() {
   // Enable await mapping.
   await pushPref("devtools.debugger.features.map-await-expression", true);
 
   // Run test with legacy JsTerm
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_completion.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_completion.js
@@ -17,88 +17,89 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const {jsterm, ui} = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm, ui} = hud;
   const {autocompletePopup} = jsterm;
 
   // Test typing 'docu'.
-  await setInputValueForAutocompletion(jsterm, "foob");
-  is(jsterm.getInputValue(), "foob", "'foob' completion (input.value)");
-  checkJsTermCompletionValue(jsterm, "    ar", "'foob' completion (completeNode)");
+  await setInputValueForAutocompletion(hud, "foob");
+  is(getInputValue(hud), "foob", "'foob' completion (input.value)");
+  checkInputCompletionValue(hud, "    ar", "'foob' completion (completeNode)");
   is(autocompletePopup.items.length, 1, "autocomplete popup has 1 item");
   is(autocompletePopup.isOpen, false, "autocomplete popup is not open");
 
   // Test typing 'docu' and press tab.
   EventUtils.synthesizeKey("KEY_Tab");
-  is(jsterm.getInputValue(), "foobar", "'foob' tab completion");
+  is(getInputValue(hud), "foobar", "'foob' tab completion");
 
-  checkJsTermCursor(jsterm, "foobar".length, "cursor is at the end of 'foobar'");
-  is(getJsTermCompletionValue(jsterm).replace(/ /g, ""), "", "'foob' completed");
+  checkInputCursorPosition(hud, "foobar".length, "cursor is at the end of 'foobar'");
+  is(getInputCompletionValue(hud).replace(/ /g, ""), "", "'foob' completed");
 
   // Test typing 'window.Ob' and press tab.  Just 'window.O' is
   // ambiguous: could be window.Object, window.Option, etc.
-  await setInputValueForAutocompletion(jsterm, "window.Ob");
+  await setInputValueForAutocompletion(hud, "window.Ob");
   EventUtils.synthesizeKey("KEY_Tab");
-  is(jsterm.getInputValue(), "window.Object", "'window.Ob' tab completion");
+  is(getInputValue(hud), "window.Object", "'window.Ob' tab completion");
 
   // Test typing 'document.getElem'.
   const onPopupOpened = autocompletePopup.once("popup-opened");
-  await setInputValueForAutocompletion(jsterm, "document.getElem");
-  is(jsterm.getInputValue(), "document.getElem", "'document.getElem' completion");
-  checkJsTermCompletionValue(jsterm, "                entById",
+  await setInputValueForAutocompletion(hud, "document.getElem");
+  is(getInputValue(hud), "document.getElem", "'document.getElem' completion");
+  checkInputCompletionValue(hud, "                entById",
      "'document.getElem' completion");
 
   // Test pressing key down.
   await onPopupOpened;
   EventUtils.synthesizeKey("KEY_ArrowDown");
-  is(jsterm.getInputValue(), "document.getElem", "'document.getElem' completion");
-  checkJsTermCompletionValue(jsterm, "                entsByClassName",
+  is(getInputValue(hud), "document.getElem", "'document.getElem' completion");
+  checkInputCompletionValue(hud, "                entsByClassName",
      "'document.getElem' another tab completion");
 
   // Test pressing key up.
   EventUtils.synthesizeKey("KEY_ArrowUp");
-  await waitFor(() => (getJsTermCompletionValue(jsterm) || "").includes("entById"));
-  is(jsterm.getInputValue(), "document.getElem", "'document.getElem' untab completion");
-  checkJsTermCompletionValue(jsterm, "                entById",
+  await waitFor(() => (getInputCompletionValue(hud) || "").includes("entById"));
+  is(getInputValue(hud), "document.getElem", "'document.getElem' untab completion");
+  checkInputCompletionValue(hud, "                entById",
      "'document.getElem' completion");
 
   ui.clearOutput();
 
-  await setInputValueForAutocompletion(jsterm, "docu");
-  checkJsTermCompletionValue(jsterm, "    ment", "'docu' completion");
+  await setInputValueForAutocompletion(hud, "docu");
+  checkInputCompletionValue(hud, "    ment", "'docu' completion");
 
   let onAutocompletUpdated = jsterm.once("autocomplete-updated");
   await jsterm.execute();
-  checkJsTermCompletionValue(jsterm, "", "clear completion on execute()");
+  checkInputCompletionValue(hud, "", "clear completion on execute()");
 
   // Test multi-line completion works. We can't use setInputValueForAutocompletion because
   // it would trigger an evaluation (because of the new line, an Enter keypress is
   // simulated).
   onAutocompletUpdated = jsterm.once("autocomplete-updated");
-  jsterm.setInputValue("console.log('one');\n");
+  setInputValue(hud, "console.log('one');\n");
   EventUtils.sendString("consol");
   await onAutocompletUpdated;
-  checkJsTermCompletionValue(jsterm, "\n      e", "multi-line completion");
+  checkInputCompletionValue(hud, "\n      e", "multi-line completion");
 
   // Test multi-line completion works even if there is text after the cursor
   onAutocompletUpdated = jsterm.once("autocomplete-updated");
-  jsterm.setInputValue("{\n\n}");
+  setInputValue(hud, "{\n\n}");
   EventUtils.synthesizeKey("KEY_ArrowUp");
   EventUtils.sendString("console.g");
   await onAutocompletUpdated;
-  checkJsTermValueAndCursor(jsterm, "{\nconsole.g|\n}");
-  checkJsTermCompletionValue(jsterm, "\n         roup", "multi-line completion");
+  checkInputValueAndCursorPosition(hud, "{\nconsole.g|\n}");
+  checkInputCompletionValue(hud, "\n         roup", "multi-line completion");
   is(autocompletePopup.isOpen, true, "popup is opened");
 
   // Test non-object autocompletion.
-  await setInputValueForAutocompletion(jsterm, "Object.name.sl");
-  checkJsTermCompletionValue(jsterm, "              ice", "non-object completion");
+  await setInputValueForAutocompletion(hud, "Object.name.sl");
+  checkInputCompletionValue(hud, "              ice", "non-object completion");
 
   // Test string literal autocompletion.
-  await setInputValueForAutocompletion(jsterm, "'Asimov'.sl");
-  checkJsTermCompletionValue(jsterm, "           ice", "string literal completion");
+  await setInputValueForAutocompletion(hud, "'Asimov'.sl");
+  checkInputCompletionValue(hud, "           ice", "string literal completion");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_bracket.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_bracket.js
@@ -25,24 +25,24 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const {jsterm} = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
 
-  await testInputs(jsterm);
-  await testCompletionTextUpdateOnPopupNavigate(jsterm);
-  await testAcceptCompletionExistingClosingBracket(jsterm);
+  await testInputs(hud);
+  await testCompletionTextUpdateOnPopupNavigate(hud);
+  await testAcceptCompletionExistingClosingBracket(hud);
 }
 
-async function testInputs(jsterm) {
+async function testInputs(hud) {
   const tests = [{
     description: "Check that the popup is opened when typing `[`",
     input: "window.testObject[",
     expectedItems: [
       `"bar"`,
       `"da'ta'test"`,
       `"da\\"ta\\"test"`,
       `"da\`ta\`test"`,
@@ -120,93 +120,96 @@ async function testInputs(jsterm) {
     expectedItems: [
       `"DATA-TEST"`,
     ],
     expectedCompletionText: `TEST"]`,
     expectedInputAfterCompletion: `window.testObject["DATA-TEST"]`,
   }];
 
   for (const test of tests) {
-    await testInput(jsterm, test);
+    await testInput(hud, test);
   }
 }
 
-async function testInput(jsterm, {
+async function testInput(hud, {
   description,
   input,
   expectedItems,
   expectedCompletionText,
   expectedInputAfterCompletion,
 }) {
+  const {jsterm} = hud;
   const {autocompletePopup} = jsterm;
 
   info(`${description} - test popup opening`);
   const onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.sendString(input);
   await onPopUpOpen;
 
   is(getAutocompletePopupLabels(autocompletePopup).join("|"), expectedItems.join("|"),
     `${description} - popup has expected item, in expected order`);
-  checkJsTermCompletionValue(jsterm, " ".repeat(input.length) + expectedCompletionText,
+  checkInputCompletionValue(hud, " ".repeat(input.length) + expectedCompletionText,
     `${description} - completeNode has expected value`);
 
   info(`${description} - test accepting completion`);
   const onPopupClose = autocompletePopup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Tab");
   await onPopupClose;
-  checkJsTermValueAndCursor(jsterm, expectedInputAfterCompletion + "|",
+  checkInputValueAndCursorPosition(hud, expectedInputAfterCompletion + "|",
     `${description} - input was completed as expected`);
-  checkJsTermCompletionValue(jsterm, "", `${description} - completeNode is empty`);
+  checkInputCompletionValue(hud, "", `${description} - completeNode is empty`);
 
-  jsterm.setInputValue("");
+  setInputValue(hud, "");
 }
 
-async function testCompletionTextUpdateOnPopupNavigate(jsterm) {
+async function testCompletionTextUpdateOnPopupNavigate(hud) {
+  const {jsterm} = hud;
   const {autocompletePopup} = jsterm;
 
   info("Test that navigating the popup list update the completionText as expected");
   const onPopUpOpen = autocompletePopup.once("popup-opened");
   const input = `window.testObject[data`;
   EventUtils.sendString(input);
   await onPopUpOpen;
 
   is(getAutocompletePopupLabels(autocompletePopup).join("|"),
     `"data-test"|"dataTest"|"DATA-TEST"`, `popup has expected items, in expected order`);
-  checkJsTermCompletionValue(jsterm, " ".repeat(input.length) + `-test"]`,
+  checkInputCompletionValue(hud, " ".repeat(input.length) + `-test"]`,
     `completeNode has expected value`);
 
   EventUtils.synthesizeKey("KEY_ArrowDown");
-  checkJsTermCompletionValue(jsterm, " ".repeat(input.length) + `Test"]`,
+  checkInputCompletionValue(hud, " ".repeat(input.length) + `Test"]`,
     `completeNode has expected value`);
 
   EventUtils.synthesizeKey("KEY_ArrowDown");
-  checkJsTermCompletionValue(jsterm, " ".repeat(input.length) + `-TEST"]`,
+  checkInputCompletionValue(hud, " ".repeat(input.length) + `-TEST"]`,
     `completeNode has expected value`);
 
   const onPopupClose = autocompletePopup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Tab");
   await onPopupClose;
-  checkJsTermValueAndCursor(jsterm, `window.testObject["DATA-TEST"]|`,
+  checkInputValueAndCursorPosition(hud, `window.testObject["DATA-TEST"]|`,
     `input was completed as expected after navigating the popup`);
 }
 
-async function testAcceptCompletionExistingClosingBracket(jsterm) {
+async function testAcceptCompletionExistingClosingBracket(hud) {
+  const {jsterm} = hud;
   const {autocompletePopup} = jsterm;
 
   info("Check that accepting completion when there's a closing bracket does not append " +
     "another closing bracket");
-  await setInputValueForAutocompletion(jsterm, "window.testObject[]", -1);
+  await setInputValueForAutocompletion(hud, "window.testObject[]", -1);
   const onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.sendString("b");
   await onPopUpOpen;
   is(getAutocompletePopupLabels(autocompletePopup).join("|"), `"bar"`,
     `popup has expected item`);
 
   const onPopupClose = autocompletePopup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Tab");
   await onPopupClose;
-  checkJsTermValueAndCursor(jsterm, `window.testObject["bar"|]`,
+  checkInputValueAndCursorPosition(hud, `window.testObject["bar"|]`,
     `input was completed as expected, without adding a closing bracket`);
 }
 
 function getAutocompletePopupLabels(autocompletePopup) {
   return autocompletePopup.items.map(i => i.label);
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_bracket_cached_results.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_bracket_cached_results.js
@@ -25,17 +25,18 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const {jsterm} = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm} = hud;
 
   info("Test that the autocomplete cache works with brackets");
   const {autocompletePopup} = jsterm;
 
   const tests = [{
     description: "Test that it works if the user did not type a quote",
     initialInput: `window.testObject[dat`,
     expectedItems: [
@@ -119,32 +120,32 @@ async function performTests() {
     info(test.description);
 
     const onPopUpOpen = autocompletePopup.once("popup-opened");
     EventUtils.sendString(test.initialInput);
     await onPopUpOpen;
 
     is(getAutocompletePopupLabels(autocompletePopup).join("|"),
       test.expectedItems.join("|"), `popup has expected items, in expected order`);
-    checkJsTermCompletionValue(jsterm,
+    checkInputCompletionValue(hud,
       " ".repeat(test.initialInput.length) + test.expectedCompletionText,
       `completeNode has expected value`);
     for (const {char, expectedItems, expectedCompletionText} of test.sequence) {
       const onPopupUpdate = jsterm.once("autocomplete-updated");
       EventUtils.sendString(char);
       await onPopupUpdate;
 
       is(getAutocompletePopupLabels(autocompletePopup).join("|"), expectedItems.join("|"),
         `popup has expected items, in expected order`);
-      checkJsTermCompletionValue(jsterm,
-        " ".repeat(jsterm.getInputValue().length) + expectedCompletionText,
+      checkInputCompletionValue(hud,
+        " ".repeat(getInputValue(hud).length) + expectedCompletionText,
         `completeNode has expected value`);
     }
 
-    jsterm.setInputValue("");
+    setInputValue(hud, "");
     const onPopupClose = autocompletePopup.once("popup-closed");
     EventUtils.synthesizeKey("KEY_Escape");
     await onPopupClose;
   }
 }
 
 function getAutocompletePopupLabels(autocompletePopup) {
   return autocompletePopup.items.map(i => i.label);
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_case_sensitivity.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_case_sensitivity.js
@@ -23,86 +23,87 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const {jsterm} = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm} = hud;
   const {autocompletePopup} = jsterm;
 
   const checkInput = (expected, assertionInfo) =>
-    checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
+    checkInputValueAndCursorPosition(hud, expected, assertionInfo);
 
   info("Check that lowercased input is case-insensitive");
   let onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.sendString("foob");
   await onPopUpOpen;
 
   is(getAutocompletePopupLabels(autocompletePopup).join(" - "), "fooBar - FooBar",
     "popup has expected item, in expected order");
-  checkJsTermCompletionValue(jsterm, "    ar", "completeNode has expected value");
+  checkInputCompletionValue(hud, "    ar", "completeNode has expected value");
 
   info("Check that filtering the autocomplete cache is also case insensitive");
   let onAutoCompleteUpdated = jsterm.once("autocomplete-updated");
   // Send "a" to make the input "fooba"
   EventUtils.sendString("a");
   await onAutoCompleteUpdated;
 
   checkInput("fooba|");
   is(getAutocompletePopupLabels(autocompletePopup).join(" - "), "fooBar - FooBar",
     "popup cache filtering is also case-insensitive");
-  checkJsTermCompletionValue(jsterm, "     r", "completeNode has expected value");
+  checkInputCompletionValue(hud, "     r", "completeNode has expected value");
 
   info("Check that accepting the completion value will change the input casing");
   let onPopupClose = autocompletePopup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Tab");
   await onPopupClose;
   checkInput("fooBar|", "The input was completed with the correct casing");
-  checkJsTermCompletionValue(jsterm, "", "completeNode is empty");
+  checkInputCompletionValue(hud, "", "completeNode is empty");
 
   info("Check that the popup is displayed with only 1 matching item");
   onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.sendString(".f");
   await onPopUpOpen;
 
   // Here we want to match "Foo", and since the completion text will only be "oo", we want
   // to display the popup so the user knows that we are matching "Foo" and not "foo".
   checkInput("fooBar.f|");
   ok(true, "The popup was opened even if there's 1 item matching");
   is(getAutocompletePopupLabels(autocompletePopup).join(" - "), "Foo",
     "popup has expected item");
-  checkJsTermCompletionValue(jsterm, "        oo", "completeNode has expected value");
+  checkInputCompletionValue(hud, "        oo", "completeNode has expected value");
 
   onPopupClose = autocompletePopup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Tab");
   await onPopupClose;
   checkInput("fooBar.Foo|", "The input was completed with the correct casing");
-  checkJsTermCompletionValue(jsterm, "", "completeNode is empty");
+  checkInputCompletionValue(hud, "", "completeNode is empty");
 
-  jsterm.setInputValue("");
+  setInputValue(hud, "");
 
   info("Check that Javascript keywords are displayed first");
   onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.sendString("func");
   await onPopUpOpen;
 
   is(getAutocompletePopupLabels(autocompletePopup).join(" - "), "function - Function",
     "popup has expected item");
-  checkJsTermCompletionValue(jsterm, "    tion", "completeNode has expected value");
+  checkInputCompletionValue(hud, "    tion", "completeNode has expected value");
 
   onPopupClose = autocompletePopup.once("popup-closed");
   EventUtils.synthesizeKey("KEY_Tab");
   await onPopupClose;
   checkInput("function|", "The input was completed as expected");
-  checkJsTermCompletionValue(jsterm, "", "completeNode is empty");
+  checkInputCompletionValue(hud, "", "completeNode is empty");
 
-  jsterm.setInputValue("");
+  setInputValue(hud, "");
 
   info("Check that filtering the cache works like on the server");
   onPopUpOpen = autocompletePopup.once("popup-opened");
   EventUtils.sendString("fooBar.");
   await onPopUpOpen;
   is(getAutocompletePopupLabels(autocompletePopup).join(" - "),
     "test - Foo - TEST - Test", "popup has expected items");
 
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_dollar_underscore.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_dollar_underscore.js
@@ -14,43 +14,44 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const {jsterm} = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm} = hud;
   const {autocompletePopup} = jsterm;
 
   info("Test that there's no issue when trying to do an autocompletion without last " +
     "evaluation result");
-  await setInputValueForAutocompletion(jsterm, "$_.");
+  await setInputValueForAutocompletion(hud, "$_.");
   is(autocompletePopup.items.length, 0, "autocomplete popup has no items");
   is(autocompletePopup.isOpen, false, "autocomplete popup is not open");
 
   info("Populate $_ by executing a command");
   await jsterm.execute(`Object.create(null, Object.getOwnPropertyDescriptors({
     x: 1,
     y: "hello"
   }))`);
 
-  await setInputValueForAutocompletion(jsterm, "$_.");
-  checkJsTermCompletionValue(jsterm, "   x", "'$_.' completion (completeNode)");
+  await setInputValueForAutocompletion(hud, "$_.");
+  checkInputCompletionValue(hud, "   x", "'$_.' completion (completeNode)");
   is(getAutocompletePopupLabels(autocompletePopup).join("|"), "x|y",
     "autocomplete popup has expected items");
   is(autocompletePopup.isOpen, true, "autocomplete popup is open");
 
-  await setInputValueForAutocompletion(jsterm, "$_.x.");
+  await setInputValueForAutocompletion(hud, "$_.x.");
   is(autocompletePopup.isOpen, true, "autocomplete popup is open");
   is(getAutocompletePopupLabels(autocompletePopup).includes("toExponential"), true,
     "autocomplete popup has expected items");
 
-  await setInputValueForAutocompletion(jsterm, "$_.y.");
+  await setInputValueForAutocompletion(hud, "$_.y.");
   is(autocompletePopup.isOpen, true, "autocomplete popup is open");
   is(getAutocompletePopupLabels(autocompletePopup).includes("trim"), true,
     "autocomplete popup has expected items");
 }
 
 function getAutocompletePopupLabels(autocompletePopup) {
   return autocompletePopup.items.map(i => i.label);
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_dollar_zero.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_completion_dollar_zero.js
@@ -36,22 +36,22 @@ async function performTests() {
   info("Picker mode stopped, <h1> selected, now switching to the console");
   const hud = await openConsole();
   const {jsterm} = hud;
 
   hud.ui.clearOutput();
 
   const {autocompletePopup} = jsterm;
 
-  await setInputValueForAutocompletion(jsterm, "$0.");
+  await setInputValueForAutocompletion(hud, "$0.");
   is(getAutocompletePopupLabels(autocompletePopup).includes("attributes"), true,
     "autocomplete popup has expected items");
   is(autocompletePopup.isOpen, true, "autocomplete popup is open");
 
-  await setInputValueForAutocompletion(jsterm, "$0.attributes.");
+  await setInputValueForAutocompletion(hud, "$0.attributes.");
   is(autocompletePopup.isOpen, true, "autocomplete popup is open");
   is(getAutocompletePopupLabels(autocompletePopup).includes("getNamedItem"), true,
     "autocomplete popup has expected items");
 }
 
 function getAutocompletePopupLabels(autocompletePopup) {
   return autocompletePopup.items.map(i => i.label);
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_content_defined_helpers.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_content_defined_helpers.js
@@ -46,17 +46,17 @@ add_task(async function() {
 });
 
 async function performTests() {
   const hud = await openNewTabAndConsole(TEST_URI);
   const {jsterm} = hud;
   const {autocompletePopup} = jsterm;
 
   for (const helper of HELPERS) {
-    await setInputValueForAutocompletion(jsterm, helper);
+    await setInputValueForAutocompletion(hud, helper);
     const autocompleteItems = getPopupLabels(autocompletePopup).filter(l => l === helper);
     is(autocompleteItems.length, 1,
       `There's no duplicated "${helper}" item in the autocomplete popup`);
 
     await executeAndWaitForMessage(
       hud, `${helper}()`, `"${PREFIX + helper}"`, ".result");
     ok(true, `output is correct for ${helper}()`);
   }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_ctrl_a_select_all.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_ctrl_a_select_all.js
@@ -14,30 +14,31 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const {jsterm} = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm} = hud;
 
-  jsterm.setInputValue("Ignore These Four Words");
+  setInputValue(hud, "Ignore These Four Words");
 
   // Test select all with (cmd|control) + a.
   EventUtils.synthesizeKey("a", { accelKey: true });
 
   const inputLength = getSelectionTextLength(jsterm);
-  is(inputLength, jsterm.getInputValue().length, "Select all of input");
+  is(inputLength, getInputValue(hud).length, "Select all of input");
 
   // (cmd|control) + e cannot be disabled on Linux so skip this section on that OS.
   if (Services.appinfo.OS !== "Linux") {
    // Test do nothing on Control + E.
-    jsterm.setInputValue("Ignore These Four Words");
+    setInputValue(hud, "Ignore These Four Words");
     setCursorAtStart(jsterm);
     EventUtils.synthesizeKey("e", { accelKey: true });
     checkSelectionStart(jsterm, 0, "control|cmd + e does not move to end of input");
   }
 }
 
 function getSelectionTextLength(jsterm) {
   if (jsterm.inputNode) {
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_ctrl_key_nav.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_ctrl_key_nav.js
@@ -19,29 +19,29 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
-  const {jsterm} = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
 
-  ok(!jsterm.getInputValue(), "jsterm.getInputValue() is empty");
-  checkJsTermCursor(jsterm, 0, "Cursor is at the start of the input");
+  ok(!getInputValue(hud), "input is empty");
+  checkInputCursorPosition(hud, 0, "Cursor is at the start of the input");
 
-  testSingleLineInputNavNoHistory(jsterm);
-  testMultiLineInputNavNoHistory(jsterm);
-  await testNavWithHistory(jsterm);
+  testSingleLineInputNavNoHistory(hud);
+  testMultiLineInputNavNoHistory(hud);
+  await testNavWithHistory(hud);
 }
 
-function testSingleLineInputNavNoHistory(jsterm) {
+function testSingleLineInputNavNoHistory(hud) {
   const checkInput = (expected, assertionInfo) =>
-    checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
+    checkInputValueAndCursorPosition(hud, expected, assertionInfo);
 
   // Single char input
   EventUtils.sendString("1");
   checkInput("1|", "caret location after single char input");
 
   // nav to start/end with ctrl-a and ctrl-e;
   synthesizeLineStartKey();
   checkInput("|1", "caret location after single char input and ctrl-a");
@@ -80,25 +80,25 @@ function testSingleLineInputNavNoHistory
 
   synthesizeLineUpKey();
   checkInput("|12", "ctrl-p moves to start of line");
 
   synthesizeLineDownKey();
   checkInput("12|", "ctrl-n moves to end of line");
 }
 
-function testMultiLineInputNavNoHistory(jsterm) {
+function testMultiLineInputNavNoHistory(hud) {
   const checkInput = (expected, assertionInfo) =>
-    checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
+    checkInputValueAndCursorPosition(hud, expected, assertionInfo);
 
   const lineValues = ["one", "2", "something longer", "", "", "three!"];
-  jsterm.setInputValue("");
+  setInputValue(hud, "");
   // simulate shift-return
   for (const lineValue of lineValues) {
-    jsterm.setInputValue(jsterm.getInputValue() + lineValue);
+    setInputValue(hud, getInputValue(hud) + lineValue);
     EventUtils.synthesizeKey("KEY_Enter", {shiftKey: true});
   }
 
   checkInput(
 `one
 2
 something longer
 
@@ -201,32 +201,32 @@ three!
 2
 |something longer
 
 
 three!
 `);
 }
 
-async function testNavWithHistory(jsterm) {
+async function testNavWithHistory(hud) {
   const checkInput = (expected, assertionInfo) =>
-    checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
+    checkInputValueAndCursorPosition(hud, expected, assertionInfo);
 
   // NOTE: Tests does NOT currently define behaviour for ctrl-p/ctrl-n with
   // caret placed _within_ single line input
   const values = [
     "single line input",
     "a longer single-line input to check caret repositioning",
     "multi-line\ninput\nhere",
   ];
 
   // submit to history
   for (const value of values) {
-    jsterm.setInputValue(value);
-    await jsterm.execute();
+    setInputValue(hud, value);
+    await hud.jsterm.execute();
   }
 
   checkInput("|", "caret location at start of empty line");
 
   synthesizeLineUpKey();
   checkInput("multi-line\ninput\nhere|", "caret location at end of last history input");
 
   synthesizeLineStartKey();
@@ -259,17 +259,17 @@ async function testNavWithHistory(jsterm
   synthesizeLineDownKey();
   checkInput("multi-line\ninput\nhere|", "caret location at end of last history input");
 
   synthesizeLineDownKey();
   checkInput("|", "ctrl-n at end of history updates to empty input");
 
   // Simulate editing multi-line
   const inputValue = "one\nlinebreak";
-  jsterm.setInputValue(inputValue);
+  setInputValue(hud, inputValue);
   checkInput("one\nlinebreak|");
 
   // Attempt nav within input
   synthesizeLineUpKey();
   checkInput("one|\nlinebreak", "ctrl-p from end of multi-line does not trigger history");
 
   synthesizeLineStartKey();
   checkInput("|one\nlinebreak");
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_focus_reload.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_focus_reload.js
@@ -14,20 +14,19 @@ add_task(async function() {
   await performTests();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
   info("Testing that messages disappear on a refresh if logs aren't persisted");
-  const {jsterm} = await openNewTabAndConsole(TEST_URI);
-  is(isJstermFocused(jsterm), true, "JsTerm is focused when opening the console");
+  const hud = await openNewTabAndConsole(TEST_URI);
+  is(isInputFocused(hud), true, "JsTerm is focused when opening the console");
 
   info("Put the focus on the content page");
   ContentTask.spawn(gBrowser.selectedBrowser, null, () => content.focus());
-  await waitFor(() => isJstermFocused(jsterm) === false);
+  await waitFor(() => isInputFocused(hud) === false);
 
   info("Reload the page to check that JsTerm does not steal the content page focus");
   await refreshTab();
-  is(isJstermFocused(jsterm), false,
-    "JsTerm is still unfocused after reloading the page");
+  is(isInputFocused(hud), false, "JsTerm is still unfocused after reloading the page");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_history.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_history.js
@@ -14,47 +14,48 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", false);
   await testHistory();
 
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await testHistory();
 });
 
 async function testHistory() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm} = hud;
   jsterm.focus();
 
   for (const command of COMMANDS) {
     info(`Executing command ${command}`);
     await jsterm.execute(command);
   }
 
   for (let x = COMMANDS.length - 1; x != -1; x--) {
     EventUtils.synthesizeKey("KEY_ArrowUp");
-    is(jsterm.getInputValue(), COMMANDS[x], "check history previous idx:" + x);
+    is(getInputValue(hud), COMMANDS[x], "check history previous idx:" + x);
   }
 
   EventUtils.synthesizeKey("KEY_ArrowUp");
-  is(jsterm.getInputValue(), COMMANDS[0], "test that item is still index 0");
+  is(getInputValue(hud), COMMANDS[0], "test that item is still index 0");
 
   EventUtils.synthesizeKey("KEY_ArrowUp");
-  is(jsterm.getInputValue(), COMMANDS[0], "test that item is still still index 0");
+  is(getInputValue(hud), COMMANDS[0], "test that item is still still index 0");
 
   for (let i = 1; i < COMMANDS.length; i++) {
     EventUtils.synthesizeKey("KEY_ArrowDown");
-    is(jsterm.getInputValue(), COMMANDS[i], "check history next idx:" + i);
+    is(getInputValue(hud), COMMANDS[i], "check history next idx:" + i);
   }
 
   EventUtils.synthesizeKey("KEY_ArrowDown");
-  is(jsterm.getInputValue(), "", "check input is empty again");
+  is(getInputValue(hud), "", "check input is empty again");
 
   // Simulate pressing Arrow_Down a few times and then if Arrow_Up shows
   // the previous item from history again.
   EventUtils.synthesizeKey("KEY_ArrowDown");
   EventUtils.synthesizeKey("KEY_ArrowDown");
   EventUtils.synthesizeKey("KEY_ArrowDown");
 
-  is(jsterm.getInputValue(), "", "check input is still empty");
+  is(getInputValue(hud), "", "check input is still empty");
 
   const idxLast = COMMANDS.length - 1;
   EventUtils.synthesizeKey("KEY_ArrowUp");
-  is(jsterm.getInputValue(), COMMANDS[idxLast], "check history next idx:" + idxLast);
+  is(getInputValue(hud), COMMANDS[idxLast], "check history next idx:" + idxLast);
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_history_arrow_keys.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_history_arrow_keys.js
@@ -27,24 +27,24 @@ add_task(async function() {
   await performTests();
 });
 
 async function performTests() {
   const hud = await openNewTabAndConsole(TEST_URI);
   const { jsterm } = hud;
 
   const checkInput = (expected, assertionInfo) =>
-    checkJsTermValueAndCursor(jsterm, expected, assertionInfo);
+    checkInputValueAndCursorPosition(hud, expected, assertionInfo);
 
   jsterm.focus();
   checkInput("|", "input is empty");
 
   info("Execute each test value in the console");
   for (const value of TEST_VALUES) {
-    jsterm.setInputValue(value);
+    setInputValue(hud, value);
     await jsterm.execute();
   }
 
   EventUtils.synthesizeKey("KEY_ArrowUp");
   checkInput("document.location|", "↑: input #4 is correct");
   ok(inputHasNoSelection(jsterm));
 
   EventUtils.synthesizeKey("KEY_ArrowUp");
@@ -82,17 +82,17 @@ async function performTests() {
 
   EventUtils.synthesizeKey("KEY_ArrowDown");
   checkInput("document.body|", "↓: input #2 is correct");
 
   EventUtils.synthesizeKey("KEY_ArrowDown");
   checkInput("document;\nwindow;\ndocument.body|", "↓: input #3 is correct");
   ok(inputHasNoSelection(jsterm));
 
-  setCursorAtPosition(jsterm, 2);
+  setCursorAtPosition(hud, 2);
   checkInput("do|cument;\nwindow;\ndocument.body");
 
   EventUtils.synthesizeKey("KEY_ArrowDown");
   EventUtils.synthesizeKey("KEY_ArrowDown");
   checkInput("document;\nwindow;\ndo|cument.body", "↓↓: input #3 is correct");
   ok(inputHasNoSelection(jsterm));
 
   EventUtils.synthesizeKey("KEY_ArrowDown");
@@ -135,24 +135,25 @@ async function performTests() {
     EventUtils.synthesizeKey("KEY_ArrowDown", option);
     checkInput("document.location|", "Cmd+↓ : input is correct");
 
     EventUtils.synthesizeKey("KEY_ArrowDown", option);
     checkInput("|", "Cmd+↓: input is empty");
   }
 }
 
-function setCursorAtPosition(jsterm, pos) {
+function setCursorAtPosition(hud, pos) {
+  const {jsterm} = hud;
   const {inputNode, editor} = jsterm;
 
   if (editor) {
     let line = 0;
     let ch = 0;
     let currentPos = 0;
-    jsterm.getInputValue().split("\n").every(l => {
+    getInputValue(hud).split("\n").every(l => {
       if (l.length < pos - currentPos) {
         line++;
         currentPos += l.length;
         return true;
       }
       ch = pos - currentPos;
       return false;
     });
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_history_nav.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_history_nav.js
@@ -17,17 +17,18 @@ add_task(async function() {
   await testHistory();
 
   // And then in codeMirror JsTerm.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await testHistory();
 });
 
 async function testHistory() {
-  const { jsterm } = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { jsterm } = hud;
   const popup = jsterm.autocompletePopup;
 
   // The autocomplete popup should never be displayed during the test.
   const onShown = function() {
     ok(false, "popup shown");
   };
   popup.on("popup-opened", onShown);
 
@@ -44,14 +45,14 @@ async function testHistory() {
   const onSetInputValue = jsterm.once("set-input-value");
   EventUtils.synthesizeKey("KEY_ArrowUp");
   await onSetInputValue;
 
   // We don't have an explicit event to wait for here, so we just wait for the next tick
   // before checking the popup status.
   await new Promise(executeSoon);
 
-  is(jsterm.getInputValue(), "window.foobarBug660806.location",
+  is(getInputValue(hud), "window.foobarBug660806.location",
     "input has expected value");
 
   ok(!popup.isOpen, "popup is not open");
   popup.off("popup-opened", onShown);
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_history_persist.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_history_persist.js
@@ -66,17 +66,17 @@ async function testHistory() {
   let state3 = hud3.ui.wrapper.getStore().getState();
 
   is(JSON.stringify(getHistoryEntries(state3)),
      '["0","1","2","3","4","5","6","7","8","9"]',
      "Third tab has populated history");
 
   // Set input value separately from execute so UP arrow accurately navigates
   // history.
-  hud3.jsterm.setInputValue('"hello from third tab"');
+  setInputValue(hud3, '"hello from third tab"');
   await hud3.jsterm.execute();
 
   state1 = hud1.ui.wrapper.getStore().getState();
   is(JSON.stringify(getHistoryEntries(state1)),
      '["0","1","2","3","4","5","6","7","8","9"]',
      "First tab history hasn't changed due to command in third tab");
 
   state2 = hud2.ui.wrapper.getStore().getState();
@@ -119,28 +119,28 @@ async function testHistory() {
  * Populate the history by running the following commands:
  *  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  */
 async function populateInputHistory(hud) {
   const jsterm = hud.jsterm;
 
   for (let i = 0; i < INPUT_HISTORY_COUNT; i++) {
     // Set input value separately from execute so UP arrow accurately navigates history.
-    jsterm.setInputValue(i.toString());
+    setInputValue(hud, i.toString());
     await jsterm.execute();
   }
 }
 
 /**
  * Check pressing up results in history traversal like:
  *  [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
  */
 function testNavigatingHistoryInUI(hud) {
   const {jsterm} = hud;
   jsterm.focus();
 
   // Count backwards from original input and make sure that pressing up
   // restores this.
   for (let i = INPUT_HISTORY_COUNT - 1; i >= 0; i--) {
     EventUtils.synthesizeKey("KEY_ArrowUp");
-    is(jsterm.getInputValue(), i, "Pressing up restores last input");
+    is(getInputValue(hud), i, "Pressing up restores last input");
   }
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_middle_click_paste.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_middle_click_paste.js
@@ -28,17 +28,17 @@ async function performTests() {
   info("Set clipboard content");
   const clipboardContent = "test clipboard content";
   setClipboardText(clipboardContent);
 
   info("Middle-click on the console input");
   const node = jsterm.node || jsterm.inputNode;
 
   EventUtils.synthesizeMouse(node, 30, 10, {button: 1}, hud.iframeWindow);
-  is(jsterm.getInputValue(), clipboardContent,
+  is(getInputValue(hud), clipboardContent,
     "clipboard content was pasted in the console input");
 }
 
 function setClipboardText(text) {
   const helper = SpecialPowers.Cc["@mozilla.org/widget/clipboardhelper;1"]
     .getService(SpecialPowers.Ci.nsIClipboardHelper);
   helper.copyString(text);
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_multiline.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_multiline.js
@@ -87,34 +87,35 @@ add_task(async function() {
 async function performTests() {
   // Let's reset the counts.
   Services.telemetry.clearEvents();
 
   // Ensure no events have been logged
   const snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);
   ok(!snapshot.parent, "No events have been logged for the main process");
 
-  const {jsterm} = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm} = hud;
 
   for (const {input, shiftKey} of SHOULD_ENTER_MULTILINE) {
-    jsterm.setInputValue(input);
+    setInputValue(hud, input);
     EventUtils.synthesizeKey("VK_RETURN", { shiftKey });
 
     // We need to remove the spaces at the end of the input since code mirror do some
     // automatic indent in some case.
-    const newValue = jsterm.getInputValue().replace(/ +$/g, "");
+    const newValue = getInputValue(hud).replace(/ +$/g, "");
     is(newValue, input + "\n", "A new line was added");
   }
 
   for (const {input, shiftKey} of SHOULD_EXECUTE) {
-    jsterm.setInputValue(input);
+    setInputValue(hud, input);
     EventUtils.synthesizeKey("VK_RETURN", { shiftKey });
 
-    await waitFor(() => !jsterm.getInputValue());
-    is(jsterm.getInputValue(), "", "Input is cleared");
+    await waitFor(() => !getInputValue(hud));
+    is(getInputValue(hud), "", "Input is cleared");
   }
 
   await jsterm.execute("document.\nlocation.\nhref");
 
   checkEventTelemetry();
 }
 
 function checkEventTelemetry() {
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_no_input_and_tab_key_pressed.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_no_input_and_tab_key_pressed.js
@@ -19,43 +19,43 @@ add_task(async function() {
 });
 
 async function performTests(codeMirror) {
   const hud = await openNewTabAndConsole(TEST_URI);
   const jsterm = hud.jsterm;
 
   info("Check that hitting Tab when input is empty insert blur the input");
   jsterm.focus();
-  jsterm.setInputValue("");
+  setInputValue(hud, "");
   EventUtils.synthesizeKey("KEY_Tab");
-  is(jsterm.getInputValue(), "", "inputnode is empty - matched");
-  ok(!isJstermFocused(jsterm), "input isn't focused anymore");
+  is(getInputValue(hud), "", "inputnode is empty - matched");
+  ok(!isInputFocused(hud), "input isn't focused anymore");
 
   info("Check that hitting Shift+Tab when input is not empty insert a tab");
   jsterm.focus();
   EventUtils.synthesizeKey("KEY_Tab", {shiftKey: true});
-  is(jsterm.getInputValue(), "", "inputnode is empty - matched");
-  ok(!isJstermFocused(jsterm), "input isn't focused anymore");
+  is(getInputValue(hud), "", "inputnode is empty - matched");
+  ok(!isInputFocused(hud), "input isn't focused anymore");
   ok(hasFocus(hud.ui.outputNode.querySelector(".devtools-button.net")),
     `The "Requests" filter button is now focused`);
 
   info("Check that hitting Tab when input is not empty insert a tab");
   jsterm.focus();
 
   const testString = "window.Bug583816";
-  await setInputValueForAutocompletion(jsterm, testString, 0);
-  checkJsTermValueAndCursor(jsterm, `|${testString}`,
+  await setInputValueForAutocompletion(hud, testString, 0);
+  checkInputValueAndCursorPosition(hud, `|${testString}`,
     "cursor is at the start of the input");
 
   EventUtils.synthesizeKey("KEY_Tab");
-  checkJsTermValueAndCursor(jsterm, `\t|${testString}`,
+  checkInputValueAndCursorPosition(hud, `\t|${testString}`,
     "a tab char was added at the start of the input after hitting Tab");
-  ok(isJstermFocused(jsterm), "input is still focused");
+  ok(isInputFocused(hud), "input is still focused");
 
   // We only check the 'unindent' feature for CodeMirror JsTerm.
   if (codeMirror) {
     info("Check that hitting Shift+Tab when input is not empty removed leading tabs");
     EventUtils.synthesizeKey("KEY_Tab", {shiftKey: true});
-    checkJsTermValueAndCursor(jsterm, `|${testString}`,
+    checkInputValueAndCursorPosition(hud, `|${testString}`,
       "The tab char at the the start of the input was removed after hitting Shift+Tab");
-    ok(isJstermFocused(jsterm), "input is still focused");
+    ok(isInputFocused(hud), "input is still focused");
   }
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_popup_close_on_tab_switch.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_popup_close_on_tab_switch.js
@@ -20,17 +20,17 @@ add_task(async function() {
   await performTests();
 });
 
 async function performTests() {
   const hud = await openNewTabAndConsole(TEST_URI);
   const popup = hud.jsterm.autocompletePopup;
   const popupShown = once(popup, "popup-opened");
 
-  hud.jsterm.setInputValue("sc");
+  setInputValue(hud, "sc");
   EventUtils.sendString("r");
 
   await popupShown;
 
   await addTab(TEST_URI_NAVIGATE);
 
   ok(!popup.isOpen, "Popup closes on tab switch");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_selfxss.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_selfxss.js
@@ -23,44 +23,45 @@ add_task(async function() {
   await performTest();
   // And then run it with the CodeMirror-powered one.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTest();
 });
 
 async function performTest() {
   await pushPref("devtools.selfxss.count", 0);
-  const {jsterm, ui} = await openNewTabAndConsole(TEST_URI);
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const {jsterm, ui} = hud;
   const {document} = ui;
 
   info("Self-xss paste tests");
   WebConsoleUtils.usageCount = 0;
   is(WebConsoleUtils.usageCount, 0, "Test for usage count getter");
 
   // Input some commands to check if usage counting is working
   for (let i = 0; i <= 3; i++) {
-    jsterm.setInputValue(i.toString());
+    setInputValue(hud, i.toString());
     jsterm.execute();
   }
   is(WebConsoleUtils.usageCount, 4, "Usage count incremented");
   WebConsoleUtils.usageCount = 0;
 
   info(`Copy "${stringToCopy}" in clipboard`);
   await waitForClipboardPromise(() =>
     clipboardHelper.copyString(stringToCopy), stringToCopy);
   goDoCommand("cmd_paste");
 
   const notificationbox = document.getElementById("webconsole-notificationbox");
   const notification = notificationbox.querySelector(".notification");
   is(notification.getAttribute("data-key"), "selfxss-notification",
     "Self-xss notification shown");
-  is(jsterm.getInputValue(), "", "Paste blocked by self-xss prevention");
+  is(getInputValue(hud), "", "Paste blocked by self-xss prevention");
 
   // Allow pasting
   const allowToken = "allow pasting";
   for (const char of allowToken) {
     EventUtils.sendString(char);
   }
 
-  jsterm.setInputValue("");
+  setInputValue(hud, "");
   goDoCommand("cmd_paste");
-  is(jsterm.getInputValue(), stringToCopy, "Paste works");
+  is(getInputValue(hud), stringToCopy, "Paste works");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_close_sidebar.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_close_sidebar.js
@@ -62,17 +62,17 @@ add_task(async function() {
   await showSidebar(hud);
 
   info("Send escape to hide sidebar");
   onSidebarShown = waitForNodeMutation(appNode, { childList: true });
   EventUtils.synthesizeKey("KEY_Escape");
   await onSidebarShown;
   sidebar = hud.ui.document.querySelector(".sidebar");
   ok(!sidebar, "Sidebar hidden after sending esc");
-  ok(isJstermFocused(hud.jsterm), "console input is focused after closing the sidebar");
+  ok(isInputFocused(hud), "console input is focused after closing the sidebar");
 });
 
 async function showSidebar(hud) {
   const onMessage = waitForMessage(hud, "Object");
   ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
     content.wrappedJSObject.console.log({a: 1});
   });
   await onMessage;
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_closing_after_completion.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_closing_after_completion.js
@@ -12,17 +12,17 @@ const TEST_URI = "http://example.com/bro
                  "test/mochitest/test-console.html";
 
 add_task(async function() {
   const tab = await addTab(TEST_URI);
   const browser = tab.linkedBrowser;
   const hud = await openConsole();
 
   // Fire a completion.
-  await setInputValueForAutocompletion(hud.jsterm, "doc");
+  await setInputValueForAutocompletion(hud, "doc");
 
   let errorWhileClosing = false;
   function errorListener() {
     errorWhileClosing = true;
   }
 
   browser.addEventListener("error", errorListener);
   const onToolboxDestroyed = gDevTools.once("toolbox-destroyed");
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_context_menu_store_as_global.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_context_menu_store_as_global.js
@@ -79,13 +79,13 @@ async function storeAsVariable(hud, msg,
   storeMenuItem.click();
 
   info("Wait for console input to be updated with the temp variable");
   await onceInputSet;
 
   info("Wait for context menu to be hidden");
   await hideContextMenu(hud);
 
-  is(hud.jsterm.getInputValue(), "temp" + varIdx, "Input was set");
+  is(getInputValue(hud), "temp" + varIdx, "Input was set");
 
   const equal = await hud.jsterm.requestEvaluation("temp" + varIdx + " === " + equalTo);
   is(equal.result, true, "Correct variable assigned into console.");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_document_focus.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_document_focus.js
@@ -5,17 +5,17 @@
 
 // Check that focus is restored to content page after closing the console. See Bug 588342.
 const TEST_URI = "data:text/html;charset=utf-8,Test content focus after closing console";
 
 add_task(async function() {
   const hud = await openNewTabAndConsole(TEST_URI);
 
   info("Focus after console is opened");
-  ok(isJstermFocused(hud.jsterm), "input node is focused after console is opened");
+  ok(isInputFocused(hud), "input node is focused after console is opened");
 
   info("Closing console");
   await closeConsole();
   const isFocused = await ContentTask.spawn(gBrowser.selectedBrowser, { }, function() {
     return Services.focus.focusedWindow == content;
   });
   ok(isFocused, "content document has focus after closing the console");
 });
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_in_line_layout.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_in_line_layout.js
@@ -19,17 +19,17 @@ add_task(async function() {
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   await performTests();
 });
 
 async function performTests() {
   // The style is only enabled in the new jsterm.
   await pushPref("devtools.webconsole.jsterm.codeMirror", true);
   const hud = await openNewTabAndConsole(TEST_URI);
-  const {jsterm, ui} = hud;
+  const {ui} = hud;
   const {document} = ui;
   const appNode = document.querySelector(".webconsole-app");
   const [
     filterBarNode,
     outputNode,
     ,
     inputNode,
   ] = appNode.querySelector(".webconsole-flex-wrapper").childNodes;
@@ -57,17 +57,17 @@ async function performTests() {
       content.wrappedJSObject.console.log("message-" + i);
     }
   });
   await onLastMessage;
   ok(outputNode.scrollHeight > outputNode.clientHeight, "Output node overflows");
   testLayout(appNode);
 
   info("Make sure setting a tall value in the input does not break the layout");
-  jsterm.setInputValue("multiline\n".repeat(200));
+  setInputValue(hud, "multiline\n".repeat(200));
   is(outputNode.clientHeight, MINIMUM_MESSAGE_HEIGHT,
     "One message is still visible in the output node");
   testLayout(appNode);
 
   const filterBarHeight = filterBarNode.clientHeight;
 
   info("Show the hidden messages label");
   const onHiddenMessagesLabelVisible = waitFor(() =>
@@ -84,17 +84,17 @@ async function performTests() {
 
   ok(filterBarNode.clientHeight > filterBarHeight, "The filter bar is taller");
   testLayout(appNode);
 
   info("Expand the window so hidden label isn't on its own line anymore");
   hostWindow.resizeTo(window.screen.availWidth, window.screen.availHeight);
   testLayout(appNode);
 
-  jsterm.setInputValue("");
+  setInputValue(hud, "");
   testLayout(appNode);
 
   ui.clearOutput();
   testLayout(appNode);
   is(outputNode.offsetHeight, 0, "output node has no height");
   is(filterBarNode.offsetHeight + inputNode.offsetHeight, appNode.offsetHeight,
     "The entire height is taken by filter bar and input");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_input_field_focus_on_panel_select.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_input_field_focus_on_panel_select.js
@@ -11,23 +11,23 @@
 const TEST_URI = "data:text/html;charset=utf8,<p>Test console input focus";
 
 add_task(async function() {
   const hud = await openNewTabAndConsole(TEST_URI);
 
   const filterInput = hud.ui.outputNode.querySelector(".text-filter");
 
   info("Focus after console is opened");
-  ok(isJstermFocused(hud.jsterm), "jsterm is focused after console is opened");
+  ok(isInputFocused(hud), "input is focused after console is opened");
 
   filterInput.focus();
   ok(hasFocus(filterInput), "filter input should be focused");
 
-  is(isJstermFocused(hud.jsterm), false, "input node is not focused anymore");
+  is(isInputFocused(hud), false, "input node is not focused anymore");
 
   info("Go to the inspector panel");
   await openInspector();
 
   info("Go back to the console");
   await openConsole();
 
-  ok(isJstermFocused(hud.jsterm), "jsterm is focused when coming from a different panel");
+  ok(isInputFocused(hud), "input is focused when coming from a different panel");
 });
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_input_focus.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_input_focus.js
@@ -12,46 +12,46 @@ const TEST_URI =
   <script>
     console.log("console message 1");
   </script>`;
 
 add_task(async function() {
   const hud = await openNewTabAndConsole(TEST_URI);
 
   info("Focus after console is opened");
-  ok(isJstermFocused(hud.jsterm), "input node is focused after console is opened");
+  ok(isInputFocused(hud), "input node is focused after console is opened");
 
   hud.ui.clearOutput();
-  ok(isJstermFocused(hud.jsterm), "input node is focused after output is cleared");
+  ok(isInputFocused(hud), "input node is focused after output is cleared");
 
   info("Focus during message logging");
   ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
     content.wrappedJSObject.console.log("console message 2");
   });
   const msg = await waitFor(() => findMessage(hud, "console message 2"));
-  ok(isJstermFocused(hud.jsterm), "input node is focused, first time");
+  ok(isInputFocused(hud), "input node is focused, first time");
 
   info("Focus after clicking in the output area");
   await waitForBlurredInput(hud);
   EventUtils.sendMouseEvent({type: "click"}, msg);
-  ok(isJstermFocused(hud.jsterm), "input node is focused, second time");
+  ok(isInputFocused(hud), "input node is focused, second time");
 
   info("Setting a text selection and making sure a click does not re-focus");
   await waitForBlurredInput(hud);
   const selection = hud.iframeWindow.getSelection();
   selection.selectAllChildren(msg.querySelector(".message-body"));
   EventUtils.sendMouseEvent({type: "click"}, msg);
-  ok(!isJstermFocused(hud.jsterm), "input node not focused after text is selected");
+  ok(!isInputFocused(hud), "input node not focused after text is selected");
 });
 
 function waitForBlurredInput(hud) {
   const node = hud.jsterm.node || hud.jsterm.inputNode;
   return new Promise(resolve => {
     const lostFocus = () => {
-      ok(!isJstermFocused(hud.jsterm), "input node is not focused");
+      ok(!isInputFocused(hud), "input node is not focused");
       resolve();
     };
     node.addEventListener("focusout", lostFocus, { once: true });
 
     // The 'focusout' event fires if we focus e.g. the filter box.
     node.ownerDocument.querySelector("input.text-filter").focus();
   });
 }
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_keyboard_accessibility.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_keyboard_accessibility.js
@@ -59,22 +59,22 @@ async function performTests() {
   let clearShortcut;
   if (Services.appinfo.OS === "Darwin") {
     clearShortcut = WCUL10n.getStr("webconsole.clear.keyOSX");
   } else {
     clearShortcut = WCUL10n.getStr("webconsole.clear.key");
   }
   synthesizeKeyShortcut(clearShortcut);
   await waitFor(() => findMessages(hud, "").length == 0);
-  ok(isJstermFocused(hud.jsterm), "jsterm input is focused");
+  ok(isInputFocused(hud), "input is focused");
 
   // Focus filter
   info("try ctrl-f to focus filter");
   synthesizeKeyShortcut(WCUL10n.getStr("webconsole.find.key"));
-  ok(!isJstermFocused(hud.jsterm), "jsterm input is not focused");
+  ok(!isInputFocused(hud), "input is not focused");
   ok(hasFocus(hud.ui.filterBox), "filter input is focused");
 
   info("try ctrl-f when filter is already focused");
   synthesizeKeyShortcut(WCUL10n.getStr("webconsole.find.key"));
-  ok(!isJstermFocused(hud.jsterm), "jsterm input is not focused");
+  ok(!isInputFocused(hud), "input is not focused");
   is(hud.ui.filterBox, outputScroller.ownerDocument.activeElement,
     "filter input is focused");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search.js
@@ -23,30 +23,30 @@ add_task(async function() {
   ];
 
   const onLastMessage = waitForMessage(hud, `"😎"`);
   for (const input of jstermHistory) {
     await hud.jsterm.execute(input);
   }
   await onLastMessage;
 
-  const jstermInitialValue = "initialValue";
-  hud.jsterm.setInputValue(jstermInitialValue);
+  const initialValue = "initialValue";
+  setInputValue(hud, initialValue);
 
   info("Check that the reverse search toolbar as the expected initial state");
   let reverseSearchElement = await openReverseSearch(hud);
   ok(reverseSearchElement, "Reverse search is displayed with a keyboard shortcut");
   ok(!getReverseSearchInfoElement(hud),
     "The result info element is not displayed by default");
   ok(
     !reverseSearchElement.querySelector(".search-result-button-prev") &&
     !reverseSearchElement.querySelector(".search-result-button-next"),
     "The results navigation buttons are not displayed by default"
   );
-  is(hud.jsterm.getInputValue(), jstermInitialValue,
+  is(getInputValue(hud), initialValue,
     "The jsterm value is not changed when opening reverse search");
   is(isReverseSearchInputFocused(hud), true, "reverse search input is focused");
 
   EventUtils.sendString("d");
   let infoElement = await waitFor(() => getReverseSearchInfoElement(hud));
   is(infoElement.textContent, "3 of 3 results", "The reverse info has the expected text "
     + "— duplicated results (`document`) are coalesced");
 
@@ -54,57 +54,57 @@ add_task(async function() {
   const nextButton = reverseSearchElement.querySelector(".search-result-button-next");
   ok(previousButton, "Previous navigation button is now displayed");
   is(previousButton.title, `Previous result (${isMacOS ? "Ctrl + R" : "F9"})`,
     "Previous navigation button has expected title");
 
   ok(nextButton, "Next navigation button is now displayed");
   is(nextButton.title, `Next result (${isMacOS ? "Ctrl + S" : "Shift + F9"})`,
     "Next navigation button has expected title");
-  is(hud.jsterm.getInputValue(), "document", "JsTerm has the expected input");
+  is(getInputValue(hud), "document", "JsTerm has the expected input");
   is(hud.jsterm.autocompletePopup.isOpen, false,
     "Setting the input value did not trigger the autocompletion");
   is(isReverseSearchInputFocused(hud), true, "reverse search input is focused");
 
   let onJsTermValueChanged = hud.jsterm.once("set-input-value");
   EventUtils.sendString("og");
   await onJsTermValueChanged;
-  is(hud.jsterm.getInputValue(), `Dog = "Snoopy"`, "JsTerm input was updated");
+  is(getInputValue(hud), `Dog = "Snoopy"`, "JsTerm input was updated");
   is(infoElement.textContent, "1 result", "The reverse info has the expected text");
   ok(
     !reverseSearchElement.querySelector(".search-result-button-prev") &&
     !reverseSearchElement.querySelector(".search-result-button-next"),
     "The results navigation buttons are not displayed when there's only one result"
   );
 
   info("Check that the UI and results are updated when typing in the input");
   onJsTermValueChanged = hud.jsterm.once("set-input-value");
   EventUtils.sendString("g");
   await waitFor(() => reverseSearchElement.classList.contains("no-result"));
-  is(hud.jsterm.getInputValue(), `Dog = "Snoopy"`,
+  is(getInputValue(hud), `Dog = "Snoopy"`,
     "JsTerm input was not updated since there's no results");
   is(infoElement.textContent, "No results", "The reverse info has the expected text");
   ok(
     !reverseSearchElement.querySelector(".search-result-button-prev") &&
     !reverseSearchElement.querySelector(".search-result-button-next"),
     "The results navigation buttons are not displayed when there's no result"
   );
 
   info("Check that Backspace updates the UI");
   EventUtils.synthesizeKey("KEY_Backspace");
   await waitFor(() => !reverseSearchElement.classList.contains("no-result"));
   is(infoElement.textContent, "1 result", "The reverse info has the expected text");
-  is(hud.jsterm.getInputValue(), `Dog = "Snoopy"`, "JsTerm kept its value");
+  is(getInputValue(hud), `Dog = "Snoopy"`, "JsTerm kept its value");
 
   info("Check that Escape does not affect the jsterm value");
   EventUtils.synthesizeKey("KEY_Escape");
   await waitFor(() => !getReverseSearchElement(hud));
-  is(hud.jsterm.getInputValue(), `Dog = "Snoopy"`,
+  is(getInputValue(hud), `Dog = "Snoopy"`,
     "Closing the input did not changed the JsTerm value");
-  is(isJstermFocused(hud.jsterm), true, "JsTerm is focused");
+  is(isInputFocused(hud), true, "input is focused");
 
   info("Check that the search works with emojis");
   reverseSearchElement = await openReverseSearch(hud);
   onJsTermValueChanged = hud.jsterm.once("set-input-value");
   EventUtils.sendString("😎");
   infoElement = await waitFor(() => getReverseSearchInfoElement(hud));
   is(infoElement.textContent, "1 result", "The reverse info has the expected text");
 
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_initial_value.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_initial_value.js
@@ -31,18 +31,17 @@ async function performTests() {
   ];
 
   const onLastMessage = waitForMessage(hud, `"😎"`);
   for (const input of jstermHistory) {
     await jsterm.execute(input);
   }
   await onLastMessage;
 
-  const jstermInitialValue = "ado";
-  jsterm.setInputValue(jstermInitialValue);
+  setInputValue(hud, "ado");
 
   info(`Select 2 chars ("do") from the input`);
   if (jsterm.inputNode) {
     jsterm.inputNode.selectionStart = 1;
     jsterm.inputNode.selectionEnd = 3;
   } else {
     jsterm.editor.setSelection({line: 0, ch: 1}, {line: 0, ch: 3});
   }
@@ -56,24 +55,24 @@ async function performTests() {
   const infoElement = getReverseSearchInfoElement(hud);
   is(infoElement.textContent, "3 of 3 results", "The reverse info has the expected text");
 
   const previousButton = reverseSearchElement.querySelector(".search-result-button-prev");
   const nextButton = reverseSearchElement.querySelector(".search-result-button-next");
   ok(previousButton, "Previous navigation button is displayed");
   ok(nextButton, "Next navigation button is displayed");
 
-  is(jsterm.getInputValue(), "document", "JsTerm has the expected input");
+  is(getInputValue(hud), "document", "JsTerm has the expected input");
   is(jsterm.autocompletePopup.isOpen, false,
     "Setting the input value did not trigger the autocompletion");
 
   const onJsTermValueChanged = jsterm.once("set-input-value");
   EventUtils.sendString("g");
   await onJsTermValueChanged;
-  is(jsterm.getInputValue(), `Dog = "Snoopy"`, "JsTerm input was updated");
+  is(getInputValue(hud), `Dog = "Snoopy"`, "JsTerm input was updated");
   is(infoElement.textContent, "1 result", "The reverse info has the expected text");
   ok(
     !reverseSearchElement.querySelector(".search-result-button-prev") &&
     !reverseSearchElement.querySelector(".search-result-button-next"),
     "The results navigation buttons are not displayed when there's only one result"
   );
 
   info("Check that there's no initial value when no text is selected");
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_keyboard_navigation.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_keyboard_navigation.js
@@ -26,17 +26,17 @@ add_task(async function() {
   }
   await onLastMessage;
 
   await openReverseSearch(hud);
   EventUtils.sendString("d");
   const infoElement = await waitFor(() => getReverseSearchInfoElement(hud));
   is(infoElement.textContent, "3 of 3 results", "The reverse info has the expected text");
 
-  is(hud.jsterm.getInputValue(), jstermHistory[2], "JsTerm has the expected input");
+  is(getInputValue(hud), jstermHistory[2], "JsTerm has the expected input");
   is(hud.jsterm.autocompletePopup.isOpen, false,
     "Setting the input value did not trigger the autocompletion");
 
   await navigateResultsAndCheckState(hud, {
     direction: "previous",
     expectedInfoText: "2 of 3 results",
     expectedJsTermInputValue: jstermHistory[1],
   });
@@ -93,17 +93,17 @@ async function navigateResultsAndCheckSt
   const onJsTermValueChanged = hud.jsterm.once("set-input-value");
   if (direction === "previous") {
     triggerPreviousResultShortcut();
   } else {
     triggerNextResultShortcut();
   }
   await onJsTermValueChanged;
 
-  is(hud.jsterm.getInputValue(), expectedJsTermInputValue, "JsTerm has expected value");
+  is(getInputValue(hud), expectedJsTermInputValue, "JsTerm has expected value");
 
   const infoElement = getReverseSearchInfoElement(hud);
   is(infoElement.textContent, expectedInfoText, "The reverse info has the expected text");
   is(isReverseSearchInputFocused(hud), true, "reverse search input is still focused");
 }
 
 function triggerPreviousResultShortcut() {
   if (isMacOS) {
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_mouse_navigation.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_reverse_search_mouse_navigation.js
@@ -25,17 +25,17 @@ add_task(async function() {
   }
   await onLastMessage;
 
   await openReverseSearch(hud);
   EventUtils.sendString("d");
   const infoElement = await waitFor(() => getReverseSearchInfoElement(hud));
   is(infoElement.textContent, "3 of 3 results", "The reverse info has the expected text");
 
-  is(hud.jsterm.getInputValue(), jstermHistory[2], "JsTerm has the expected input");
+  is(getInputValue(hud), jstermHistory[2], "JsTerm has the expected input");
   is(hud.jsterm.autocompletePopup.isOpen, false,
     "Setting the input value did not trigger the autocompletion");
 
   await navigateResultsAndCheckState(hud, {
     direction: "previous",
     expectedInfoText: "2 of 3 results",
     expectedJsTermInputValue: jstermHistory[1],
   });
@@ -80,17 +80,17 @@ async function navigateResultsAndCheckSt
   const onJsTermValueChanged = hud.jsterm.once("set-input-value");
   if (direction === "previous") {
     clickPreviousButton(hud);
   } else {
     clickNextButton(hud);
   }
   await onJsTermValueChanged;
 
-  is(hud.jsterm.getInputValue(), expectedJsTermInputValue, "JsTerm has expected value");
+  is(getInputValue(hud), expectedJsTermInputValue, "JsTerm has expected value");
 
   const infoElement = getReverseSearchInfoElement(hud);
   is(infoElement.textContent, expectedInfoText, "The reverse info has the expected text");
   is(isReverseSearchInputFocused(hud), true, "reverse search input is still focused");
 }
 
 function clickPreviousButton(hud) {
   const reverseSearchElement = getReverseSearchElement(hud);
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_split_focus.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_split_focus.js
@@ -29,18 +29,18 @@ async function testHistory() {
 
   let activeElement = getActiveElement(inspector.panelDoc);
   is(activeElement, inspector.searchBox, "Search box is focused");
 
   await toolbox.openSplitConsole();
 
   ok(toolbox.splitConsole, "Split console is now visible");
 
-  const {jsterm} = toolbox.getPanel("webconsole").hud;
-  ok(isJstermFocused(jsterm), "Split console input is focused by default");
+  const {hud} = toolbox.getPanel("webconsole");
+  ok(isInputFocused(hud), "Split console input is focused by default");
 
   await toolbox.closeSplitConsole();
 
   info("Making sure that the search box is refocused after closing the split console");
   activeElement = getActiveElement(inspector.panelDoc);
   is(activeElement, inspector.searchBox, "Search box is focused");
 }
 
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_split_persist.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_split_persist.js
@@ -28,17 +28,17 @@ add_task(async function() {
      "Panel height matches the pref");
   toolbox.webconsolePanel.height = 200;
 
   await toolbox.destroy();
 
   info("Opening a tab while there is a true user setting on split console pref");
   toolbox = await openNewTabAndToolbox(TEST_URI, "inspector");
   ok(toolbox.splitConsole, "Split console is visible by default.");
-  ok(isJstermFocused(toolbox.getPanel("webconsole").hud.jsterm),
+  ok(isInputFocused(toolbox.getPanel("webconsole").hud),
      "Split console input is focused by default");
   ok(await doesMenuSayHide(toolbox),
      "Split console menu item initially says hide");
   is(getHeightPrefValue(), 200, "Height is set based on panel height after closing");
 
   toolbox.webconsolePanel.height = 1;
   ok(toolbox.webconsolePanel.clientHeight > 1,
      "The actual height of the console is bound with a min height");
--- a/devtools/client/webconsole/test/mochitest/head.js
+++ b/devtools/client/webconsole/test/mochitest/head.js
@@ -275,30 +275,30 @@ function findMessages(hud, text, selecto
  * @param element element
  *        The dom element on which the context menu event should be synthesized.
  * @return promise
  */
 async function openContextMenu(hud, element) {
   const onConsoleMenuOpened = hud.ui.wrapper.once("menu-open");
   synthesizeContextMenuEvent(element);
   await onConsoleMenuOpened;
-  const doc = hud.ui.wrapper.owner.chromeWindow.document;
+  const doc = hud.chromeWindow.document;
   return doc.getElementById("webconsole-menu");
 }
 
 /**
  * Hide the webconsole context menu popup. Returns a promise that will resolve when the
  * context menu popup is hidden or immediately if the popup can't be found.
  *
  * @param object hud
  *        The web console.
  * @return promise
  */
 function hideContextMenu(hud) {
-  const doc = hud.ui.wrapper.owner.chromeWindow.document;
+  const doc = hud.chromeWindow.document;
   const popup = doc.getElementById("webconsole-menu");
   if (!popup) {
     return Promise.resolve();
   }
 
   const onPopupHidden = once(popup, "popuphidden");
   popup.hidePopup();
   return onPopupHidden;
@@ -388,32 +388,54 @@ async function checkClickOnNode(hud, too
  * Returns true if the give node is currently focused.
  */
 function hasFocus(node) {
   return node.ownerDocument.activeElement == node
     && node.ownerDocument.hasFocus();
 }
 
 /**
- * Set the value of the JsTerm and its caret position, and wait for the autocompletion
- * to be updated.
+ * Get the value of the console input .
+ *
+ * @param {WebConsole} hud: The webconsole
+ * @returns {String}: The value of the console input.
+ */
+function getInputValue(hud) {
+  return hud.jsterm._getValue();
+}
+
+/**
+ * Set the value of the console input .
  *
- * @param {JsTerm} jsterm
+ * @param {WebConsole} hud: The webconsole
+ * @param {String} value : The value to set the console input to.
+ */
+function setInputValue(hud, value) {
+  return hud.jsterm._setValue(value);
+}
+
+/**
+ * Set the value of the console input and its caret position, and wait for the
+ * autocompletion to be updated.
+ *
+ * @param {WebConsole} hud: The webconsole
  * @param {String} value : The value to set the jsterm to.
  * @param {Integer} caretPosition : The index where to place the cursor. A negative
  *                  number will place the caret at (value.length - offset) position.
  *                  Default to value.length (caret set at the end).
  * @returns {Promise} resolves when the jsterm is completed.
  */
 async function setInputValueForAutocompletion(
-  jsterm,
+  hud,
   value,
   caretPosition = value.length,
 ) {
-  jsterm.setInputValue("");
+  const {jsterm} = hud;
+
+  setInputValue(hud, "");
   jsterm.focus();
 
   const updated = jsterm.once("autocomplete-updated");
   EventUtils.sendString(value);
   await updated;
 
   if (caretPosition < 0) {
     caretPosition = value.length + caretPosition;
@@ -428,131 +450,134 @@ async function setInputValueForAutocompl
 
     if (jsterm.editor) {
       jsterm.editor.setCursor(jsterm.editor.getPosition(caretPosition));
     }
   }
 }
 
 /**
- * Set the value of the JsTerm and wait for the confirm dialog to be displayed.
+ * Set the value of the console input and wait for the confirm dialog to be displayed.
  *
  * @param {Toolbox} toolbox
- * @param {JsTerm} jsterm
+ * @param {WebConsole} hud
  * @param {String} value : The value to set the jsterm to.
  *                  Default to value.length (caret set at the end).
  * @returns {Promise<HTMLElement>} resolves with dialog element when it is opened.
  */
-async function setInputValueForGetterConfirmDialog(toolbox, jsterm, value) {
-  await setInputValueForAutocompletion(jsterm, value);
+async function setInputValueForGetterConfirmDialog(toolbox, hud, value) {
+  await setInputValueForAutocompletion(hud, value);
   await waitFor(() => isConfirmDialogOpened(toolbox));
   ok(true, "The confirm dialog is displayed");
   return getConfirmDialog(toolbox);
 }
 
 /**
- * Checks if the jsterm has the expected completion value.
+ * Checks if the console input has the expected completion value.
  *
- * @param {JsTerm} jsterm
+ * @param {WebConsole} hud
  * @param {String} expectedValue
  * @param {String} assertionInfo: Description of the assertion passed to `is`.
  */
-function checkJsTermCompletionValue(jsterm, expectedValue, assertionInfo) {
-  const completionValue = getJsTermCompletionValue(jsterm);
+function checkInputCompletionValue(hud, expectedValue, assertionInfo) {
+  const completionValue = getInputCompletionValue(hud);
   if (completionValue === null) {
     ok(false, "Couldn't retrieve the completion value");
   }
 
   info(`Expects "${expectedValue}", is "${completionValue}"`);
 
-  if (jsterm.completeNode) {
+  if (hud.jsterm.completeNode) {
     is(completionValue, expectedValue, assertionInfo);
   } else {
     // CodeMirror jsterm doesn't need to add prefix-spaces.
     is(completionValue, expectedValue.trim(), assertionInfo);
   }
 }
 
 /**
- * Checks if the cursor on jsterm is at the expected position.
+ * Checks if the cursor on console input is at expected position.
  *
- * @param {JsTerm} jsterm
+ * @param {WebConsole} hud
  * @param {Integer} expectedCursorIndex
  * @param {String} assertionInfo: Description of the assertion passed to `is`.
  */
-function checkJsTermCursor(jsterm, expectedCursorIndex, assertionInfo) {
+function checkInputCursorPosition(hud, expectedCursorIndex, assertionInfo) {
+  const {jsterm} = hud;
   if (jsterm.inputNode) {
     const {selectionStart, selectionEnd} = jsterm.inputNode;
     is(selectionStart, expectedCursorIndex, assertionInfo);
     ok(selectionStart === selectionEnd);
   } else {
     is(jsterm.editor.getCursor().ch, expectedCursorIndex, assertionInfo);
   }
 }
 
 /**
- * Checks the jsterm value and the cursor position given an expected string containing
- * a "|" to indicate the expected cursor position.
+ * Checks the console input value and the cursor position given an expected string
+ * containing a "|" to indicate the expected cursor position.
  *
- * @param {JsTerm} jsterm
+ * @param {WebConsole} hud
  * @param {String} expectedStringWithCursor:
  *                  String with a "|" to indicate the expected cursor position.
  *                  For example, this is how you assert an empty value with the focus "|",
  *                  and this indicates the value should be "test" and the cursor at the
  *                  end of the input: "test|".
  * @param {String} assertionInfo: Description of the assertion passed to `is`.
  */
-function checkJsTermValueAndCursor(jsterm, expectedStringWithCursor, assertionInfo) {
+function checkInputValueAndCursorPosition(hud, expectedStringWithCursor, assertionInfo) {
   info(`Checking jsterm state: \n${expectedStringWithCursor}`);
   if (!expectedStringWithCursor.includes("|")) {
     ok(false,
       `expectedStringWithCursor must contain a "|" char to indicate cursor position`);
   }
 
   const inputValue = expectedStringWithCursor.replace("|", "");
-  is(jsterm.getInputValue(), inputValue, "jsterm has expected value");
+  const {jsterm} = hud;
+  is(getInputValue(hud), inputValue, "console input has expected value");
   if (jsterm.inputNode) {
     is(jsterm.inputNode.selectionStart, jsterm.inputNode.selectionEnd);
     is(jsterm.inputNode.selectionStart, expectedStringWithCursor.indexOf("|"),
       assertionInfo);
   } else {
     const lines = expectedStringWithCursor.split("\n");
     const lineWithCursor = lines.findIndex(line => line.includes("|"));
     const {ch, line} = jsterm.editor.getCursor();
     is(line, lineWithCursor, assertionInfo + " - correct line");
     is(ch, lines[lineWithCursor].indexOf("|"), assertionInfo + " - correct ch");
   }
 }
 
 /**
- * Returns the jsterm completion value, whether there's CodeMirror enabled or not.
+ * Returns the console input completion value.
  *
- * @param {JsTerm} jsterm
+ * @param {WebConsole} hud
  * @returns {String}
  */
-function getJsTermCompletionValue(jsterm) {
+function getInputCompletionValue(hud) {
+  const {jsterm} = hud;
   if (jsterm.completeNode) {
     return jsterm.completeNode.value;
   }
 
   if (jsterm.editor) {
     return jsterm.editor.getAutoCompletionText();
   }
 
   return null;
 }
 
 /**
- * Returns a boolean indicating if the jsterm is focused, whether there's CodeMirror
- * enabled or not.
+ * Returns a boolean indicating if the console input is focused.
  *
- * @param {JsTerm} jsterm
+ * @param {WebConsole} hud
  * @returns {Boolean}
  */
-function isJstermFocused(jsterm) {
+function isInputFocused(hud) {
+  const {jsterm} = hud;
   const document = jsterm.outputNode.ownerDocument;
   const documentIsFocused = document.hasFocus();
 
   if (jsterm.inputNode) {
     return document.activeElement == jsterm.inputNode && documentIsFocused;
   }
 
   if (jsterm.editor) {
@@ -945,17 +970,17 @@ function getReverseSearchInfoElement(hud
   }
 
   return reverseSearchElement.querySelector(".reverse-search-info");
 }
 
 /**
  * Returns a boolean indicating if the reverse search input is focused.
  *
- * @param {JsTerm} jsterm
+ * @param {WebConsole} hud
  * @returns {Boolean}
  */
 function isReverseSearchInputFocused(hud) {
   const {outputNode} = hud.ui;
   const document = outputNode.ownerDocument;
   const documentIsFocused = document.hasFocus();
   const reverseSearchInput = outputNode.querySelector(".reverse-search-input");
 
--- a/devtools/client/webconsole/utils/context-menu.js
+++ b/devtools/client/webconsole/utils/context-menu.js
@@ -112,17 +112,17 @@ function createContextMenu(webConsoleUI,
         "temp" + i;
       }`;
       const options = {
         selectedObjectActor: actor,
       };
 
       webConsoleUI.jsterm.requestEvaluation(evalString, options).then((res) => {
         webConsoleUI.jsterm.focus();
-        webConsoleUI.jsterm.setInputValue(res.result);
+        webConsoleUI.hud.setInputValue(res.result);
       });
     },
   }));
 
   // Copy message or grip.
   menu.append(new MenuItem({
     id: "console-menu-copy",
     label: l10n.getStr("webconsole.menu.copyMessage.label"),
--- a/devtools/client/webconsole/webconsole-ui.js
+++ b/devtools/client/webconsole/webconsole-ui.js
@@ -27,23 +27,23 @@ const PREF_SIDEBAR_ENABLED = "devtools.w
  * that displays console log data as well as provides an interactive terminal to
  * manipulate the target's document content.
  *
  * The WebConsoleUI is responsible for the actual Web Console UI
  * implementation.
  */
 class WebConsoleUI {
   /*
-   * @param {object} webConsoleOwner: The WebConsole owner object.
+   * @param {WebConsole} hud: The WebConsole owner object.
    */
-  constructor(webConsoleOwner) {
-    this.owner = webConsoleOwner;
-    this.hudId = this.owner.hudId;
-    this.isBrowserConsole = this.owner._browserConsole;
-    this.window = this.owner.iframeWindow;
+  constructor(hud) {
+    this.hud = hud;
+    this.hudId = this.hud.hudId;
+    this.isBrowserConsole = this.hud._browserConsole;
+    this.window = this.hud.iframeWindow;
 
     this._onToolboxPrefChanged = this._onToolboxPrefChanged.bind(this);
     this._onPanelSelected = this._onPanelSelected.bind(this);
     this._onChangeSplitConsoleState = this._onChangeSplitConsoleState.bind(this);
 
     EventEmitter.decorate(this);
   }
 
@@ -81,24 +81,24 @@ class WebConsoleUI {
     this._destroyer = defer();
     Services.prefs.removeObserver(PREF_MESSAGE_TIMESTAMP, this._onToolboxPrefChanged);
     this.React = this.ReactDOM = this.FrameView = null;
     if (this.jsterm) {
       this.jsterm.destroy();
       this.jsterm = null;
     }
 
-    const toolbox = gDevTools.getToolbox(this.owner.target);
+    const toolbox = gDevTools.getToolbox(this.hud.target);
     if (toolbox) {
       toolbox.off("webconsole-selected", this._onPanelSelected);
       toolbox.off("split-console", this._onChangeSplitConsoleState);
       toolbox.off("select", this._onChangeSplitConsoleState);
     }
 
-    this.window = this.owner = this.wrapper = null;
+    this.window = this.hud = this.wrapper = null;
 
     const onDestroy = () => {
       this._destroyer.resolve(null);
     };
     if (this.proxy) {
       this.proxy.disconnect().then(onDestroy);
       this.proxy = null;
     } else {
@@ -146,17 +146,17 @@ class WebConsoleUI {
         type: "inspectObject",
         object: objectActor,
       },
     }, true);
     return this.wrapper;
   }
 
   logWarningAboutReplacedAPI() {
-    return this.owner.target.logWarningInPage(l10n.getStr("ConsoleAPIDisabled"),
+    return this.hud.target.logWarningInPage(l10n.getStr("ConsoleAPIDisabled"),
       "ConsoleAPIDisabled");
   }
 
   /**
    * Setter for saving of network request and response bodies.
    *
    * @param boolean value
    *        The new value you want to set.
@@ -185,17 +185,17 @@ class WebConsoleUI {
    *         result.
    */
   _initConnection() {
     if (this._initDefer) {
       return this._initDefer.promise;
     }
 
     this._initDefer = defer();
-    this.proxy = new WebConsoleConnectionProxy(this, this.owner.target);
+    this.proxy = new WebConsoleConnectionProxy(this, this.hud.target);
 
     this.proxy.connect().then(() => {
       // on success
       this._initDefer.resolve(this);
     }, (reason) => {
       // on failure
       // TODO Print a message to console
       this._initDefer.reject(reason);
@@ -205,20 +205,20 @@ class WebConsoleUI {
   }
 
   _initUI() {
     this.document = this.window.document;
     this.rootElement = this.document.documentElement;
 
     this.outputNode = this.document.getElementById("app-wrapper");
 
-    const toolbox = gDevTools.getToolbox(this.owner.target);
+    const toolbox = gDevTools.getToolbox(this.hud.target);
 
     this.wrapper = new this.window.WebConsoleWrapper(
-      this.outputNode, this, toolbox, this.owner, this.document);
+      this.outputNode, this, toolbox, this.document);
 
     this._initShortcuts();
     this._initOutputSyntaxHighlighting();
 
     if (toolbox) {
       toolbox.on("webconsole-selected", this._onPanelSelected);
       toolbox.on("split-console", this._onChangeSplitConsoleState);
       toolbox.on("select", this._onChangeSplitConsoleState);
@@ -294,18 +294,18 @@ class WebConsoleUI {
    *
    * @param string uri
    *        New page location.
    * @param string title
    *        New page title.
    */
   onLocationChange(uri, title) {
     this.contentLocation = uri;
-    if (this.owner.onLocationChange) {
-      this.owner.onLocationChange(uri, title);
+    if (this.hud.onLocationChange) {
+      this.hud.onLocationChange(uri, title);
     }
   }
 
   /**
    * Release an actor.
    *
    * @private
    * @param string actor
--- a/devtools/client/webconsole/webconsole-wrapper.js
+++ b/devtools/client/webconsole/webconsole-wrapper.js
@@ -23,23 +23,30 @@ const App = createFactory(require("devto
 const ObjectClient = require("devtools/shared/client/object-client");
 const LongStringClient = require("devtools/shared/client/long-string-client");
 loader.lazyRequireGetter(this, "Constants", "devtools/client/webconsole/constants");
 loader.lazyRequireGetter(this, "getElementText", "devtools/client/webconsole/utils/clipboard", true);
 
 let store = null;
 
 class WebConsoleWrapper {
-  constructor(parentNode, webConsoleUI, toolbox, owner, document) {
+  /**
+   *
+   * @param {HTMLElement} parentNode
+   * @param {WebConsoleUI} webConsoleUI
+   * @param {Toolbox} toolbox
+   * @param {Document} document
+   */
+  constructor(parentNode, webConsoleUI, toolbox, document) {
     EventEmitter.decorate(this);
 
     this.parentNode = parentNode;
     this.webConsoleUI = webConsoleUI;
     this.toolbox = toolbox;
-    this.owner = owner;
+    this.hud = this.webConsoleUI.hud;
     this.document = document;
 
     this.init = this.init.bind(this);
 
     this.queuedMessageAdds = [];
     this.queuedMessageUpdates = [];
     this.queuedRequestUpdates = [];
     this.throttledDispatchPromise = null;
@@ -48,54 +55,54 @@ class WebConsoleWrapper {
   }
 
   init() {
     return new Promise((resolve) => {
       const attachRefToWebConsoleUI = (id, node) => {
         this.webConsoleUI[id] = node;
       };
       const { webConsoleUI } = this;
-      const debuggerClient = this.owner.target.client;
+      const debuggerClient = this.hud.target.client;
 
       const serviceContainer = {
         attachRefToWebConsoleUI,
         emitNewMessage: (node, messageId, timeStamp) => {
           webConsoleUI.emit("new-messages", new Set([{
             node,
             messageId,
             timeStamp,
           }]));
         },
         proxy: webConsoleUI.proxy,
         openLink: (url, e) => {
-          webConsoleUI.owner.openLink(url, e);
+          webConsoleUI.hud.openLink(url, e);
         },
         canRewind: () => {
           if (!(
-            webConsoleUI.owner
-            && webConsoleUI.owner.target
-            && webConsoleUI.owner.target.traits
+            webConsoleUI.hud
+            && webConsoleUI.hud.target
+            && webConsoleUI.hud.target.traits
           )) {
             return false;
           }
 
-          return webConsoleUI.owner.target.traits.canRewind;
+          return webConsoleUI.hud.target.traits.canRewind;
         },
         createElement: nodename => {
           return this.document.createElement(nodename);
         },
         getLongString: (grip) => {
           return webConsoleUI.proxy.webConsoleClient.getString(grip);
         },
         requestData(id, type) {
           return webConsoleUI.proxy.networkDataProvider.requestData(id, type);
         },
         onViewSource(frame) {
-          if (webConsoleUI && webConsoleUI.owner && webConsoleUI.owner.viewSource) {
-            webConsoleUI.owner.viewSource(frame.url, frame.line);
+          if (webConsoleUI && webConsoleUI.hud && webConsoleUI.hud.viewSource) {
+            webConsoleUI.hud.viewSource(frame.url, frame.line);
           }
         },
         recordTelemetryEvent: (eventName, extra = {}) => {
           this.telemetry.recordEvent(eventName, "webconsole", null, {
             ...extra,
             "session_id": this.toolbox && this.toolbox.sessionId || -1,
           });
         },
@@ -123,17 +130,17 @@ class WebConsoleWrapper {
          * Retrieve the FrameActor ID given a frame depth, or the selected one if no
          * frame depth given.
          *
          * @param {Number} frame: optional frame depth.
          * @return {String|null}: The FrameActor ID for the given frame depth (or the
          *                        selected frame if it exists).
          */
         getFrameActor: (frame = null) => {
-          const state = this.owner.getDebuggerFrames();
+          const state = this.hud.getDebuggerFrames();
           if (!state) {
             return null;
           }
 
           const grip = Number.isInteger(frame)
             ? state.frames[frame]
             : state.frames[state.selected];
           return grip ? grip.actor : null;
@@ -142,25 +149,37 @@ class WebConsoleWrapper {
         inputHasSelection: () => {
           const {editor, inputNode} = webConsoleUI.jsterm || {};
           return editor
             ? !!editor.getSelection()
             : (inputNode && inputNode.selectionStart !== inputNode.selectionEnd);
         },
 
         getInputValue: () => {
-          return webConsoleUI.jsterm && webConsoleUI.jsterm.getInputValue();
+          return this.hud.getInputValue();
+        },
+
+        setInputValue: (value) => {
+          this.hud.setInputValue(value);
+        },
+
+        focusInput: () => {
+          return webConsoleUI.jsterm && webConsoleUI.jsterm.focus();
+        },
+
+        evaluateInput: (expression) => {
+          return webConsoleUI.jsterm && webConsoleUI.jsterm.execute(expression);
         },
 
         getInputCursor: () => {
           return webConsoleUI.jsterm && webConsoleUI.jsterm.getSelectionStart();
         },
 
         getSelectedNodeActor: () => {
-          const inspectorSelection = this.owner.getInspectorSelection();
+          const inspectorSelection = this.hud.getInspectorSelection();
           if (inspectorSelection && inspectorSelection.nodeFront) {
             return inspectorSelection.nodeFront.actorID;
           }
           return null;
         },
 
         getJsTermTooltipAnchor: () => {
           if (jstermCodeMirror) {
@@ -213,27 +232,27 @@ class WebConsoleWrapper {
           openSidebar,
           rootActorId,
           executionPoint,
           toolbox: this.toolbox,
         });
 
         // Emit the "menu-open" event for testing.
         menu.once("open", () => this.emit("menu-open"));
-        menu.popup(screenX, screenY, { doc: this.owner.chromeWindow.document });
+        menu.popup(screenX, screenY, { doc: this.hud.chromeWindow.document });
 
         return menu;
       };
 
       serviceContainer.openEditContextMenu = (e) => {
         const { screenX, screenY } = e;
         const menu = createEditContextMenu(window, "webconsole-menu");
         // Emit the "menu-open" event for testing.
         menu.once("open", () => this.emit("menu-open"));
-        menu.popup(screenX, screenY, { doc: this.owner.chromeWindow.document });
+        menu.popup(screenX, screenY, { doc: this.hud.chromeWindow.document });
 
         return menu;
       };
 
       if (this.toolbox) {
         this.toolbox.threadClient.addListener("paused", this.dispatchPaused.bind(this));
         this.toolbox.threadClient.addListener(
           "progress", this.dispatchProgress.bind(this));
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -16,138 +16,158 @@ var gHudId = 0;
 /**
  * A WebConsole instance is an interactive console initialized *per target*
  * that displays console log data as well as provides an interactive terminal to
  * manipulate the target's document content.
  *
  * This object only wraps the iframe that holds the Web Console UI. This is
  * meant to be an integration point between the Firefox UI and the Web Console
  * UI and features.
- *
- * @constructor
- * @param object target
- *        The target that the web console will connect to.
- * @param nsIDOMWindow iframeWindow
- *        The window where the web console UI is already loaded.
- * @param nsIDOMWindow chromeWindow
- *        The window of the web console owner.
- * @param object hudService
- *        The parent HUD Service
  */
-function WebConsole(target, iframeWindow, chromeWindow, hudService) {
-  this.iframeWindow = iframeWindow;
-  this.chromeWindow = chromeWindow;
-  this.hudId = "hud_" + ++gHudId;
-  this.target = target;
-  this.browserWindow = this.chromeWindow.top;
-  this.hudService = hudService;
+class WebConsole {
+  /*
+  * @constructor
+  * @param object target
+  *        The target that the web console will connect to.
+  * @param nsIDOMWindow iframeWindow
+  *        The window where the web console UI is already loaded.
+  * @param nsIDOMWindow chromeWindow
+  *        The window of the web console owner.
+  * @param object hudService
+  *        The parent HUD Service
+  * @param bool isBrowserConsole
+  */
+  constructor(target, iframeWindow, chromeWindow, hudService, isBrowserConsole = false) {
+    this.iframeWindow = iframeWindow;
+    this.chromeWindow = chromeWindow;
+    this.hudId = "hud_" + ++gHudId;
+    this.target = target;
+    this.browserWindow = this.chromeWindow.top;
+    this.hudService = hudService;
+    this._browserConsole = isBrowserConsole;
 
-  const element = this.browserWindow.document.documentElement;
-  if (element.getAttribute("windowtype") != gDevTools.chromeWindowType) {
-    this.browserWindow = this.hudService.currentContext();
+    const element = this.browserWindow.document.documentElement;
+    if (element.getAttribute("windowtype") != gDevTools.chromeWindowType) {
+      this.browserWindow = this.hudService.currentContext();
+    }
+    this.ui = new WebConsoleUI(this);
+    this._destroyer = null;
   }
-  this.ui = new WebConsoleUI(this);
-}
-
-WebConsole.prototype = {
-  iframeWindow: null,
-  chromeWindow: null,
-  browserWindow: null,
-  hudId: null,
-  target: null,
-  ui: null,
-  _browserConsole: false,
-  _destroyer: null,
 
   /**
    * Getter for a function to to listen for every request that completes. Used
    * by unit tests. The callback takes one argument: the HTTP activity object as
    * received from the remote Web Console.
    *
    * @type function
    */
   get lastFinishedRequestCallback() {
     return this.hudService.lastFinishedRequest.callback;
-  },
+  }
 
   /**
    * Getter for the window that can provide various utilities that the web
    * console makes use of, like opening links, managing popups, etc.  In
    * most cases, this will be |this.browserWindow|, but in some uses (such as
    * the Browser Toolbox), there is no browser window, so an alternative window
    * hosts the utilities there.
    * @type nsIDOMWindow
    */
   get chromeUtilsWindow() {
     if (this.browserWindow) {
       return this.browserWindow;
     }
     return this.chromeWindow.top;
-  },
+  }
 
   /**
    * Getter for the output element that holds messages we display.
    * @type Element
    */
   get outputNode() {
     return this.ui ? this.ui.outputNode : null;
-  },
+  }
 
   get gViewSourceUtils() {
     return this.chromeUtilsWindow.gViewSourceUtils;
-  },
+  }
 
   /**
    * Initialize the Web Console instance.
    *
    * @return object
    *         A promise for the initialization.
    */
   init() {
     return this.ui.init().then(() => this);
-  },
+  }
 
   /**
    * The JSTerm object that manages the console's input.
    * @see webconsole.js::JSTerm
    * @type object
    */
   get jsterm() {
     return this.ui ? this.ui.jsterm : null;
-  },
+  }
+
+  /**
+   * Get the value from the input field.
+   * @returns {String|null} returns null if there's no input.
+   */
+  getInputValue() {
+    if (!this.jsterm) {
+      return null;
+    }
+
+    return this.jsterm._getValue();
+  }
+
+  /**
+   * Sets the value of the input field (command line)
+   *
+   * @param {String} newValue: The new value to set.
+   */
+  setInputValue(newValue) {
+    if (!this.jsterm) {
+      return;
+    }
+
+    this.jsterm._setValue(newValue);
+  }
 
   /**
    * Alias for the WebConsoleUI.setFilterState() method.
    * @see webconsole.js::WebConsoleUI.setFilterState()
    */
   setFilterState() {
     this.ui && this.ui.setFilterState.apply(this.ui, arguments);
-  },
+  }
 
   /**
    * Open a link in a new tab.
    *
    * @param string link
    *        The URL you want to open in a new tab.
    */
   openLink(link, e) {
     openDocLink(link);
-  },
+  }
 
   /**
    * Open a link in Firefox's view source.
    *
    * @param string sourceURL
    *        The URL of the file.
    * @param integer sourceLine
    *        The line number which should be highlighted.
    */
   viewSource(sourceURL, sourceLine) {
     this.gViewSourceUtils.viewSource({ URL: sourceURL, lineNumber: sourceLine || 0 });
-  },
+  }
 
   /**
    * Tries to open a Stylesheet file related to the web page for the web console
    * instance in the Style Editor. If the file is not found, it is opened in
    * source view instead.
    *
    * Manually handle the case where toolbox does not exist (Browser Console).
    *
@@ -158,17 +178,17 @@ WebConsole.prototype = {
    */
   viewSourceInStyleEditor(sourceURL, sourceLine) {
     const toolbox = gDevTools.getToolbox(this.target);
     if (!toolbox) {
       this.viewSource(sourceURL, sourceLine);
       return;
     }
     toolbox.viewSourceInStyleEditor(sourceURL, sourceLine);
-  },
+  }
 
   /**
    * Tries to open a JavaScript file related to the web page for the web console
    * instance in the Script Debugger. If the file is not found, it is opened in
    * source view instead.
    *
    * Manually handle the case where toolbox does not exist (Browser Console).
    *
@@ -181,28 +201,28 @@ WebConsole.prototype = {
     const toolbox = gDevTools.getToolbox(this.target);
     if (!toolbox) {
       this.viewSource(sourceURL, sourceLine);
       return;
     }
     toolbox.viewSourceInDebugger(sourceURL, sourceLine).then(() => {
       this.ui.emit("source-in-debugger-opened");
     });
-  },
+  }
 
   /**
    * Tries to open a JavaScript file related to the web page for the web console
    * instance in the corresponding Scratchpad.
    *
    * @param string sourceURL
    *        The URL of the file which corresponds to a Scratchpad id.
    */
   viewSourceInScratchpad(sourceURL, sourceLine) {
     viewSource.viewSourceInScratchpad(sourceURL, sourceLine);
-  },
+  }
 
   /**
    * Retrieve information about the JavaScript debugger's stackframes list. This
    * is used to allow the Web Console to evaluate code in the selected
    * stackframe.
    *
    * @return object|null
    *         An object which holds:
@@ -219,17 +239,17 @@ WebConsole.prototype = {
     }
     const panel = toolbox.getPanel("jsdebugger");
 
     if (!panel) {
       return null;
     }
 
     return panel.getFrames();
-  },
+  }
 
   /**
    * Given an expression, returns an object containing a new expression, mapped by the
    * parser worker to provide additional feature for the user (top-level await,
    * original languages mapping, …).
    *
    * @param {String} expression: The input to maybe map.
    * @returns {Object|null}
@@ -256,34 +276,34 @@ WebConsole.prototype = {
       const shouldMapBindings = false;
       const shouldMapAwait = true;
       const res = this.parserService.mapExpression(
         expression, null, null, shouldMapBindings, shouldMapAwait);
       return res;
     }
 
     return null;
-  },
+  }
 
   /**
    * A common access point for the client-side parser service that any panel can use.
    */
   get parserService() {
     if (this._parserService) {
       return this._parserService;
     }
 
     this._parserService =
       require("devtools/client/debugger/new/src/workers/parser/index");
 
     this._parserService.start(
       "resource://devtools/client/debugger/new/dist/parser-worker.js",
       this.chromeUtilsWindow);
     return this._parserService;
-  },
+  }
 
   /**
    * Retrieves the current selection from the Inspector, if such a selection
    * exists. This is used to pass the ID of the selected actor to the Web
    * Console server for the $0 helper.
    *
    * @return object|null
    *         A Selection referring to the currently selected node in the
@@ -296,17 +316,17 @@ WebConsole.prototype = {
     if (!toolbox) {
       return null;
     }
     const panel = toolbox.getPanel("inspector");
     if (!panel || !panel.selection) {
       return null;
     }
     return panel.selection;
-  },
+  }
 
   /**
    * Destroy the object. Call this method to avoid memory leaks when the Web
    * Console is closed.
    *
    * @return object
    *         A promise object that is resolved once the Web Console is closed.
    */
@@ -335,12 +355,12 @@ WebConsole.prototype = {
         this._parserService = null;
       }
 
       const id = Utils.supportsString(this.hudId);
       Services.obs.notifyObservers(id, "web-console-destroyed");
     })();
 
     return this._destroyer;
-  },
-};
+  }
+}
 
 module.exports = WebConsole;
--- a/dom/base/nsIRemoteWindowContext.idl
+++ b/dom/base/nsIRemoteWindowContext.idl
@@ -6,10 +6,15 @@
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 
 [scriptable, builtinclass, uuid(94f4a92b-752e-4fd9-8345-11b069ca19f3)]
 interface nsIRemoteWindowContext : nsISupports
 {
+  /*
+   * Determines if the window is in private browsing.
+   */
+  readonly attribute boolean usePrivateBrowsing;
+
   void openURI(in nsIURI aURI);
 };
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1299,16 +1299,23 @@ RemoteWindowContext::GetInterface(const 
 }
 
 NS_IMETHODIMP
 RemoteWindowContext::OpenURI(nsIURI* aURI) {
   mTabParent->LoadURL(aURI);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+RemoteWindowContext::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) {
+  nsCOMPtr<nsILoadContext> loadContext = mTabParent->GetLoadContext();
+  *aUsePrivateBrowsing = loadContext && loadContext->UsePrivateBrowsing();
+  return NS_OK;
+}
+
 }  // namespace
 
 void ContentParent::ShutDownProcess(ShutDownMethod aMethod) {
   if (mScriptableHelper) {
     static_cast<ScriptableCPInfo*>(mScriptableHelper.get())->ProcessDied();
     mScriptableHelper = nullptr;
   }
 
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -311,16 +311,17 @@ GenericFileName=file
 LargeAllocationSuccess=This page was loaded in a new process due to a Large-Allocation header.
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name. Do not translate GET.
 LargeAllocationNonGetRequest=A Large-Allocation header was ignored due to the load being triggered by a non-GET request.
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name. Do not translate `window.opener`.
 LargeAllocationNotOnlyToplevelInTabGroup=A Large-Allocation header was ignored due to the presence of windows which have a reference to this browsing context through the frame hierarchy or window.opener.
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name
 LargeAllocationNonE10S=A Large-Allocation header was ignored due to the document not being loaded out of process.
 GeolocationInsecureRequestIsForbidden=A Geolocation request can only be fulfilled in a secure context.
+NotificationsInsecureRequestIsForbidden=The Notification permission may only be requested in a secure context.
 # LOCALIZATION NOTE: Do not translate "Large-Allocation", as it is a literal header name.
 LargeAllocationNonWin32=This page would be loaded in a new process due to a Large-Allocation header, however Large-Allocation process creation is disabled on non-Win32 platforms.
 # LOCALIZATION NOTE: Do not translate "content", "Window", and "window.top"
 WindowContentUntrustedWarning=The ‘content’ attribute of Window objects is deprecated.  Please use ‘window.top’ instead.
 # LOCALIZATION NOTE: The first %S is the tag name of the element that starts the loop, the second %S is the element's ID.
 SVGRefLoopWarning=The SVG <%S> with ID “%S” has a reference loop.
 # LOCALIZATION NOTE: The first %S is the tag name of the element in the chain where the chain was broken, the second %S is the element's ID.
 SVGRefChainLengthExceededWarning=An SVG <%S> reference chain which is too long was abandoned at the element with ID “%S”.
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -41,16 +41,17 @@
 #include "nsIAlertsService.h"
 #include "nsIContentPermissionPrompt.h"
 #include "mozilla/dom/Document.h"
 #include "nsILoadContext.h"
 #include "nsINotificationStorage.h"
 #include "nsIPermissionManager.h"
 #include "nsIPermission.h"
 #include "nsIPushService.h"
+#include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIServiceWorkerManager.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIUUIDGenerator.h"
 #include "nsIXPConnect.h"
 #include "nsNetUtil.h"
 #include "nsProxyRelease.h"
 #include "nsServiceManagerUtils.h"
@@ -473,23 +474,35 @@ NS_IMETHODIMP
 NotificationPermissionRequest::Run() {
   if (nsContentUtils::IsSystemPrincipal(mPrincipal)) {
     mPermission = NotificationPermission::Granted;
   } else {
     // File are automatically granted permission.
     nsCOMPtr<nsIURI> uri;
     mPrincipal->GetURI(getter_AddRefs(uri));
 
+    bool isFile = false;
     if (uri) {
-      bool isFile;
       uri->SchemeIs("file", &isFile);
       if (isFile) {
         mPermission = NotificationPermission::Granted;
       }
     }
+
+    if (!isFile && !StaticPrefs::dom_webnotifications_allowinsecure() &&
+        !mWindow->IsSecureContext()) {
+      mPermission = NotificationPermission::Denied;
+      nsCOMPtr<Document> doc = mWindow->GetExtantDoc();
+      if (doc) {
+        nsContentUtils::ReportToConsole(
+            nsIScriptError::errorFlag, NS_LITERAL_CSTRING("DOM"), doc,
+            nsContentUtils::eDOM_PROPERTIES,
+            "NotificationsInsecureRequestIsForbidden");
+      }
+    }
   }
 
   // We can't call ShowPrompt() directly here since our logic for determining
   // whether to display a prompt depends on the checks above as well as the
   // result of CheckPromptPrefs().  So we have to manually check the prompt
   // prefs and decide what to do based on that.
   PromptResult pr = CheckPromptPrefs();
   switch (pr) {
--- a/dom/notification/test/browser/browser_permission_dismiss.js
+++ b/dom/notification/test/browser/browser_permission_dismiss.js
@@ -1,16 +1,16 @@
 "use strict";
 
-const ORIGIN_URI = Services.io.newURI("http://mochi.test:8888");
+const ORIGIN_URI = Services.io.newURI("https://example.com");
 const PERMISSION_NAME = "desktop-notification";
 const PROMPT_ALLOW_BUTTON = -1;
 const PROMPT_NOT_NOW_BUTTON = 0;
 const PROMPT_NEVER_BUTTON = 1;
-const TEST_URL = "http://mochi.test:8888/browser/dom/notification/test/browser/notification.html";
+const TEST_URL = "https://example.com/browser/dom/notification/test/browser/notification.html";
 
 /**
  * Clicks the specified web-notifications prompt button.
  *
  * @param {Number} aButtonIndex Number indicating which button to click.
  *                              See the constants in this file.
  * @note modified from toolkit/components/passwordmgr/test/browser/head.js
  */
--- a/dom/notification/test/mochitest/NotificationTest.js
+++ b/dom/notification/test/mochitest/NotificationTest.js
@@ -7,16 +7,19 @@ var NotificationTest = (function() {
 
   function setup_testing_env() {
     SimpleTest.waitForExplicitFinish();
     // turn on testing pref (used by notification.cpp, and mock the alerts
     SpecialPowers.setBoolPref("notification.prompt.testing", true);
   }
 
   function teardown_testing_env() {
+    SpecialPowers.clearUserPref("notification.prompt.testing");
+    SpecialPowers.clearUserPref("notification.prompt.testing.allow");
+
     SimpleTest.finish();
   }
 
   function executeTests(tests, callback) {
     // context is `this` object in test functions
     // it can be used to track data between tests
     var context = {};
 
--- a/dom/notification/test/mochitest/mochitest.ini
+++ b/dom/notification/test/mochitest/mochitest.ini
@@ -1,12 +1,14 @@
 [DEFAULT]
 
 support-files =
   create_notification.html
   MockServices.js
   NotificationTest.js
 
 [test_notification_basics.html]
+# This test needs to be run on HTTP (not HTTPS).
+[test_notification_insecure_context.html]
 [test_notification_storage.html]
 [test_bug931307.html]
 skip-if = (os == 'android') # Bug 1258975 on android.
-[test_notification_tag.html]
\ No newline at end of file
+[test_notification_tag.html]
new file mode 100644
--- /dev/null
+++ b/dom/notification/test/mochitest/test_notification_insecure_context.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests that Notification permissions are denied in insecure context.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1429432
+-->
+<head>
+  <title>Notification permission in insecure context</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+  <p id="display"></p>
+  <div id="content" style="display: none">
+  </div>
+  <pre id="test">
+  <script class="testbody" type="text/javascript">
+  SimpleTest.waitForExplicitFinish();
+
+  // Add an allow permission for the mochitest origin to test this.
+  let script = SpecialPowers.loadChromeScript(function() {
+    let {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+    let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("http://mochi.test:8888");
+    Services.perms.addFromPrincipal(principal, "desktop-notification", Services.perms.ALLOW_ACTION);
+    /* global addMessageListener */
+    addMessageListener("destroy", function() {
+      Services.perms.removeFromPrincipal(principal, "desktop-notification");
+    });
+  });
+
+  (async function runTest() {
+    let response = await Notification.requestPermission();
+    is(response, "denied", "Denied permission in insecure context");
+
+    await SpecialPowers.pushPrefEnv({"set": [["dom.webnotifications.allowinsecure", true]]});
+
+    response = await Notification.requestPermission();
+    is(response, "granted", "Granted permission in insecure context with pref set");
+
+    script.sendSyncMessage("destroy");
+    script.destroy();
+
+    SimpleTest.finish();
+  })();
+  </script>
+  </pre>
+</body>
+</html>
--- a/editor/libeditor/HTMLEditorCommands.cpp
+++ b/editor/libeditor/HTMLEditorCommands.cpp
@@ -1404,16 +1404,19 @@ InsertTagCommand::DoCommandParams(const 
     return NS_ERROR_FAILURE;
   }
 
   // Don't use nsAutoString here because nsCommandParams stores string member
   // with nsString*.  Therefore, nsAutoString always needs to copy the storage
   // but nsString may avoid it.
   nsString value;
   nsresult rv = aParams->AsCommandParams()->GetString(STATE_ATTRIBUTE, value);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
   if (NS_WARN_IF(value.IsEmpty())) {
     return NS_ERROR_INVALID_ARG;
   }
 
   // filter out tags we don't know how to insert
   nsAtom* attribute = nullptr;
   if (mTagName == nsGkAtoms::a) {
     attribute = nsGkAtoms::href;
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -2215,17 +2215,19 @@ pub extern "C" fn wr_dp_define_scroll_la
     let space_and_clip = state.frame_builder.dl_builder.define_scroll_frame(
         &parent.to_webrender(state.pipeline_id),
         Some(ExternalScrollId(external_scroll_id, state.pipeline_id)),
         content_rect,
         clip_rect,
         vec![],
         None,
         ScrollSensitivity::Script,
-        scroll_offset,
+        // TODO(gw): We should also update the Gecko-side APIs to provide
+        //           this as a vector rather than a point.
+        scroll_offset.to_vector(),
     );
 
     WrSpaceAndClip::from_webrender(space_and_clip)
 }
 
 #[no_mangle]
 pub extern "C" fn wr_dp_push_iframe(state: &mut WrState,
                                     rect: LayoutRect,
--- a/gfx/wr/examples/scrolling.rs
+++ b/gfx/wr/examples/scrolling.rs
@@ -51,17 +51,17 @@ impl Example for App {
             let space_and_clip1 = builder.define_scroll_frame(
                 &root_space_and_clip,
                 None,
                 (0, 0).by(1000, 1000),
                 scrollbox,
                 vec![],
                 None,
                 ScrollSensitivity::ScriptAndInputEvents,
-                LayoutPoint::zero(),
+                LayoutVector2D::zero(),
             );
 
             // now put some content into it.
             // start with a white background
             let mut info = LayoutPrimitiveInfo::new((0, 0).to(1000, 1000));
             info.tag = Some((0, 1));
             builder.push_rect(&info, &space_and_clip1, ColorF::new(1.0, 1.0, 1.0, 1.0));
 
@@ -83,17 +83,17 @@ impl Example for App {
             let space_and_clip2 = builder.define_scroll_frame(
                 &space_and_clip1,
                 None,
                 (0, 100).to(300, 1000),
                 (0, 100).to(200, 300),
                 vec![],
                 None,
                 ScrollSensitivity::ScriptAndInputEvents,
-                LayoutPoint::zero(),
+                LayoutVector2D::zero(),
             );
 
             // give it a giant gray background just to distinguish it and to easily
             // visually identify the nested scrollbox
             let mut info = LayoutPrimitiveInfo::new((-1000, -1000).to(5000, 5000));
             info.tag = Some((0, 4));
             builder.push_rect(&info, &space_and_clip2, ColorF::new(0.5, 0.5, 0.5, 1.0));
 
--- a/gfx/wr/webrender/src/clip_scroll_tree.rs
+++ b/gfx/wr/webrender/src/clip_scroll_tree.rs
@@ -428,25 +428,27 @@ impl ClipScrollTree {
         &mut self,
         parent_index: SpatialNodeIndex,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
         frame_kind: ScrollFrameKind,
+        external_scroll_offset: LayoutVector2D,
     ) -> SpatialNodeIndex {
         let node = SpatialNode::new_scroll_frame(
             pipeline_id,
             parent_index,
             external_id,
             frame_rect,
             content_size,
             scroll_sensitivity,
             frame_kind,
+            external_scroll_offset,
         );
         self.add_spatial_node(node)
     }
 
     pub fn add_reference_frame(
         &mut self,
         parent_index: Option<SpatialNodeIndex>,
         transform_style: TransformStyle,
--- a/gfx/wr/webrender/src/display_list_flattener.rs
+++ b/gfx/wr/webrender/src/display_list_flattener.rs
@@ -626,16 +626,17 @@ impl<'a> DisplayListFlattener<'a> {
             info.scroll_frame_id,
             parent_node_index,
             info.external_id,
             pipeline_id,
             &frame_rect,
             &content_size,
             info.scroll_sensitivity,
             ScrollFrameKind::Explicit,
+            info.external_scroll_offset,
         );
     }
 
     fn flatten_reference_frame(
         &mut self,
         traversal: &mut BuiltDisplayListIter<'a>,
         pipeline_id: PipelineId,
         parent_spatial_node: SpatialNodeIndex,
@@ -781,16 +782,17 @@ impl<'a> DisplayListFlattener<'a> {
             SpatialId::root_scroll_node(iframe_pipeline_id),
             spatial_node_index,
             Some(ExternalScrollId(0, iframe_pipeline_id)),
             iframe_pipeline_id,
             &iframe_rect,
             &pipeline.content_size,
             ScrollSensitivity::ScriptAndInputEvents,
             ScrollFrameKind::PipelineRoot,
+            LayoutVector2D::zero(),
         );
 
         self.rf_mapper.push_scope();
         self.flatten_items(
             &mut pipeline.display_list.iter(),
             pipeline.pipeline_id,
             true,
         );
@@ -1798,16 +1800,17 @@ impl<'a> DisplayListFlattener<'a> {
             SpatialId::root_scroll_node(pipeline_id),
             spatial_node_index,
             Some(ExternalScrollId(0, pipeline_id)),
             pipeline_id,
             &LayoutRect::new(LayoutPoint::zero(), *viewport_size),
             content_size,
             ScrollSensitivity::ScriptAndInputEvents,
             ScrollFrameKind::PipelineRoot,
+            LayoutVector2D::zero(),
         );
     }
 
     pub fn add_clip_node<I>(
         &mut self,
         new_node_id: ClipId,
         space_and_clip: &SpaceAndClipInfo,
         clip_region: ClipRegion<I>,
@@ -1895,25 +1898,27 @@ impl<'a> DisplayListFlattener<'a> {
         new_node_id: SpatialId,
         parent_node_index: SpatialNodeIndex,
         external_id: Option<ExternalScrollId>,
         pipeline_id: PipelineId,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
         frame_kind: ScrollFrameKind,
+        external_scroll_offset: LayoutVector2D,
     ) -> SpatialNodeIndex {
         let node_index = self.clip_scroll_tree.add_scroll_frame(
             parent_node_index,
             external_id,
             pipeline_id,
             frame_rect,
             content_size,
             scroll_sensitivity,
             frame_kind,
+            external_scroll_offset,
         );
         self.id_to_index_mapper.map_spatial_node(new_node_id, node_index);
         node_index
     }
 
     pub fn push_shadow(
         &mut self,
         shadow: Shadow,
--- a/gfx/wr/webrender/src/spatial_node.rs
+++ b/gfx/wr/webrender/src/spatial_node.rs
@@ -120,26 +120,28 @@ impl SpatialNode {
     pub fn new_scroll_frame(
         pipeline_id: PipelineId,
         parent_index: SpatialNodeIndex,
         external_id: Option<ExternalScrollId>,
         frame_rect: &LayoutRect,
         content_size: &LayoutSize,
         scroll_sensitivity: ScrollSensitivity,
         frame_kind: ScrollFrameKind,
+        external_scroll_offset: LayoutVector2D,
     ) -> Self {
         let node_type = SpatialNodeType::ScrollFrame(ScrollFrameInfo::new(
                 *frame_rect,
                 scroll_sensitivity,
                 LayoutSize::new(
                     (content_size.width - frame_rect.size.width).max(0.0),
                     (content_size.height - frame_rect.size.height).max(0.0)
                 ),
                 external_id,
                 frame_kind,
+                external_scroll_offset,
             )
         );
 
         Self::new(pipeline_id, Some(parent_index), node_type)
     }
 
     pub fn new_reference_frame(
         parent_index: Option<SpatialNodeIndex>,
@@ -650,34 +652,40 @@ pub struct ScrollFrameInfo {
     /// Stores whether this is a scroll frame added implicitly by WR when adding
     /// a pipeline (either the root or an iframe). We need to exclude these
     /// when searching for scroll roots we care about for picture caching.
     /// TODO(gw): I think we can actually completely remove the implicit
     ///           scroll frame being added by WR, and rely on the embedder
     ///           to define scroll frames. However, that involves API changes
     ///           so we will use this as a temporary hack!
     pub frame_kind: ScrollFrameKind,
+
+    /// Amount that visual components attached to this scroll node have been
+    /// pre-scrolled in their local coordinates.
+    pub external_scroll_offset: LayoutVector2D,
 }
 
 /// Manages scrolling offset.
 impl ScrollFrameInfo {
     pub fn new(
         viewport_rect: LayoutRect,
         scroll_sensitivity: ScrollSensitivity,
         scrollable_size: LayoutSize,
         external_id: Option<ExternalScrollId>,
         frame_kind: ScrollFrameKind,
+        external_scroll_offset: LayoutVector2D,
     ) -> ScrollFrameInfo {
         ScrollFrameInfo {
             viewport_rect,
             offset: LayoutVector2D::zero(),
             scroll_sensitivity,
             scrollable_size,
             external_id,
             frame_kind,
+            external_scroll_offset,
         }
     }
 
     pub fn sensitive_to_input_events(&self) -> bool {
         match self.scroll_sensitivity {
             ScrollSensitivity::ScriptAndInputEvents => true,
             ScrollSensitivity::Script => false,
         }
@@ -689,16 +697,17 @@ impl ScrollFrameInfo {
     ) -> ScrollFrameInfo {
         ScrollFrameInfo {
             viewport_rect: self.viewport_rect,
             offset: old_scroll_info.offset,
             scroll_sensitivity: self.scroll_sensitivity,
             scrollable_size: self.scrollable_size,
             external_id: self.external_id,
             frame_kind: self.frame_kind,
+            external_scroll_offset: self.external_scroll_offset,
         }
     }
 }
 
 /// Contains information about reference frames.
 #[derive(Copy, Clone, Debug)]
 pub struct ReferenceFrameInfo {
     /// The source transform and perspective matrices provided by the stacking context
--- a/gfx/wr/webrender_api/src/display_item.rs
+++ b/gfx/wr/webrender_api/src/display_item.rs
@@ -235,17 +235,17 @@ pub struct ScrollFrameDisplayItem {
     pub external_id: Option<ExternalScrollId>,
     pub image_mask: Option<ImageMask>,
     pub scroll_sensitivity: ScrollSensitivity,
     /// The amount this scrollframe has already been scrolled by, in the caller.
     /// This means that all the display items that are inside the scrollframe
     /// will have their coordinates shifted by this amount, and this offset
     /// should be added to those display item coordinates in order to get a
     /// normalized value that is consistent across display lists.
-    pub external_scroll_offset: LayoutPoint,
+    pub external_scroll_offset: LayoutVector2D,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct RectangleDisplayItem {
     pub color: ColorF,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
--- a/gfx/wr/webrender_api/src/display_list.rs
+++ b/gfx/wr/webrender_api/src/display_list.rs
@@ -1463,17 +1463,17 @@ impl DisplayListBuilder {
         &mut self,
         parent_space_and_clip: &di::SpaceAndClipInfo,
         external_id: Option<di::ExternalScrollId>,
         content_rect: LayoutRect,
         clip_rect: LayoutRect,
         complex_clips: I,
         image_mask: Option<di::ImageMask>,
         scroll_sensitivity: di::ScrollSensitivity,
-        external_scroll_offset: LayoutPoint,
+        external_scroll_offset: LayoutVector2D,
     ) -> di::SpaceAndClipInfo
     where
         I: IntoIterator<Item = di::ComplexClipRegion>,
         I::IntoIter: ExactSizeIterator + Clone,
     {
         let clip_id = self.generate_clip_index();
         let scroll_frame_id = self.generate_spatial_index();
         let item = di::SpecificDisplayItem::ScrollFrame(di::ScrollFrameDisplayItem {
--- a/gfx/wr/wrench/src/yaml_frame_reader.rs
+++ b/gfx/wr/wrench/src/yaml_frame_reader.rs
@@ -1500,17 +1500,17 @@ impl YamlFrameReader {
         wrench: &mut Wrench,
         yaml: &Yaml,
     ) {
         let clip_rect = yaml["bounds"]
             .as_rect()
             .expect("scroll frame must have a bounds");
         let content_size = yaml["content-size"].as_size().unwrap_or(clip_rect.size);
         let content_rect = LayoutRect::new(clip_rect.origin, content_size);
-        let external_scroll_offset = yaml["external-scroll-offset"].as_point().unwrap_or(LayoutPoint::zero());
+        let external_scroll_offset = yaml["external-scroll-offset"].as_vector().unwrap_or(LayoutVector2D::zero());
 
         let numeric_id = yaml["id"].as_i64().map(|id| id as u64);
 
         let complex_clips = self.to_complex_clip_regions(&yaml["complex"]);
         let image_mask = self.to_image_mask(&yaml["image-mask"], wrench);
 
         let external_id =  yaml["scroll-offset"].as_point().map(|size| {
             let id = ExternalScrollId((self.scroll_offsets.len() + 1) as u64, dl.pipeline_id);
--- a/gfx/wr/wrench/src/yaml_frame_writer.rs
+++ b/gfx/wr/wrench/src/yaml_frame_writer.rs
@@ -1130,17 +1130,17 @@ impl YamlFrameWriter {
                         yaml_node(&mut v, "parent", clip_id_mapper.map_clip_id(&parent));
                     }
                 }
                 Sdi::ScrollFrame(item) => {
                     str_node(&mut v, "type", "scroll-frame");
                     usize_node(&mut v, "id", clip_id_mapper.add_spatial_id(item.scroll_frame_id));
                     size_node(&mut v, "content-size", &base.rect().size);
                     rect_node(&mut v, "bounds", &base.clip_rect());
-                    point_node(&mut v, "external-scroll-offset", &item.external_scroll_offset);
+                    vector_node(&mut v, "external-scroll-offset", &item.external_scroll_offset);
 
                     let (complex_clips, complex_clip_count) = base.complex_clip();
                     if let Some(complex) = self.make_complex_clips_node(
                         complex_clip_count,
                         complex_clips,
                         display_list,
                     ) {
                         yaml_node(&mut v, "complex", complex);
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -278,16 +278,26 @@ typedef enum JSGCParamKey {
   /**
    * If the above condition is met, then any object group that tenures more than
    * this number of objects will be pretenured (if it can be).
    *
    * Default: PretenureGroupThreshold
    * Pref: None
    */
   JSGC_PRETENURE_GROUP_THRESHOLD = 29,
+
+  /**
+   * Attempt to run a minor GC in the idle time if the free space falls
+   * below this percentage (from 0 to 99).
+   *
+   * Default: 25
+   * Pref: None
+   */
+  JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION_PERCENT = 30,
+
 } JSGCParamKey;
 
 /*
  * Generic trace operation that calls JS::TraceEdge on each traceable thing's
  * location reachable from data.
  */
 typedef void (*JSTraceDataOp)(JSTracer* trc, void* data);
 
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -342,18 +342,21 @@ static const JSGCMode Mode = JSGC_MODE_I
 
 /* JSGC_COMPACTING_ENABLED */
 static const bool CompactingEnabled = true;
 
 /* JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION */
 static const uint32_t NurseryFreeThresholdForIdleCollection =
     Nursery::NurseryChunkUsableSize / 4;
 
+/* JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION_PERCENT */
+static const float NurseryFreeThresholdForIdleCollectionFraction = 0.25f;
+
 /* JSGC_PRETENURE_THRESHOLD */
-static const float PretenureThreashold = 0.6f;
+static const float PretenureThreshold = 0.6f;
 
 /* JSGC_PRETENURE_GROUP_THRESHOLD */
 static const float PretenureGroupThreshold = 3000;
 
 }  // namespace TuningDefaults
 }  // namespace gc
 }  // namespace js
 
@@ -1471,16 +1474,22 @@ bool GCSchedulingTunables::setParameter(
       setMaxEmptyChunkCount(value);
       break;
     case JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION:
       if (value > gcMaxNurseryBytes()) {
         value = gcMaxNurseryBytes();
       }
       nurseryFreeThresholdForIdleCollection_ = value;
       break;
+    case JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION_PERCENT:
+      if (value == 0 || value > 100) {
+        return false;
+      }
+      nurseryFreeThresholdForIdleCollectionFraction_ = value / 100.0f;
+      break;
     case JSGC_PRETENURE_THRESHOLD: {
       // 100 disables pretenuring
       if (value == 0 || value > 100) {
         return false;
       }
       pretenureThreshold_ = value / 100.0f;
       break;
     }
@@ -1573,17 +1582,19 @@ GCSchedulingTunables::GCSchedulingTunabl
       highFrequencyHeapGrowthMax_(TuningDefaults::HighFrequencyHeapGrowthMax),
       highFrequencyHeapGrowthMin_(TuningDefaults::HighFrequencyHeapGrowthMin),
       lowFrequencyHeapGrowth_(TuningDefaults::LowFrequencyHeapGrowth),
       dynamicMarkSliceEnabled_(TuningDefaults::DynamicMarkSliceEnabled),
       minEmptyChunkCount_(TuningDefaults::MinEmptyChunkCount),
       maxEmptyChunkCount_(TuningDefaults::MaxEmptyChunkCount),
       nurseryFreeThresholdForIdleCollection_(
           TuningDefaults::NurseryFreeThresholdForIdleCollection),
-      pretenureThreshold_(TuningDefaults::PretenureThreashold),
+      nurseryFreeThresholdForIdleCollectionFraction_(
+          TuningDefaults::NurseryFreeThresholdForIdleCollectionFraction),
+      pretenureThreshold_(TuningDefaults::PretenureThreshold),
       pretenureGroupThreshold_(TuningDefaults::PretenureGroupThreshold) {}
 
 void GCRuntime::resetParameter(JSGCParamKey key, AutoLockGC& lock) {
   switch (key) {
     case JSGC_MAX_MALLOC_BYTES:
       setMaxMallocBytes(TuningDefaults::MaxMallocBytes, lock);
       break;
     case JSGC_SLICE_TIME_BUDGET:
@@ -1656,18 +1667,22 @@ void GCSchedulingTunables::resetParamete
       break;
     case JSGC_MAX_EMPTY_CHUNK_COUNT:
       setMaxEmptyChunkCount(TuningDefaults::MaxEmptyChunkCount);
       break;
     case JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION:
       nurseryFreeThresholdForIdleCollection_ =
           TuningDefaults::NurseryFreeThresholdForIdleCollection;
       break;
+    case JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION_PERCENT:
+      nurseryFreeThresholdForIdleCollectionFraction_ =
+          TuningDefaults::NurseryFreeThresholdForIdleCollectionFraction;
+      break;
     case JSGC_PRETENURE_THRESHOLD:
-      pretenureThreshold_ = TuningDefaults::PretenureThreashold;
+      pretenureThreshold_ = TuningDefaults::PretenureThreshold;
       break;
     case JSGC_PRETENURE_GROUP_THRESHOLD:
       pretenureGroupThreshold_ = TuningDefaults::PretenureGroupThreshold;
       break;
     default:
       MOZ_CRASH("Unknown GC parameter.");
   }
 }
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -693,18 +693,45 @@ inline void js::Nursery::startProfile(Pr
 }
 
 inline void js::Nursery::endProfile(ProfileKey key) {
   profileDurations_[key] = ReallyNow() - startTimes_[key];
   totalDurations_[key] += profileDurations_[key];
 }
 
 bool js::Nursery::shouldCollect() const {
-  uint32_t threshold = tunables().nurseryFreeThresholdForIdleCollection();
-  return !isEmpty() && (minorGCRequested() || freeSpace() < threshold);
+  if (minorGCRequested()) {
+    return true;
+  }
+
+  bool belowBytesThreshold =
+      freeSpace() < tunables().nurseryFreeThresholdForIdleCollection();
+  bool belowFractionThreshold =
+      float(freeSpace()) / float(capacity()) <
+      tunables().nurseryFreeThresholdForIdleCollectionFraction();
+
+  // We want to use belowBytesThreshold when the nursery is sufficiently large,
+  // and belowFractionThreshold when it's small.
+  //
+  // When the nursery is small then belowBytesThreshold is a lower threshold
+  // (triggered earlier) than belowFractionThreshold.  So if the fraction
+  // threshold is true, the bytes one will be true also.  The opposite is true
+  // when the nursery is large.
+  //
+  // Therefore, by the time we cross the threshold we care about, we've already
+  // crossed the other one, and we can boolean AND to use either condition
+  // without encoding any "is the nursery big/small" test/threshold.  The point
+  // at which they cross is when the nursery is:  BytesThreshold /
+  // FractionThreshold large.
+  //
+  // With defaults that's:
+  //
+  //   1MB = 256KB / 0.25
+  //
+  return belowBytesThreshold && belowFractionThreshold;
 }
 
 static inline bool IsFullStoreBufferReason(JS::GCReason reason) {
   return reason == JS::GCReason::FULL_WHOLE_CELL_BUFFER ||
          reason == JS::GCReason::FULL_GENERIC_BUFFER ||
          reason == JS::GCReason::FULL_VALUE_BUFFER ||
          reason == JS::GCReason::FULL_CELL_PTR_BUFFER ||
          reason == JS::GCReason::FULL_SLOT_BUFFER ||
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -137,35 +137,35 @@ inline bool CanNurseryAllocateFinalizedC
 }
 
 class Nursery {
  public:
   static const size_t Alignment = gc::ChunkSize;
   static const size_t ChunkShift = gc::ChunkShift;
 
   /*
+   * SubChunkStep is the minimum amount to adjust the nursery's size by.
+   */
+  static const size_t SubChunkStep = gc::ArenaSize;
+
+  /*
    * SubChunkLimit is the lower limit of the nursery's capacity.
-   * SubChunkStep is the minimum amount to adjust the nursery's size by (to
-   * avoid too many size adjustments and allow quicker changes in size than eg:
-   * 4k).
    */
 #ifndef JS_GC_SMALL_CHUNK_SIZE
   /*
    * 192K is conservative, not too low that root marking dominates.  The Limit
    * should be a multiple of the Step.
    */
   static const size_t SubChunkLimit = 192 * 1024;
-  static const size_t SubChunkStep = 64 * 1024;
 #else
   /*
    * With small chunk sizes (256K) we need to use smaller sub chunk limits and
    * steps so that a full chunk minus one step is still larger than the limit.
    */
   static const size_t SubChunkLimit = 64 * 1024;
-  static const size_t SubChunkStep = 16 * 1024;
 #endif
 
   struct alignas(gc::CellAlignBytes) CellAlignedByte {
     char byte;
   };
 
   struct StringLayout {
     JS::Zone* zone;
--- a/js/src/gc/Scheduling.h
+++ b/js/src/gc/Scheduling.h
@@ -422,21 +422,24 @@ class GCSchedulingTunables {
    *
    * Controls the number of empty chunks reserved for future allocation.
    */
   UnprotectedData<uint32_t> minEmptyChunkCount_;
   UnprotectedData<uint32_t> maxEmptyChunkCount_;
 
   /*
    * JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION
+   * JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION_FRACTION
    *
    * Attempt to run a minor GC in the idle time if the free space falls
-   * below this threshold.
+   * below this threshold. The absolute threshold is used when the nursery is
+   * large and the percentage when it is small.  See Nursery::shouldCollect()
    */
   UnprotectedData<uint32_t> nurseryFreeThresholdForIdleCollection_;
+  UnprotectedData<float> nurseryFreeThresholdForIdleCollectionFraction_;
 
   /*
    * JSGC_PRETENURE_THRESHOLD
    *
    * Fraction of objects tenured to trigger pretenuring (between 0 and 1). If
    * this fraction is met, the GC proceeds to calculate which objects will be
    * tenured. If this is 1.0f (actually if it is not < 1.0f) then pretenuring
    * is disabled.
@@ -483,16 +486,19 @@ class GCSchedulingTunables {
   bool isDynamicMarkSliceEnabled() const { return dynamicMarkSliceEnabled_; }
   unsigned minEmptyChunkCount(const AutoLockGC&) const {
     return minEmptyChunkCount_;
   }
   unsigned maxEmptyChunkCount() const { return maxEmptyChunkCount_; }
   uint32_t nurseryFreeThresholdForIdleCollection() const {
     return nurseryFreeThresholdForIdleCollection_;
   }
+  float nurseryFreeThresholdForIdleCollectionFraction() const {
+    return nurseryFreeThresholdForIdleCollectionFraction_;
+  }
 
   bool attemptPretenuring() const { return pretenureThreshold_ < 1.0f; }
   float pretenureThreshold() const { return pretenureThreshold_; }
   uint32_t pretenureGroupThreshold() const { return pretenureGroupThreshold_; }
 
   MOZ_MUST_USE bool setParameter(JSGCParamKey key, uint32_t value,
                                  const AutoLockGC& lock);
   void resetParameter(JSGCParamKey key, const AutoLockGC& lock);
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -202,17 +202,17 @@ load 1383319.html
 pref(dom.animations-api.implicit-keyframes.enabled,true) load 1383589-1.html
 load 1383975.html
 load border-image-visited-link.html
 load content-only-on-link-before.html
 load content-only-on-visited-before.html
 load font-face-truncated-src.html
 load large_border_image_width.html
 load link-transition-before.html
-load long-url-list-stack-overflow.html
+skip-if(winWidget&&isDebugBuild&&/^Windows\x20NT\x206\.1/.test(http.oscpu)) load long-url-list-stack-overflow.html #Bug 1525117
 skip-if(Android&&AndroidVersion<21) load 1383981.html
 skip-if(Android&&AndroidVersion<21) load 1383981-2.html
 skip-if(Android&&AndroidVersion<21) load 1383981-3.html
 load 1384824-1.html
 load 1384824-2.html
 load 1386773.html
 load 1387481-1.html
 load 1387499.html
--- a/mobile/android/themes/core/jar.mn
+++ b/mobile/android/themes/core/jar.mn
@@ -1,33 +1,27 @@
 #filter substitution
 # 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/.
 
 chrome.jar:
 % skin browser classic/1.0 %skin/
   skin/aboutPage.css                        (aboutPage.css)
-  skin/about.css                            (about.css)
   skin/aboutAccounts.css                    (aboutAccounts.css)
   skin/aboutAddons.css                      (aboutAddons.css)
   skin/aboutBase.css                        (aboutBase.css)
   skin/aboutDownloads.css                   (aboutDownloads.css)
   skin/aboutExperiments.css                 (aboutExperiments.css)
-  skin/aboutMemory.css                      (aboutMemory.css)
   skin/aboutPrivateBrowsing.css             (aboutPrivateBrowsing.css)
   skin/aboutReader.css                      (aboutReader.css)
-  skin/aboutSupport.css                     (aboutSupport.css)
   skin/defines.css                          (defines.css)
   skin/netError.css                         (netError.css)
   skin/spinner.css                          (spinner.css)
-% override chrome://global/skin/about.css chrome://browser/skin/about.css
-% override chrome://global/skin/aboutMemory.css chrome://browser/skin/aboutMemory.css
 % override chrome://global/skin/aboutReader.css chrome://browser/skin/aboutReader.css
-% override chrome://global/skin/aboutSupport.css chrome://browser/skin/aboutSupport.css
 % override chrome://global/skin/netError.css chrome://browser/skin/netError.css
 
   skin/aboutLogins.css                      (aboutLogins.css)
 
   skin/images/amo-logo.png                  (images/amo-logo.png)
   skin/images/blocked-warning.png           (images/blocked-warning.png)
   skin/images/checkbox_checked.png          (images/checkbox_checked.png)
   skin/images/checkbox_checked_disabled.png (images/checkbox_checked_disabled.png)
--- a/modules/libjar/nsZipArchive.cpp
+++ b/modules/libjar/nsZipArchive.cpp
@@ -606,19 +606,19 @@ nsresult nsZipArchive::BuildFileList(PRF
     if (readaheadLength) {
 #if defined(XP_SOLARIS)
       posix_madvise(const_cast<uint8_t *>(startp), readaheadLength,
                     POSIX_MADV_WILLNEED);
 #elif defined(XP_UNIX)
       madvise(const_cast<uint8_t *>(startp), readaheadLength, MADV_WILLNEED);
 #elif defined(XP_WIN)
       static auto prefetchVirtualMemory =
-          reinterpret_cast<BOOL (*)(HANDLE, ULONG_PTR, PVOID, ULONG)>(
+          reinterpret_cast<BOOL (WINAPI *)(HANDLE, ULONG_PTR, PVOID, ULONG)>(
               GetProcAddress(GetModuleHandle(L"kernel32.dll"),
-                               "PrefetchVirtualMemory"));
+                             "PrefetchVirtualMemory"));
       if (prefetchVirtualMemory) {
         // Normally, we'd use WIN32_MEMORY_RANGE_ENTRY, but that requires
         // a different _WIN32_WINNT value before including windows.h, but
         // that causes complications with unified sources. It's a simple
         // enough struct anyways.
         struct {
           PVOID VirtualAddress;
           SIZE_T NumberOfBytes;
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -314,16 +314,22 @@ VARCACHE_PREF(
 
 VARCACHE_PREF(
   "dom.webnotifications.enabled",
    dom_webnotifications_enabled,
   RelaxedAtomicBool, true
 )
 
 VARCACHE_PREF(
+  "dom.webnotifications.allowinsecure",
+   dom_webnotifications_allowinsecure,
+  RelaxedAtomicBool, false
+)
+
+VARCACHE_PREF(
   "dom.webnotifications.serviceworker.enabled",
    dom_webnotifications_serviceworker_enabled,
   RelaxedAtomicBool, true
 )
 
 #ifdef NIGHTLY_BUILD
 # define PREF_VALUE  true
 #else
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -6017,12 +6017,8 @@ pref("dom.datatransfer.mozAtAPIs", true)
 #endif
 
 // Whether or not Prio is supported on this platform.
 pref("prio.enabled", false);
 
 // External.AddSearchProvider is deprecated and it will be removed in the next
 // cycles.
 pref("dom.sidebar.enabled", true);
-
-#if defined(MOZ_WIDGET_GTK)
-pref("widget.default-hidden-titlebar", true);
-#endif
--- a/python/mozboot/mozboot/android.py
+++ b/python/mozboot/mozboot/android.py
@@ -55,18 +55,17 @@ ac_add_options --enable-application=mobi
 # For x86 emulators (and x86 devices, which are uncommon):
 # ac_add_options --target=i686
 # For newer phones.
 # ac_add_options --target=aarch64
 # For x86_64 emulators (and x86_64 devices, which are even less common):
 # ac_add_options --target=x86_64
 
 {extra_lines}
-# With the following Android SDK and NDK:
-ac_add_options --with-android-sdk="{sdk_path}"
+# With the following Android NDK:
 ac_add_options --with-android-ndk="{ndk_path}"
 
 # With the following compiler toolchain:
 CC="{moz_state_dir}/clang/bin/clang"
 CXX="{moz_state_dir}/clang/bin/clang++"
 <<<
 '''
 
@@ -76,19 +75,16 @@ Paste the lines between the chevrons (>>
 
 >>>
 # Build GeckoView/Firefox for Android Artifact Mode:
 ac_add_options --enable-application=mobile/android
 ac_add_options --target=arm-linux-androideabi
 ac_add_options --enable-artifact-builds
 
 {extra_lines}
-# With the following Android SDK:
-ac_add_options --with-android-sdk="{sdk_path}"
-
 # Write build artifacts to:
 mk_add_options MOZ_OBJDIR=./objdir-frontend
 <<<
 '''
 
 
 def install_mobile_android_sdk_or_ndk(url, path):
     '''
--- a/python/mozbuild/mozbuild/action/package_fennec_apk.py
+++ b/python/mozbuild/mozbuild/action/package_fennec_apk.py
@@ -25,17 +25,17 @@ import mozpack.path as mozpath
 
 
 def package_fennec_apk(inputs=[], omni_ja=None,
                        lib_dirs=[],
                        assets_dirs=[],
                        features_dirs=[],
                        root_files=[],
                        verbose=False):
-    jarrer = Jarrer(optimize=False)
+    jarrer = Jarrer()
 
     # First, take input files.  The contents of the later files overwrites the
     # content of earlier files.  Multidexing requires special care: we want a
     # coherent set of classesN.dex files, so we only take DEX files from a
     # single input.  This avoids taking, say, classes{1,2,3}.dex from the first
     # input and only classes{1,2}.dex from the second input, leading to
     # (potentially) duplicated symbols at runtime.
     last_input_with_dex_files = None
--- a/python/mozbuild/mozbuild/action/symbols_archive.py
+++ b/python/mozbuild/mozbuild/action/symbols_archive.py
@@ -14,17 +14,17 @@ import mozpack.path as mozpath
 
 def make_archive(archive_name, base, exclude, include):
     compress = ['**/*.sym']
     finder = FileFinder(base, ignore=exclude)
     if not include:
         include = ['*']
     archive_basename = os.path.basename(archive_name)
     with open(archive_name, 'wb') as fh:
-        with JarWriter(fileobj=fh, optimize=False, compress_level=5) as writer:
+        with JarWriter(fileobj=fh, compress_level=5) as writer:
             for pat in include:
                 for p, f in finder.find(pat):
                     print('  Adding to "%s":\n\t"%s"' % (archive_basename, p))
                     should_compress = any(mozpath.match(p, pat) for pat in compress)
                     writer.add(p.encode('utf-8'), f, mode=f.mode,
                                compress=should_compress, skip_duplicates=True)
 
 def main(argv):
--- a/python/mozbuild/mozbuild/action/test_archive.py
+++ b/python/mozbuild/mozbuild/action/test_archive.py
@@ -770,17 +770,17 @@ def main(argv):
         # marginally larger sizes than higher values and is the sweet spot
         # for optimal compression. Read the detailed commit message that
         # introduced this for raw numbers.
         if out_file.endswith('.tar.gz'):
             files = dict(res)
             create_tar_gz_from_files(fh, files, compresslevel=5)
             file_count = len(files)
         elif out_file.endswith('.zip'):
-            with JarWriter(fileobj=fh, optimize=False, compress_level=5) as writer:
+            with JarWriter(fileobj=fh, compress_level=5) as writer:
                 for p, f in res:
                     writer.add(p.encode('utf-8'), f.read(), mode=f.mode,
                                skip_duplicates=True)
                     file_count += 1
         else:
             raise Exception('unhandled file extension: %s' % out_file)
 
     duration = time.time() - t_start
--- a/python/mozbuild/mozbuild/action/zip.py
+++ b/python/mozbuild/mozbuild/action/zip.py
@@ -26,17 +26,17 @@ def main(args):
                         help="Strip executables")
     parser.add_argument("-x", metavar='EXCLUDE', default=[], action='append',
                         help="Exclude files that match the pattern")
     parser.add_argument("zip", help="Path to zip file to write")
     parser.add_argument("input", nargs="+",
                         help="Path to files to add to zip")
     args = parser.parse_args(args)
 
-    jarrer = Jarrer(optimize=False)
+    jarrer = Jarrer()
 
     with errors.accumulate():
         finder = FileFinder(args.C, find_executables=args.strip)
         for path in args.input:
             for p, f in finder.find(path):
                 if not any([match(p, exclude) for exclude in args.x]):
                     jarrer.add(p, f)
         jarrer.copy(mozpath.join(args.C, args.zip))
--- a/python/mozbuild/mozbuild/artifacts.py
+++ b/python/mozbuild/mozbuild/artifacts.py
@@ -209,17 +209,17 @@ class ArtifactJob(object):
 
     def process_package_artifact(self, filename, processed_filename):
         raise NotImplementedError("Subclasses must specialize process_package_artifact!")
 
     def process_tests_zip_artifact(self, filename, processed_filename):
         from mozbuild.action.test_archive import OBJDIR_TEST_FILES
         added_entry = False
 
-        with JarWriter(file=processed_filename, optimize=False, compress_level=5) as writer:
+        with JarWriter(file=processed_filename, compress_level=5) as writer:
             reader = JarReader(filename)
             for filename, entry in reader.entries.iteritems():
                 for pattern, (src_prefix, dest_prefix) in self.test_artifact_patterns:
                     if not mozpath.match(filename, pattern):
                         continue
                     destpath = mozpath.relpath(filename, src_prefix)
                     destpath = mozpath.join(dest_prefix, destpath)
                     self.log(logging.INFO, 'artifact',
@@ -245,17 +245,17 @@ class ArtifactJob(object):
             raise ValueError('Archive format changed! No pattern from "{patterns}"'
                              'matched an archive path.'.format(
                                  patterns=LinuxArtifactJob.test_artifact_patterns))
 
     def process_tests_tar_artifact(self, filename, processed_filename):
         from mozbuild.action.test_archive import OBJDIR_TEST_FILES
         added_entry = False
 
-        with JarWriter(file=processed_filename, optimize=False, compress_level=5) as writer:
+        with JarWriter(file=processed_filename, compress_level=5) as writer:
             with tarfile.open(filename) as reader:
                 for filename, entry in TarFinder(filename, reader):
                     for pattern, (src_prefix, dest_prefix) in self.test_artifact_patterns:
                         if not mozpath.match(filename, pattern):
                             continue
 
                         destpath = mozpath.relpath(filename, src_prefix)
                         destpath = mozpath.join(dest_prefix, destpath)
@@ -279,27 +279,27 @@ class ArtifactJob(object):
                             writer.add(destpath.encode('utf-8'), entry.open(), mode=mode)
 
         if not added_entry:
             raise ValueError('Archive format changed! No pattern from "{patterns}"'
                              'matched an archive path.'.format(
                                  patterns=LinuxArtifactJob.test_artifact_patterns))
 
     def process_symbols_archive(self, filename, processed_filename):
-        with JarWriter(file=processed_filename, optimize=False, compress_level=5) as writer:
+        with JarWriter(file=processed_filename, compress_level=5) as writer:
             reader = JarReader(filename)
             for filename in reader.entries:
                 destpath = mozpath.join('crashreporter-symbols', filename)
                 self.log(logging.INFO, 'artifact',
                          {'destpath': destpath},
                          'Adding {destpath} to processed archive')
                 writer.add(destpath.encode('utf-8'), reader[filename])
 
     def process_host_bin(self, filename, processed_filename):
-        with JarWriter(file=processed_filename, optimize=False, compress_level=5) as writer:
+        with JarWriter(file=processed_filename, compress_level=5) as writer:
             # Turn 'HASH-mar.exe' into 'mar.exe'.  `filename` is a path on disk
             # without any of the path parts of the artifact, so we must inject
             # the desired `host/bin` prefix here.
             orig_basename = os.path.basename(filename).split('-', 1)[1]
             destpath = mozpath.join('host/bin', orig_basename)
             writer.add(destpath.encode('utf-8'), open(filename, 'rb'))
 
 
@@ -310,17 +310,17 @@ class AndroidArtifactJob(ArtifactJob):
     package_artifact_patterns = {
         'application.ini',
         'platform.ini',
         '**/*.so',
     }
 
     def process_package_artifact(self, filename, processed_filename):
         # Extract all .so files into the root, which will get copied into dist/bin.
-        with JarWriter(file=processed_filename, optimize=False, compress_level=5) as writer:
+        with JarWriter(file=processed_filename, compress_level=5) as writer:
             for p, f in UnpackFinder(JarFinder(filename, JarReader(filename))):
                 if not any(mozpath.match(p, pat) for pat in self.package_artifact_patterns):
                     continue
 
                 dirname, basename = os.path.split(p)
                 self.log(logging.INFO, 'artifact',
                     {'basename': basename},
                    'Adding {basename} to processed archive')
@@ -348,17 +348,17 @@ class LinuxArtifactJob(ArtifactJob):
         'firefox/plugin-container',
         'firefox/updater',
         'firefox/**/*.so',
     }
 
     def process_package_artifact(self, filename, processed_filename):
         added_entry = False
 
-        with JarWriter(file=processed_filename, optimize=False, compress_level=5) as writer:
+        with JarWriter(file=processed_filename, compress_level=5) as writer:
             with tarfile.open(filename) as reader:
                 for p, f in UnpackFinder(TarFinder(filename, reader)):
                     if not any(mozpath.match(p, pat) for pat in self.package_artifact_patterns):
                         continue
 
                     # We strip off the relative "firefox/" bit from the path,
                     # but otherwise preserve it.
                     destpath = mozpath.join('bin',
@@ -444,17 +444,17 @@ class MacArtifactJob(ArtifactJob):
                     'dependentlibs.list',
                     # 'firefox',
                     'gmp-clearkey/0.1/libclearkey.dylib',
                     # 'gmp-fake/1.0/libfake.dylib',
                     # 'gmp-fakeopenh264/1.0/libfakeopenh264.dylib',
                 ]),
             ]
 
-            with JarWriter(file=processed_filename, optimize=False, compress_level=5) as writer:
+            with JarWriter(file=processed_filename, compress_level=5) as writer:
                 root, paths = paths_no_keep_path
                 finder = UnpackFinder(mozpath.join(source, root))
                 for path in paths:
                     for p, f in finder.find(path):
                         self.log(logging.INFO, 'artifact',
                             {'path': p},
                             'Adding {path} to processed archive')
                         destpath = mozpath.join('bin', os.path.basename(p))
@@ -509,17 +509,17 @@ class WinArtifactJob(ArtifactJob):
         ('bin/xpcshell.exe', ('bin', 'bin')),
         ('bin/plugins/gmp-*/*/*', ('bin/plugins', 'bin')),
         ('bin/plugins/*', ('bin/plugins', 'plugins')),
         ('bin/components/*', ('bin/components', 'bin/components')),
     }
 
     def process_package_artifact(self, filename, processed_filename):
         added_entry = False
-        with JarWriter(file=processed_filename, optimize=False, compress_level=5) as writer:
+        with JarWriter(file=processed_filename, compress_level=5) as writer:
             for p, f in UnpackFinder(JarFinder(filename, JarReader(filename))):
                 if not any(mozpath.match(p, pat) for pat in self.package_artifact_patterns):
                     continue
 
                 # strip off the relative "firefox/" bit from the path:
                 basename = mozpath.relpath(p, "firefox")
                 basename = mozpath.join('bin', basename)
                 self.log(logging.INFO, 'artifact',
--- a/python/mozbuild/mozbuild/codecoverage/packager.py
+++ b/python/mozbuild/mozbuild/codecoverage/packager.py
@@ -38,17 +38,17 @@ def describe_install_manifest(manifest, 
 
 
 def package_coverage_data(root, output_file):
     # XXX JarWriter doesn't support unicode strings, see bug 1056859
     if isinstance(root, unicode):
         root = root.encode('utf-8')
 
     finder = FileFinder(root)
-    jarrer = Jarrer(optimize=False)
+    jarrer = Jarrer()
     for p, f in finder.find("**/*.gcno"):
         jarrer.add(p, f)
 
     dist_include_manifest = mozpath.join(buildconfig.topobjdir,
                                          '_build_manifests',
                                          'install',
                                          'dist_include')
     linked_files = describe_install_manifest(dist_include_manifest,
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -69,26 +69,16 @@ If you feel this message is not appropri
 please file a Firefox Build System :: General bug at
 https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox%20Build%20System&component=General
 and tell us about your machine and build configuration so we can adjust the
 warning heuristic.
 ===================
 '''
 
 
-# Function used by clang-format to run it in parallel, according to the given
-# arguments. Must be defined at the top-level so it can be used with
-# multiprocessing.Pool.imap_unordered.
-def run_one_clang_format_batch(args):
-    try:
-        subprocess.check_output(args)
-    except subprocess.CalledProcessError as e:
-        return e
-
-
 class StoreDebugParamsAndWarnAction(argparse.Action):
     def __call__(self, parser, namespace, values, option_string=None):
         sys.stderr.write('The --debugparams argument is deprecated. Please ' +
                          'use --debugger-args instead.\n\n')
         setattr(namespace, self.dest, values)
 
 
 @CommandProvider
@@ -2907,74 +2897,56 @@ class StaticAnalysis(MachCommandBase):
 
         path_list = self._generate_path_list(paths)
 
         if path_list == []:
             return
 
         print("Processing %d file(s)..." % len(path_list))
 
+        batchsize = 200
         if show:
-            for i in range(0, len(path_list)):
-                l = path_list[i: (i + 1)]
-
+            batchsize = 1
+
+        for i in range(0, len(path_list), batchsize):
+            l = path_list[i: (i + batchsize)]
+            if show:
                 # Copy the files into a temp directory
                 # and run clang-format on the temp directory
                 # and show the diff
                 original_path = l[0]
                 local_path = ntpath.basename(original_path)
                 target_file = os.path.join(tmpdir, local_path)
                 faketmpdir = os.path.dirname(target_file)
                 if not os.path.isdir(faketmpdir):
                     os.makedirs(faketmpdir)
                 shutil.copy(l[0], faketmpdir)
                 l[0] = target_file
 
-                # Run clang-format on the list
-                try:
-                    check_output(args + l)
-                except CalledProcessError as e:
-                    # Something wrong happend
-                    print("clang-format: An error occured while running clang-format.")
-                    return e.returncode
-
+            # Run clang-format on the list
+            try:
+                check_output(args + l)
+            except CalledProcessError as e:
+                # Something wrong happend
+                print("clang-format: An error occured while running clang-format.")
+                return e.returncode
+
+            if show:
                 # show the diff
                 diff_command = ["diff", "-u", original_path, target_file]
                 try:
                     output = check_output(diff_command)
                 except CalledProcessError as e:
                     # diff -u returns 0 when no change
                     # here, we expect changes. if we are here, this means that
                     # there is a diff to show
                     if e.output:
                         print(e.output)
-
+        if show:
             shutil.rmtree(tmpdir)
-            return 0
-
-        import multiprocessing
-        import math
-
-        cpu_count = multiprocessing.cpu_count()
-        batchsize = int(math.ceil(float(len(path_list)) / cpu_count))
-
-        batches = []
-        for i in range(0, len(path_list), batchsize):
-            batches.append(args + path_list[i: (i + batchsize)])
-
-        pool = multiprocessing.Pool(cpu_count)
-
-        error_code = None
-        for result in pool.imap_unordered(run_one_clang_format_batch, batches):
-            if error_code is None and result is not None:
-                print("clang-format: An error occured while running clang-format.")
-                error_code = result.returncode
-
-        if error_code is not None:
-            return error_code
         return 0
 
 @CommandProvider
 class Vendor(MachCommandBase):
     """Vendor third-party dependencies into the source repository."""
 
     @Command('vendor', category='misc',
              description='Vendor third-party dependencies into the source repository.')
--- a/python/mozbuild/mozpack/copier.py
+++ b/python/mozbuild/mozpack/copier.py
@@ -502,23 +502,22 @@ class FileCopier(FileRegistry):
         return result
 
 
 class Jarrer(FileRegistry, BaseFile):
     '''
     FileRegistry with the ability to copy and pack the registered files as a
     jar file. Also acts as a BaseFile instance, to be copied with a FileCopier.
     '''
-    def __init__(self, compress=True, optimize=True):
+    def __init__(self, compress=True):
         '''
         Create a Jarrer instance. See mozpack.mozjar.JarWriter documentation
-        for details on the compress and optimize arguments.
+        for details on the compress argument.
         '''
         self.compress = compress
-        self.optimize = optimize
         self._preload = []
         self._compress_options = {}  # Map path to compress boolean option.
         FileRegistry.__init__(self)
 
     def add(self, path, content, compress=None):
         FileRegistry.add(self, path, content)
         if compress is not None:
             self._compress_options[path] = compress
@@ -569,18 +568,17 @@ class Jarrer(FileRegistry, BaseFile):
         from mozpack.mozjar import JarWriter, JarReader, JAR_BROTLI
         try:
             old_jar = JarReader(fileobj=dest)
         except Exception:
             old_jar = []
 
         old_contents = dict([(f.filename, f) for f in old_jar])
 
-        with JarWriter(fileobj=dest, compress=self.compress,
-                       optimize=self.optimize) as jar:
+        with JarWriter(fileobj=dest, compress=self.compress) as jar:
             for path, file in self:
                 compress = self._compress_options.get(path, self.compress)
                 # Temporary: Because l10n repacks can't handle brotli just yet,
                 # but need to be able to decompress those files, per
                 # UnpackFinder and formatters, we force deflate on them.
                 if compress == JAR_BROTLI and (
                         isinstance(file, ManifestFile) or
                         mozpath.basename(path) == 'install.rdf'):
--- a/python/mozbuild/mozpack/mozjar.py
+++ b/python/mozbuild/mozpack/mozjar.py
@@ -469,18 +469,17 @@ class JarReader(object):
 
 
 class JarWriter(object):
     '''
     Class with methods to write Jar files. Can write more-or-less standard jar
     archives as well as jar archives optimized for Gecko. See the documentation
     for the close() member function for a description of both layouts.
     '''
-    def __init__(self, file=None, fileobj=None, compress=True, optimize=True,
-                 compress_level=9):
+    def __init__(self, file=None, fileobj=None, compress=True, compress_level=9):
         '''
         Initialize a Jar archive in the given file. Use the given file-like
         object if one is given instead of opening the given file name.
         The compress option determines the default behavior for storing data
         in the jar archive. The optimize options determines whether the jar
         archive should be optimized for Gecko or not. ``compress_level``
         defines the zlib compression level. It must be a value between 0 and 9
         and defaults to 9, the highest and slowest level of compression.
@@ -490,17 +489,16 @@ class JarWriter(object):
         else:
             self._data = open(file, 'wb')
         if compress is True:
             compress = JAR_DEFLATED
         self._compress = compress
         self._compress_level = compress_level
         self._contents = OrderedDict()
         self._last_preloaded = None
-        self._optimize = optimize
 
     def __enter__(self):
         '''
         Context manager __enter__ method for JarWriter.
         '''
         return self
 
     def __exit__(self, type, value, tb):
@@ -560,32 +558,31 @@ class JarWriter(object):
         # Prepare end of central directory
         end = JarCdirEnd()
         end['disk_entries'] = len(self._contents)
         end['cdir_entries'] = end['disk_entries']
         end['cdir_size'] = reduce(lambda x, y: x + y[0].size,
                                   self._contents.values(), 0)
         # On optimized archives, store the preloaded size and the central
         # directory entries, followed by the first end of central directory.
-        if self._optimize:
+        if preload_size:
             end['cdir_offset'] = 4
             offset = end['cdir_size'] + end['cdir_offset'] + end.size
-            if preload_size:
-                preload_size += offset
+            preload_size += offset
             self._data.write(struct.pack('<I', preload_size))
             for entry, _ in self._contents.itervalues():
                 entry['offset'] += offset
                 self._data.write(entry.serialize())
             self._data.write(end.serialize())
         # Store local file entries followed by compressed data
         for entry, content in self._contents.itervalues():
             self._data.write(headers[entry].serialize())
             self._data.write(content)
         # On non optimized archives, store the central directory entries.
-        if not self._optimize:
+        if not preload_size:
             end['cdir_offset'] = offset
             for entry, _ in self._contents.itervalues():
                 self._data.write(entry.serialize())
         # Store the end of central directory.
         self._data.write(end.serialize())
         self._data.close()
 
     def add(self, name, data, compress=None, mode=None, skip_duplicates=False):
--- a/python/mozbuild/mozpack/packager/formats.py
+++ b/python/mozbuild/mozpack/packager/formats.py
@@ -185,44 +185,42 @@ class FlatSubFormatter(object):
 
 class JarFormatter(PiecemealFormatter):
     '''
     Formatter for the jar package format. Assumes manifest entries related to
     chrome are registered before the chrome data files are added. Also assumes
     manifest entries for resources are registered after chrome manifest
     entries.
     '''
-    def __init__(self, copier, compress=True, optimize=True):
+    def __init__(self, copier, compress=True):
         PiecemealFormatter.__init__(self, copier)
         self._compress=compress
-        self._optimize=optimize
 
     def _add_base(self, base, addon=False):
         if addon is True:
-            jarrer = Jarrer(self._compress, self._optimize)
+            jarrer = Jarrer(self._compress)
             self.copier.add(base + '.xpi', jarrer)
             self._sub_formatter[base] = FlatSubFormatter(jarrer)
         else:
             self._sub_formatter[base] = JarSubFormatter(
                 FileRegistrySubtree(base, self.copier),
-                self._compress, self._optimize)
+                self._compress)
 
 
 class JarSubFormatter(PiecemealFormatter):
     '''
     Sub-formatter for the jar package format. It is a PiecemealFormatter that
     dispatches between further sub-formatter for each of the jar files it
     dispatches the chrome data to, and a FlatSubFormatter for the non-chrome
     files.
     '''
-    def __init__(self, copier, compress=True, optimize=True):
+    def __init__(self, copier, compress=True):
         PiecemealFormatter.__init__(self, copier)
         self._frozen_chrome = False
         self._compress = compress
-        self._optimize = optimize
         self._sub_formatter[''] = FlatSubFormatter(copier)
 
     def _jarize(self, entry, relpath):
         '''
         Transform a manifest entry in one pointing to chrome data in a jar.
         Return the corresponding chrome path and the new entry.
         '''
         base = entry.base
@@ -234,66 +232,63 @@ class JarSubFormatter(PiecemealFormatter
         return chromepath, entry
 
     def add_manifest(self, entry):
         if isinstance(entry, ManifestChrome) and \
                 not urlparse(entry.relpath).scheme:
             chromepath, entry = self._jarize(entry, entry.relpath)
             assert not self._frozen_chrome
             if chromepath not in self._sub_formatter:
-                jarrer = Jarrer(self._compress, self._optimize)
+                jarrer = Jarrer(self._compress)
                 self.copier.add(chromepath + '.jar', jarrer)
                 self._sub_formatter[chromepath] = FlatSubFormatter(jarrer)
         elif isinstance(entry, ManifestResource) and \
                 not urlparse(entry.target).scheme:
             chromepath, new_entry = self._jarize(entry, entry.target)
             if chromepath in self._sub_formatter:
                 entry = new_entry
         PiecemealFormatter.add_manifest(self, entry)
 
 
 class OmniJarFormatter(JarFormatter):
     '''
     Formatter for the omnijar package format.
     '''
-    def __init__(self, copier, omnijar_name, compress=True, optimize=True,
-                 non_resources=()):
-        JarFormatter.__init__(self, copier, compress, optimize)
+    def __init__(self, copier, omnijar_name, compress=True, non_resources=()):
+        JarFormatter.__init__(self, copier, compress)
         self._omnijar_name = omnijar_name
         self._non_resources = non_resources
 
     def _add_base(self, base, addon=False):
         if addon:
             JarFormatter._add_base(self, base, addon)
         else:
             # Initialize a chrome.manifest next to the omnijar file so that
             # there's always a chrome.manifest file, even an empty one.
             path = mozpath.normpath(mozpath.join(base, 'chrome.manifest'))
             if not self.copier.contains(path):
                 self.copier.add(path, ManifestFile(''))
             self._sub_formatter[base] = OmniJarSubFormatter(
                 FileRegistrySubtree(base, self.copier), self._omnijar_name,
-                self._compress, self._optimize, self._non_resources)
+                self._compress, self._non_resources)
 
 
 class OmniJarSubFormatter(PiecemealFormatter):
     '''
     Sub-formatter for the omnijar package format. It is a PiecemealFormatter
     that dispatches between a FlatSubFormatter for the resources data and
     another FlatSubFormatter for the other files.
     '''
-    def __init__(self, copier, omnijar_name, compress=True, optimize=True,
-                 non_resources=()):
+    def __init__(self, copier, omnijar_name, compress=True, non_resources=()):
         PiecemealFormatter.__init__(self, copier)
         self._omnijar_name = omnijar_name
         self._compress = compress
-        self._optimize = optimize
         self._non_resources = non_resources
         self._sub_formatter[''] = FlatSubFormatter(copier)
-        jarrer = Jarrer(self._compress, self._optimize)
+        jarrer = Jarrer(self._compress)
         self._sub_formatter[omnijar_name] = FlatSubFormatter(jarrer)
 
     def _get_base(self, path):
         base = self._omnijar_name if self.is_resource(path) else ''
         # Only add the omnijar file if something ends up in it.
         if base and not self.copier.contains(base):
             self.copier.add(base, self._sub_formatter[base].copier)
         return base, path
--- a/python/mozbuild/mozpack/packager/l10n.py
+++ b/python/mozbuild/mozpack/packager/l10n.py
@@ -293,20 +293,18 @@ def repack(source, l10n, extra_l10n={}, 
             finders[base] = UnpackFinder(path)
         l10n_finder = ComposedFinder(finders)
     copier = FileCopier()
     compress = min(app_finder.compressed, JAR_DEFLATED)
     if app_finder.kind == 'flat':
         formatter = FlatFormatter(copier)
     elif app_finder.kind == 'jar':
         formatter = JarFormatter(copier,
-                                 optimize=app_finder.optimizedjars,
                                  compress=compress)
     elif app_finder.kind == 'omni':
         formatter = OmniJarFormatter(copier, app_finder.omnijar,
-                                     optimize=app_finder.optimizedjars,
                                      compress=compress,
                                      non_resources=non_resources)
 
     with errors.accumulate():
         _repack(app_finder, l10n_finder, copier, formatter, non_chrome)
     copier.copy(source, skip_if_older=False)
     generate_precomplete(source)
--- a/python/mozbuild/mozpack/packager/unpack.py
+++ b/python/mozbuild/mozpack/packager/unpack.py
@@ -45,17 +45,16 @@ class UnpackFinder(BaseFinder):
             self._finder = source
         else:
             self._finder = FileFinder(source)
         self.base = self._finder.base
         self.files = FileRegistry()
         self.kind = 'flat'
         self.omnijar = None
         self.jarlogs = {}
-        self.optimizedjars = False
         self.compressed = False
 
         jars = set()
 
         for p, f in self._finder.find('*'):
             # Skip the precomplete file, which is generated at packaging time.
             if p == 'precomplete':
                 continue
@@ -136,18 +135,16 @@ class UnpackFinder(BaseFinder):
         return entry
 
     def _open_jar(self, path, file):
         '''
         Return a JarReader for the given BaseFile instance, keeping a log of
         the preloaded entries it has.
         '''
         jar = JarReader(fileobj=file.open())
-        if jar.is_optimized:
-            self.optimizedjars = True
         self.compressed = max(self.compressed, jar.compression)
         if jar.last_preloaded:
             jarlog = jar.entries.keys()
             self.jarlogs[path] = jarlog[:jarlog.index(jar.last_preloaded) + 1]
         return jar
 
     def find(self, path):
         for p in self.files.match(path):
--- a/python/mozbuild/mozpack/test/test_mozjar.py
+++ b/python/mozbuild/mozpack/test/test_mozjar.py
@@ -134,21 +134,19 @@ class TestDeflater(unittest.TestCase):
 
 
 class TestDeflaterMemoryView(TestDeflater):
     def wrap(self, data):
         return memoryview(data)
 
 
 class TestJar(unittest.TestCase):
-    optimize = False
-
     def test_jar(self):
         s = MockDest()
-        with JarWriter(fileobj=s, optimize=self.optimize) as jar:
+        with JarWriter(fileobj=s) as jar:
             jar.add('foo', 'foo')
             self.assertRaises(JarWriterError, jar.add, 'foo', 'bar')
             jar.add('bar', 'aaaaaaaaaaaaanopqrstuvwxyz')
             jar.add('baz/qux', 'aaaaaaaaaaaaanopqrstuvwxyz', False)
             jar.add('baz\\backslash', 'aaaaaaaaaaaaaaa')
 
         files = [j for j in JarReader(fileobj=s)]
 
@@ -167,18 +165,17 @@ class TestJar(unittest.TestCase):
         if os.sep == '\\':
             self.assertEqual(files[3].filename, 'baz/backslash',
                 'backslashes in filenames on Windows should get normalized')
         else:
             self.assertEqual(files[3].filename, 'baz\\backslash',
                 'backslashes in filenames on POSIX platform are untouched')
 
         s = MockDest()
-        with JarWriter(fileobj=s, compress=False,
-                       optimize=self.optimize) as jar:
+        with JarWriter(fileobj=s, compress=False) as jar:
             jar.add('bar', 'aaaaaaaaaaaaanopqrstuvwxyz')
             jar.add('foo', 'foo')
             jar.add('baz/qux', 'aaaaaaaaaaaaanopqrstuvwxyz', True)
 
         jar = JarReader(fileobj=s)
         files = [j for j in jar]
 
         self.assertEqual(files[0].filename, 'bar')
@@ -220,23 +217,23 @@ class TestJar(unittest.TestCase):
 
         files[2].seek(0)
         self.assertEqual(jar['baz/qux'].filename, files[2].filename)
         self.assertEqual(jar['baz/qux'].compressed, files[2].compressed)
         self.assertEqual(jar['baz/qux'].read(), files[2].read())
 
     def test_rejar(self):
         s = MockDest()
-        with JarWriter(fileobj=s, optimize=self.optimize) as jar:
+        with JarWriter(fileobj=s) as jar:
             jar.add('foo', 'foo')
             jar.add('bar', 'aaaaaaaaaaaaanopqrstuvwxyz')
             jar.add('baz/qux', 'aaaaaaaaaaaaanopqrstuvwxyz', False)
 
         new = MockDest()
-        with JarWriter(fileobj=new, optimize=self.optimize) as jar:
+        with JarWriter(fileobj=new) as jar:
             for j in JarReader(fileobj=s):
                 jar.add(j.filename, j)
 
         jar = JarReader(fileobj=new)
         files = [j for j in jar]
 
         self.assertEqual(files[0].filename, 'foo')
         self.assertFalse(files[0].compressed)
@@ -247,33 +244,29 @@ class TestJar(unittest.TestCase):
         self.assertEqual(files[1].read(), 'aaaaaaaaaaaaanopqrstuvwxyz')
 
         self.assertEqual(files[2].filename, 'baz/qux')
         self.assertTrue(files[2].compressed)
         self.assertEqual(files[2].read(), 'aaaaaaaaaaaaanopqrstuvwxyz')
 
     def test_add_from_finder(self):
         s = MockDest()
-        with JarWriter(fileobj=s, optimize=self.optimize) as jar:
+        with JarWriter(fileobj=s) as jar:
             finder = FileFinder(test_data_path)
             for p, f in finder.find('test_data'):
                 jar.add('test_data', f)
 
         jar = JarReader(fileobj=s)
         files = [j for j in jar]
 
         self.assertEqual(files[0].filename, 'test_data')
         self.assertFalse(files[0].compressed)
         self.assertEqual(files[0].read(), 'test_data')
 
 
-class TestOptimizeJar(TestJar):
-    optimize = True
-
-
 class TestPreload(unittest.TestCase):
     def test_preload(self):
         s = MockDest()
         with JarWriter(fileobj=s) as jar:
             jar.add('foo', 'foo')
             jar.add('bar', 'abcdefghijklmnopqrstuvwxyz')
             jar.add('baz/qux', 'aaaaaaaaaaaaanopqrstuvwxyz')
 
--- a/taskcluster/docker/recipes/ubuntu1604-test-system-setup.sh
+++ b/taskcluster/docker/recipes/ubuntu1604-test-system-setup.sh
@@ -68,16 +68,23 @@ apt_packages+=('unzip')
 apt_packages+=('uuid')
 apt_packages+=('vim')
 apt_packages+=('wget')
 apt_packages+=('xvfb')
 apt_packages+=('yasm')
 apt_packages+=('zip')
 apt_packages+=('libsecret-1-0:i386')
 
+# Make sure we have X libraries for 32-bit tests
+apt_packages+=('libxt6:i386')
+apt_packages+=('libpulse0:i386')
+apt_packages+=('libasound2:i386')
+apt_packages+=('libxtst6:i386')
+apt_packages+=('libgtk2.0-0:i386')
+
 # get xvinfo for test-linux.sh to monitor Xvfb startup
 apt_packages+=('x11-utils')
 
 # Bug 1232407 - this allows the user to start vnc
 apt_packages+=('x11vnc')
 
 # Bug 1176031: need `xset` to disable screensavers
 apt_packages+=('x11-xserver-utils')
@@ -157,25 +164,45 @@ apt-get -q -y -f install \
     mesa-common-dev
 
 # additional packages for linux32 tests
 sudo dpkg --add-architecture i386
 apt-get update
 apt-get -q -y -f install \
     libavcodec-ffmpeg-extra56:i386 \
     libgtk-3-0:i386 \
-    libdbus-glib-1-2:i386 \
-    openjdk-8-jdk:i386
+    libdbus-glib-1-2:i386
 
 # use fc-cache:i386 to pre-build the font cache for i386 binaries
 apt-get -q -y -f install \
     fontconfig:i386 \
 
 # revert the list of repos
 cp sources.list.orig /etc/apt/sources.list
 apt-get update
 
 # clean up
+# Purge unneeded stuff from the image
+apt-get -y purge cheese 'libcheese*'
+apt-get -y purge gnome-user-guide
+apt-get -y purge 'libreoffice*'
+#apt-get -y purge firefox thunderbird
+apt-get -y purge 'liboxideqt*'
+apt-get -y purge gnome-mahjongg
+apt-get -y purge ubuntu-docs
+apt-get -y purge llvm-3.8-dev libllvm3.8
+apt-get -y purge git
+apt-get -y purge lintian
+apt-get -y purge freepats
+apt-get -y purge ubuntu-mobile-icons
+apt-get -y purge hplip
+apt-get -y purge rhythmbox
+apt-get -y purge thunderbird
+apt-get -y autoremove
+
+# We don't need no docs!
+rm -rf /usr/share/help /usr/share/doc /usr/share/man
+
 cd /
 rm -rf /setup ~/.ccache ~/.cache ~/.npm
 apt-get clean
 apt-get autoclean
 rm -f "$0"
--- a/testing/talos/talos/tests/devtools/addon/content/tests/webconsole/autocomplete.js
+++ b/testing/talos/talos/tests/devtools/addon/content/tests/webconsole/autocomplete.js
@@ -65,12 +65,12 @@ function hideAutocompletePopup(jsterm) {
   let onPopUpClosed = jsterm.autocompletePopup.once("popup-closed");
   setJsTermValueForCompletion(jsterm, "");
   return onPopUpClosed;
 }
 
 function setJsTermValueForCompletion(jsterm, value) {
   // setInputValue does not trigger the autocompletion;
   // we need to call the `autocompleteUpdate` action in order to display the popup.
-  jsterm.setInputValue(value);
+  jsterm._setValue(value);
   jsterm.props.autocompleteUpdate();
 }
 
--- a/testing/tps/mach_commands.py
+++ b/testing/tps/mach_commands.py
@@ -22,14 +22,14 @@ class MachCommands(MachCommandBase):
         dest = os.path.join(dest or os.path.join(self.topobjdir, 'services', 'sync'), 'tps.xpi')
 
         if not os.path.exists(os.path.dirname(dest)):
             os.makedirs(os.path.dirname(dest))
 
         if os.path.isfile(dest):
             os.unlink(dest)
 
-        jarrer = Jarrer(optimize=False)
+        jarrer = Jarrer()
         for p, f in FileFinder(src).find('*'):
             jarrer.add(p, f)
         jarrer.copy(dest)
 
         print('Built TPS add-on as %s' % dest)
--- a/toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html
@@ -1,11 +1,12 @@
 <!DOCTYPE HTML>
 <html>
 <head>
+  <meta charset="utf-8">
   <title>Test for protocol handlers</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
   <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
@@ -38,16 +39,19 @@ function protocolChromeScript() {
                           .getService(Ci.nsIHandlerService);
     handlerSvc.store(protoInfo);
 
     sendAsyncMessage("handlerData", data);
   });
 }
 
 add_task(async function test_protocolHandler() {
+  await SpecialPowers.pushPrefEnv({set: [
+    ["extensions.allowPrivateBrowsingByDefault", false],
+  ]});
   let extensionData = {
     manifest: {
       "protocol_handlers": [
         {
           "protocol": "ext+foo",
           "name": "a foo protocol handler",
           "uriTemplate": "foo.html?val=%s",
         },
@@ -76,16 +80,32 @@ add_task(async function test_protocolHan
           <head>
             <meta charset="utf-8">
             <script src="foo.js"><\/script>
           </head>
         </html>`,
     },
   };
 
+  let pb_extension = ExtensionTestUtils.loadExtension({
+    background() {
+      browser.test.onMessage.addListener(async (msg, arg) => {
+        if (msg == "open") {
+          let tab = await browser.windows.create({url: arg, incognito: true});
+          browser.test.sendMessage("opened", tab.id);
+        } else if (msg == "close") {
+          await browser.windows.remove(arg);
+          browser.test.sendMessage("closed");
+        }
+      });
+    },
+    incognitoOverride: "spanning",
+  });
+  await pb_extension.startup();
+
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   await extension.startup();
   let handlerUrl = await extension.awaitMessage("test-url");
 
   // Ensure that the protocol handler is configured, and set it as default to
   // bypass the dialog.
   let chromeScript = SpecialPowers.loadChromeScript(protocolChromeScript);
 
@@ -104,16 +124,47 @@ add_task(async function test_protocolHan
   let id = await extension.awaitMessage("opened");
 
   let query = await extension.awaitMessage("test-query");
   is(query, "?val=ext%2Bfoo%3Atest", "test query ok");
 
   extension.sendMessage("close", id);
   await extension.awaitMessage("closed");
 
+  // Test the protocol in a private window, watch for the
+  // console error.
+  consoleMonitor.start([{message: /NS_ERROR_FILE_NOT_FOUND/}]);
+
+  // Expect the chooser window to be open, close it.
+  chromeScript = SpecialPowers.loadChromeScript(async () => {
+    const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+
+    let window = await BrowserTestUtils.domWindowOpened(undefined, win => {
+      return BrowserTestUtils.waitForEvent(win, "load", false, event => {
+        let win = event.target.defaultView;
+        return win.document.documentElement.getAttribute("id") === "handling";
+      });
+    });
+    let entry =  window.document.getElementById("items").firstChild;
+    sendAsyncMessage("handling", {name: entry.getAttribute("name"), disabled: entry.disabled});
+    window.close();
+  });
+
+  let testData = chromeScript.promiseOneMessage("handling");
+  pb_extension.sendMessage("open", "ext+foo:test");
+  id = await pb_extension.awaitMessage("opened");
+  await consoleMonitor.finished();
+  let entry = await testData;
+  is(entry.name, "a foo protocol handler", "entry is correct");
+  ok(entry.disabled, "handler is disabled");
+
+  pb_extension.sendMessage("close", id);
+  await pb_extension.awaitMessage("closed");
+  await pb_extension.unload();
+
   // Shutdown the addon, then ensure the protocol was removed.
   await extension.unload();
   chromeScript = SpecialPowers.loadChromeScript(() => {
     addMessageListener("setup", () => {
       const protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
                          .getService(Ci.nsIExternalProtocolService);
       let protoInfo = protoSvc.getProtocolHandlerInfo("ext+foo");
       sendAsyncMessage("preferredApplicationHandler", !protoInfo.preferredApplicationHandler);
--- a/toolkit/content/tests/chrome/test_richlistbox.xul
+++ b/toolkit/content/tests/chrome/test_richlistbox.xul
@@ -85,15 +85,25 @@ function test_richlistbox()
     richListBox.addEventListener("keypress", function(aEvent) {
       richListBox.removeEventListener("keypress", arguments.callee, true);
       aEvent.preventDefault();
     }, true);
     richListBox.selectedIndex = 1;
     sendKey("HOME");
     is(richListBox.selectedIndex, 1, "A stopped event should return indexing to normal");
   }
+
+  // Test attempting to select a disabled item.
+  richListBox.clearSelection();
+  richListBox.selectedItem = richListBox.firstChild;
+  richListBox.firstChild.nextSibling.setAttribute("disabled", true);
+  richListBox.focus();
+  synthesizeKey("KEY_ArrowDown", {}, window);
+  is(richListBox.selectedItems.length, 1, "one item selected");
+  is(richListBox.selectedItems[0], richListBox.firstChild, "first item selected");
+
   SimpleTest.finish();
 }
 
 ]]>
 </script>
 
 </window>
--- a/toolkit/content/widgets/richlistbox.js
+++ b/toolkit/content/widgets/richlistbox.js
@@ -409,17 +409,17 @@ MozElements.RichListBox = class RichList
       this.removeItemFromSelection(aItem);
     } else {
       this.addItemToSelection(aItem);
     }
   }
 
   // nsIDOMXULMultiSelectControlElement
   selectItem(aItem) {
-    if (!aItem) {
+    if (!aItem || aItem.disabled) {
       return;
     }
 
     if (this.selectedItems.length == 1 && this.selectedItems[0] == aItem) {
       return;
     }
 
     this._selectionStart = null;
--- a/toolkit/locales/en-US/chrome/mozapps/handling/handling.properties
+++ b/toolkit/locales/en-US/chrome/mozapps/handling/handling.properties
@@ -4,9 +4,12 @@
 
 protocol.title=Launch Application
 protocol.description=This link needs to be opened with an application.
 protocol.choices.label=Send to:
 protocol.checkbox.label=Remember my choice for %S links.
 protocol.checkbox.accesskey=R
 protocol.checkbox.extra=This can be changed in %S’s preferences.
 
+# Displayed under the name of a protocol handler in the Launch Application dialog.
+privatebrowsing.disabled.label=Disabled in Private Windows
+
 choose.application.title=Another Application…
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -1844,21 +1844,25 @@ var gCategories = {
     this.node = document.getElementById("categories");
 
     var types = AddonManager.addonTypes;
     for (var type in types)
       this.onTypeAdded(types[type]);
 
     AddonManager.addTypeListener(this);
 
+    let lastView = Services.prefs.getStringPref(PREF_UI_LASTCATEGORY, "");
     // Set this to the default value first, or setting it to a nonexistent value
     // from the pref will leave the old value in place.
     this.node.value = gViewDefault;
-    this.node.value = Services.prefs.getStringPref(PREF_UI_LASTCATEGORY, "");
-
+    this.node.value = lastView;
+    // Fixup the last view if legacy is disabled.
+    if (lastView !== this.node.value && lastView == "addons://legacy/") {
+      this.node.value = "addons://list/extension";
+    }
     // If there was no last view or no existing category matched the last view
     // then switch to the default category
     if (!this.node.selectedItem) {
       this.node.value = gViewDefault;
     }
     // If the previous node is the discover panel which has since been disabled set to default
     if (this.node.value == "addons://discover/" && !isDiscoverEnabled()) {
       this.node.value = gViewDefault;
@@ -3554,17 +3558,17 @@ var gUpdatesView = {
     if (aType == "recent")
       this._showRecentUpdates(aRequest);
     else
       this._showAvailableUpdates(false, aRequest);
   },
 
   hide() {
     this._updateSelected.hidden = true;
-    this._categoryItem.disabled = this._categoryItem.badgeCount == 0;
+    this._categoryItem.hidden = this._categoryItem.badgeCount == 0;
     doPendingUninstalls(this._listBox);
   },
 
   async _showRecentUpdates(aRequest) {
     let aAddonsList = await AddonManager.getAllAddons();
     if (gViewController && aRequest != gViewController.currentViewRequest)
       return;
 
@@ -3655,17 +3659,17 @@ var gUpdatesView = {
 
   async updateAvailableCount(aInitializing) {
     if (aInitializing)
       gPendingInitializations++;
     let aInstallsList = await AddonManager.getAllInstalls();
     var count = aInstallsList.filter(aInstall => {
       return this.isManualUpdate(aInstall, true);
     }).length;
-    this._categoryItem.disabled = gViewController.currentViewId != "addons://updates/available" &&
+    this._categoryItem.hidden = gViewController.currentViewId != "addons://updates/available" &&
                                   count == 0;
     this._categoryItem.badgeCount = count;
     if (aInitializing)
       notifyInitialized();
   },
 
   maybeDisableUpdateSelected() {
     for (let item of this._listBox.childNodes) {
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -136,28 +136,28 @@
       <richlistbox id="categories" flex="1">
         <richlistitem id="category-discover" value="addons://discover/"
                       class="category"
                       data-l10n-id="extensions-view-discover"
                       data-l10n-attrs="name"
                       priority="1000"/>
         <richlistitem id="category-legacy" value="addons://legacy/"
                       class="category" priority="20000"
-                      disabled="true"/>
+                      hidden="true"/>
         <richlistitem id="category-availableUpdates" value="addons://updates/available"
                       class="category"
                       data-l10n-id="extensions-view-available-updates"
                       data-l10n-attrs="name"
-                      disabled="true"/>
+                      hidden="true"/>
         <richlistitem id="category-recentUpdates" value="addons://updates/recent"
                       class="category"
                       data-l10n-id="extensions-view-recent-updates"
                       data-l10n-attrs="name"
                       priority="101000"
-                      disabled="true"/>
+                      hidden="true"/>
       </richlistbox>
 
       <spacer flex="1"/>
 
       <hbox class="sidebar-footer-button" pack="center">
         <label id="preferencesButton" is="text-link">
           <hbox align="center">
             <image class="sidebar-footer-icon preferences-icon"/>
--- a/toolkit/mozapps/extensions/test/browser/browser_webext_options_addon_reload.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webext_options_addon_reload.js
@@ -76,22 +76,18 @@ add_task(async function test_options_on_
   Assert.equal(optionsBrowsers.length, 1, "Got a single XUL browser for the addon options_ui page");
 
   // Reload the addon five times in a row, and then check that there is still one addon options browser.
 
   let addon = await AddonManager.getAddonByID(ID);
 
   for (let i = 0; i < 5; i++) {
     const onceOptionsReloaded = Promise.all([
-      // Reloading the addon currently prevents the extension.awaitMessage test helper to be able
-      // to receive test messages from the reloaded extension, this test function helps to wait
-      // the extension has been restarted on addon reload.
       AddonTestUtils.promiseWebExtensionStartup(),
-      TestUtils.topicObserved(AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
-                              (subject, data) => data == extension.id),
+      waitOptionsBrowserInserted(),
     ]);
 
     await addon.reload();
 
     info("Wait for the new options_ui page XUL browser to be created");
     await onceOptionsReloaded;
 
     let optionsBrowsers = aboutAddonsDocument.querySelectorAll("#addon-options");
--- a/toolkit/mozapps/handling/content/dialog.js
+++ b/toolkit/mozapps/handling/content/dialog.js
@@ -25,17 +25,17 @@
  * window.arguments[8]:
  *   This is the nsIURI that we are being brought up for in the first place.
  * window.arguments[9]:
  *   The nsIInterfaceRequestor of the parent window; may be null
  */
 
 const {EnableDelayHelper} = ChromeUtils.import("resource://gre/modules/SharedPromptUtils.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
+const {PrivateBrowsingUtils} = ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 var dialog = {
   // Member Variables
 
   _handlerInfo: null,
   _URI: null,
   _itemChoose: null,
   _okButton: null,
@@ -46,18 +46,36 @@ var dialog = {
 
  /**
   * This function initializes the content of the dialog.
   */
   initialize: function initialize() {
     this._handlerInfo = window.arguments[7].QueryInterface(Ci.nsIHandlerInfo);
     this._URI         = window.arguments[8].QueryInterface(Ci.nsIURI);
     this._windowCtxt  = window.arguments[9];
-    if (this._windowCtxt)
+    let usePrivateBrowsing = false;
+    if (this._windowCtxt) {
+      // The context should be nsIRemoteWindowContext in OOP, or nsIDOMWindow otherwise.
+      try {
+        usePrivateBrowsing = this._windowCtxt.getInterface(Ci.nsIRemoteWindowContext)
+                                             .usePrivateBrowsing;
+      } catch (e) {
+        try {
+          let opener = this._windowCtxt.getInterface(Ci.nsIDOMWindow);
+          usePrivateBrowsing = PrivateBrowsingUtils.isContentWindowPrivate(opener);
+        } catch (e) {
+          Cu.reportError(`No interface to determine privateness: ${e}`);
+        }
+      }
       this._windowCtxt.QueryInterface(Ci.nsIInterfaceRequestor);
+    }
+
+    this.isPrivate = usePrivateBrowsing ||
+                     (window.opener && PrivateBrowsingUtils.isWindowPrivate(window.opener));
+
     this._itemChoose  = document.getElementById("item-choose");
     this._okButton    = document.documentElement.getButton("accept");
 
     var description = {
       image: document.getElementById("description-image"),
       text:  document.getElementById("description-text"),
     };
     var options = document.getElementById("item-action-text");
@@ -120,16 +138,31 @@ var dialog = {
           // because the service looks for a record with the exact URL we give
           // it, and users won't have such records for URLs they don't visit,
           // and users won't visit the handler's URL template, they'll only
           // visit URLs derived from that template (i.e. with %s in the template
           // replaced by the URL of the content being handled).
           elm.setAttribute("image", uri.prePath + "/favicon.ico");
         }
         elm.setAttribute("description", uri.prePath);
+
+        // Check for extensions needing private browsing access before
+        // creating UI elements.
+        if (this.isPrivate) {
+          let policy = WebExtensionPolicy.getByURI(uri);
+          if (policy && !policy.privateBrowsingAllowed) {
+            var bundle = document.getElementById("base-strings");
+            var disabledLabel = bundle.getString("privatebrowsing.disabled.label");
+            elm.setAttribute("disabled", true);
+            elm.setAttribute("description", disabledLabel);
+            if (app == preferredHandler) {
+              preferredHandler = null;
+            }
+          }
+        }
       } else if (app instanceof Ci.nsIDBusHandlerApp) {
         elm.setAttribute("description", app.method);
       } else if (!(app instanceof Ci.nsIGIOMimeApp)) {
         // We support GIO application handler, but no action required there
         throw "unknown handler type";
       }
 
       items.insertBefore(elm, this._itemChoose);
--- a/toolkit/mozapps/handling/content/handler.xml
+++ b/toolkit/mozapps/handling/content/handler.xml
@@ -8,21 +8,21 @@
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="handler"
            extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
 
     <content>
       <xul:vbox pack="center">
-        <xul:image xbl:inherits="src=image" height="32" width="32"/>
+        <xul:image xbl:inherits="src=image,disabled" height="32" width="32"/>
       </xul:vbox>
       <xul:vbox flex="1">
-        <xul:label class="name" xbl:inherits="value=name"/>
-        <xul:label class="description" xbl:inherits="value=description"/>
+        <xul:label class="name" xbl:inherits="value=name,disabled"/>
+        <xul:label class="description" xbl:inherits="value=description,disabled"/>
       </xul:vbox>
     </content>
     <implementation>
       <property name="label" onget="return this.getAttribute('name') + ' ' + this.getAttribute('description');"/>
     </implementation>
   </binding>
 
 </bindings>
--- a/toolkit/mozapps/installer/packager.mk
+++ b/toolkit/mozapps/installer/packager.mk
@@ -29,17 +29,16 @@ stage-package: multilocale.txt locale-ma
 		--format $(MOZ_PACKAGER_FORMAT) \
 		$(addprefix --removals ,$(MOZ_PKG_REMOVALS)) \
 		$(if $(filter-out 0,$(MOZ_PKG_FATAL_WARNINGS)),,--ignore-errors) \
 		$(if $(MOZ_PACKAGER_MINIFY),--minify) \
 		$(if $(MOZ_PACKAGER_MINIFY_JS),--minify-js \
 		  $(addprefix --js-binary ,$(JS_BINARY)) \
 		) \
 		$(addprefix --jarlog ,$(wildcard $(JARLOG_FILE_AB_CD))) \
-		$(if $(OPTIMIZEJARS),--optimizejars) \
 		$(addprefix --compress ,$(JAR_COMPRESSION)) \
 		$(MOZ_PKG_MANIFEST) '$(DIST)' '$(DIST)'/$(MOZ_PKG_DIR)$(if $(MOZ_PKG_MANIFEST),,$(_BINPATH)) \
 		$(if $(filter omni,$(MOZ_PACKAGER_FORMAT)),$(if $(NON_OMNIJAR_FILES),--non-resource $(NON_OMNIJAR_FILES)))
 ifdef RUN_FIND_DUPES
 	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/find-dupes.py $(DEFINES) $(ACDEFINES) $(MOZ_PKG_DUPEFLAGS) $(DIST)/$(MOZ_PKG_DIR)
 endif # RUN_FIND_DUPES
 ifndef MOZ_IS_COMM_TOPDIR
 	# Package mozharness
--- a/toolkit/mozapps/installer/packager.py
+++ b/toolkit/mozapps/installer/packager.py
@@ -205,18 +205,16 @@ def main():
     parser.add_argument('--minify-js', action='store_true',
                         help='Minify JavaScript files while packaging.')
     parser.add_argument('--js-binary',
                         help='Path to js binary. This is used to verify '
                         'minified JavaScript. If this is not defined, '
                         'minification verification will not be performed.')
     parser.add_argument('--jarlog', default='', help='File containing jar ' +
                         'access logs')
-    parser.add_argument('--optimizejars', action='store_true', default=False,
-                        help='Enable jar optimizations')
     parser.add_argument('--compress', choices=('none', 'deflate', 'brotli'),
                         default='deflate',
                         help='Use given jar compression (default: deflate)')
     parser.add_argument('manifest', default=None, nargs='?',
                         help='Manifest file name')
     parser.add_argument('source', help='Source directory')
     parser.add_argument('destination', help='Destination directory')
     parser.add_argument('--non-resource', nargs='+', metavar='PATTERN',
@@ -237,22 +235,21 @@ def main():
         'deflate': True,
         'brotli': JAR_BROTLI,
     }[args.compress]
 
     copier = FileCopier()
     if args.format == 'flat':
         formatter = FlatFormatter(copier)
     elif args.format == 'jar':
-        formatter = JarFormatter(copier, compress=compress, optimize=args.optimizejars)
+        formatter = JarFormatter(copier, compress=compress)
     elif args.format == 'omni':
         formatter = OmniJarFormatter(copier,
                                      buildconfig.substs['OMNIJAR_NAME'],
                                      compress=compress,
-                                     optimize=args.optimizejars,
                                      non_resources=args.non_resource)
     else:
         errors.fatal('Unknown format: %s' % args.format)
 
     # Adjust defines according to the requested format.
     if isinstance(formatter, OmniJarFormatter):
         defines['MOZ_OMNIJAR'] = 1
     elif 'MOZ_OMNIJAR' in defines:
--- a/toolkit/mozapps/installer/upload-files.mk
+++ b/toolkit/mozapps/installer/upload-files.mk
@@ -312,17 +312,16 @@ GARBAGE		+= $(DIST)/$(PACKAGE) $(PACKAGE
 
 PKG_ARG = , '$(pkg)'
 
 ifndef MOZ_PACKAGER_FORMAT
   MOZ_PACKAGER_FORMAT = $(error MOZ_PACKAGER_FORMAT is not set)
 endif
 
 ifneq (android,$(MOZ_WIDGET_TOOLKIT))
-  OPTIMIZEJARS = 1
   JAR_COMPRESSION ?= none
 endif
 
 # A js binary is needed to perform verification of JavaScript minification.
 # We can only use the built binary when not cross-compiling. Environments
 # (such as release automation) can provide their own js binary to enable
 # verification when cross-compiling.
 ifndef JS_BINARY
rename from mobile/android/themes/core/about.css
rename to toolkit/themes/mobile/global/about.css
rename from mobile/android/themes/core/aboutMemory.css
rename to toolkit/themes/mobile/global/aboutMemory.css
rename from mobile/android/themes/core/aboutSupport.css
rename to toolkit/themes/mobile/global/aboutSupport.css
--- a/toolkit/themes/mobile/jar.mn
+++ b/toolkit/themes/mobile/jar.mn
@@ -1,15 +1,18 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 toolkit.jar:
 % skin global classic/1.0 %skin/classic/global/
 # These are the CSS files that must exist
+   skin/classic/global/about.css                           (global/about.css)
+   skin/classic/global/aboutMemory.css                     (global/aboutMemory.css)
+   skin/classic/global/aboutSupport.css                    (global/aboutSupport.css)
    skin/classic/global/autocomplete.css                    (global/empty.css)
    skin/classic/global/button.css                          (global/empty.css)
    skin/classic/global/checkbox.css                        (global/empty.css)
    skin/classic/global/dialog.css                          (global/empty.css)
    skin/classic/global/dropmarker.css                      (global/empty.css)
    skin/classic/global/global.css                          (global/empty.css)
    skin/classic/global/menu.css                            (global/empty.css)
    skin/classic/global/menulist.css                        (global/empty.css)
--- a/uriloader/exthandler/WebHandlerApp.jsm
+++ b/uriloader/exthandler/WebHandlerApp.jsm
@@ -1,38 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-////////////////////////////////////////////////////////////////////////////////
-//// Constants
+const {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-const {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
-const {Services} = ChromeUtils.import('resource://gre/modules/Services.jsm');
-
-////////////////////////////////////////////////////////////////////////////////
-//// nsWebHandler class
+ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 function nsWebHandlerApp() {}
 
 nsWebHandlerApp.prototype = {
-  //////////////////////////////////////////////////////////////////////////////
-  //// nsWebHandler
-
   classDescription: "A web handler for protocols and content",
   classID: Components.ID("8b1ae382-51a9-4972-b930-56977a57919d"),
   contractID: "@mozilla.org/uriloader/web-handler-app;1",
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIWebHandlerApp, Ci.nsIHandlerApp]),
 
   _name: null,
   _detailedDescription: null,
   _uriTemplate: null,
 
-  //////////////////////////////////////////////////////////////////////////////
-  //// nsIHandlerApp
+  // nsIHandlerApp
 
   get name() {
     return this._name;
   },
 
   set name(aName) {
     this._name = aName;
   },
@@ -40,126 +32,137 @@ nsWebHandlerApp.prototype = {
   get detailedDescription() {
     return this._detailedDescription;
   },
 
   set detailedDescription(aDesc) {
     this._detailedDescription = aDesc;
   },
 
-  equals: function(aHandlerApp) {
-    if (!aHandlerApp)
+  equals(aHandlerApp) {
+    if (!aHandlerApp) {
       throw Cr.NS_ERROR_NULL_POINTER;
+    }
 
     if (aHandlerApp instanceof Ci.nsIWebHandlerApp &&
         aHandlerApp.uriTemplate &&
         this.uriTemplate &&
-        aHandlerApp.uriTemplate == this.uriTemplate)
+        aHandlerApp.uriTemplate == this.uriTemplate) {
       return true;
-
+    }
     return false;
   },
 
-  launchWithURI: function nWHA__launchWithURI(aURI, aWindowContext) {
-
+  launchWithURI(aURI, aWindowContext) {
     // XXX need to strip passwd & username from URI to handle, as per the
     // WhatWG HTML5 draft.  nsSimpleURL, which is what we're going to get,
     // can't do this directly.  Ideally, we'd fix nsStandardURL to make it
     // possible to turn off all of its quirks handling, and use that...
 
     // encode the URI to be handled
     var escapedUriSpecToHandle = encodeURIComponent(aURI.spec);
 
-    // insert the encoded URI and create the object version 
+    // insert the encoded URI and create the object version.
     var uriSpecToSend = this.uriTemplate.replace("%s", escapedUriSpecToHandle);
-    var ioService = Cc["@mozilla.org/network/io-service;1"].
-                    getService(Ci.nsIIOService);
-    var uriToSend = ioService.newURI(uriSpecToSend);
-    
+    var uriToSend = Services.io.newURI(uriSpecToSend);
+
+    let policy = WebExtensionPolicy.getByURI(uriToSend);
+    let privateAllowed = !policy || policy.privateBrowsingAllowed;
+
     // if we have a window context, use the URI loader to load there
     if (aWindowContext) {
       try {
+        let remoteWindow = aWindowContext.getInterface(Ci.nsIRemoteWindowContext);
+        if (remoteWindow.usePrivateBrowsing && !privateAllowed) {
+          throw Components.Exception("Extension not allowed in private windows.",
+                                     Cr.NS_ERROR_FILE_NOT_FOUND);
+        }
         // getInterface throws if the object doesn't implement the given
         // interface, so this try/catch statement is more of an if.
         // If aWindowContext refers to a remote docshell, send the load
         // request to the correct process.
-        aWindowContext.getInterface(Ci.nsIRemoteWindowContext)
-                      .openURI(uriToSend);
+        remoteWindow.openURI(uriToSend);
         return;
       } catch (e) {
         if (e.result != Cr.NS_NOINTERFACE) {
           throw e;
         }
       }
 
+      try {
+        let isPrivate = aWindowContext.getInterface(Ci.nsIDocShell)
+                                      .QueryInterface(Ci.nsILoadContext)
+                                      .usePrivateBrowsing;
+        if (isPrivate && !privateAllowed) {
+          throw Components.Exception("Extension not allowed in private windows.",
+                                      Cr.NS_ERROR_FILE_NOT_FOUND);
+        }
+      } catch (e) {
+        if (e.result != Cr.NS_NOINTERFACE) {
+          throw e;
+        }
+      }
+
       // create a channel from this URI
       var channel = NetUtil.newChannel({
         uri: uriToSend,
-        loadUsingSystemPrincipal: true
+        loadUsingSystemPrincipal: true,
       });
       channel.loadFlags = Ci.nsIChannel.LOAD_DOCUMENT_URI;
 
       // load the channel
-      var uriLoader = Cc["@mozilla.org/uriloader;1"].
-                      getService(Ci.nsIURILoader);
+      var uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
+
       // XXX ideally, whether to pass the IS_CONTENT_PREFERRED flag should be
       // passed in from above.  Practically, the flag is probably a reasonable
       // default since browsers don't care much, and link click is likely to be
-      // the more interesting case for non-browser apps.  See 
+      // the more interesting case for non-browser apps.  See
       // <https://bugzilla.mozilla.org/show_bug.cgi?id=392957#c9> for details.
       uriLoader.openURI(channel, Ci.nsIURILoader.IS_CONTENT_PREFERRED,
                         aWindowContext);
       return;
-    } 
+    }
 
-    // since we don't have a window context, hand it off to a browser
-    var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
-      getService(Ci.nsIWindowMediator);
+    let win = Services.wm.getMostRecentWindow("navigator:browser");
 
-    // get browser dom window
-    var browserDOMWin = windowMediator.getMostRecentWindow("navigator:browser")
-                        .QueryInterface(Ci.nsIDOMChromeWindow)
-                        .browserDOMWindow;
+    // If this is an extension handler, check private browsing access.
+    if (!privateAllowed &&
+        PrivateBrowsingUtils.isContentWindowPrivate(win)) {
+      throw Components.Exception("Extension not allowed in private windows.",
+                                 Cr.NS_ERROR_FILE_NOT_FOUND);
+    }
 
-    // if we got an exception, there are several possible reasons why:
+    // If we get an exception, there are several possible reasons why:
     // a) this gecko embedding doesn't provide an nsIBrowserDOMWindow
     //    implementation (i.e. doesn't support browser-style functionality),
     //    so we need to kick the URL out to the OS default browser.  This is
     //    the subject of bug 394479.
     // b) this embedding does provide an nsIBrowserDOMWindow impl, but
     //    there doesn't happen to be a browser window open at the moment; one
     //    should be opened.  It's not clear whether this situation will really
     //    ever occur in real life.  If it does, the only API that I can find
     //    that seems reasonably likely to work for most embedders is the
-    //    command line handler.  
-    // c) something else went wrong 
+    //    command line handler.
+    // c) something else went wrong
     //
-    // it's not clear how one would differentiate between the three cases
-    // above, so for now we don't catch the exception
+    // It's not clear how one would differentiate between the three cases
+    // above, so for now we don't catch the exception.
 
     // openURI
-    browserDOMWin.openURI(uriToSend,
-                          null, // no window.opener
-                          Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
-                          Ci.nsIBrowserDOMWindow.OPEN_NEW,
-                          Services.scriptSecurityManager.getSystemPrincipal());
-      
-    return;
+    win.browserDOMWindow.openURI(uriToSend,
+                                 null, // no window.opener
+                                 Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
+                                 Ci.nsIBrowserDOMWindow.OPEN_NEW,
+                                 Services.scriptSecurityManager.getSystemPrincipal());
   },
 
-  //////////////////////////////////////////////////////////////////////////////
-  //// nsIWebHandlerApp
+  // nsIWebHandlerApp
 
   get uriTemplate() {
     return this._uriTemplate;
   },
 
   set uriTemplate(aURITemplate) {
     this._uriTemplate = aURITemplate;
   },
-
-  //////////////////////////////////////////////////////////////////////////////
-  //// nsISupports
-
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIWebHandlerApp, Ci.nsIHandlerApp])
 };
 
 var EXPORTED_SYMBOLS = ["nsWebHandlerApp"];
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -3322,17 +3322,19 @@ nsresult nsWindow::Create(nsIWidget *aPa
             // Wayland uses ARGB visual by default
             needsAlphaVisual = true;
           } else if (mCSDSupportLevel != CSD_SUPPORT_NONE) {
             if (shouldAccelerate) {
               needsAlphaVisual = true;
             } else {
               // We want to draw a transparent titlebar but we can't use
               // ARGB visual due to Bug 1516224.
-              mTransparencyBitmapForTitlebar = true;
+              // If we're on Mutter/X.org (Bug 1530252) just give up
+              // and don't render transparent corners at all.
+              mTransparencyBitmapForTitlebar = TitlebarCanUseShapeMask();
             }
           }
         }
       }
 
       bool isSetVisual = false;
       // If using WebRender on X11, we need to select a visual with a depth
       // buffer, as well as an alpha channel if transparency is requested. This
@@ -6468,17 +6470,17 @@ nsWindow::CSDSupportLevel nsWindow::GetS
     sCSDSupportLevel = CSD_SUPPORT_CLIENT;
     return sCSDSupportLevel;
   }
 
   const char *currentDesktop = getenv("XDG_CURRENT_DESKTOP");
   if (currentDesktop) {
     // GNOME Flashback (fallback)
     if (strstr(currentDesktop, "GNOME-Flashback:GNOME") != nullptr) {
-      sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+      sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
       // gnome-shell
     } else if (strstr(currentDesktop, "GNOME") != nullptr) {
       sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
     } else if (strstr(currentDesktop, "XFCE") != nullptr) {
       sCSDSupportLevel = CSD_SUPPORT_CLIENT;
     } else if (strstr(currentDesktop, "X-Cinnamon") != nullptr) {
       sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
       // KDE Plasma
@@ -6525,37 +6527,71 @@ nsWindow::CSDSupportLevel nsWindow::GetS
     if (csdOverride && g_strcmp0(csdOverride, "1") == 0) {
       sCSDSupportLevel = CSD_SUPPORT_CLIENT;
     }
   }
 
   return sCSDSupportLevel;
 }
 
+// Check for Mutter regression on X.org (Bug 1530252). In that case we
+// don't hide system titlebar by default as we can't draw transparent
+// corners reliably.
+bool nsWindow::TitlebarCanUseShapeMask()
+{
+  static int canUseShapeMask = -1;
+  if (canUseShapeMask != -1) {
+    return canUseShapeMask;
+  }
+  canUseShapeMask = true;
+
+  const char *currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+  if (!currentDesktop) {
+    return canUseShapeMask;
+  }
+
+  if (strstr(currentDesktop, "GNOME-Flashback:GNOME") != nullptr ||
+      strstr(currentDesktop, "GNOME") != nullptr) {
+    const char *sessionType = getenv("XDG_SESSION_TYPE");
+    canUseShapeMask = (sessionType && strstr(sessionType, "x11") == nullptr);
+  }
+
+  return canUseShapeMask;
+}
+
 bool nsWindow::HideTitlebarByDefault() {
   static int hideTitlebar = -1;
   if (hideTitlebar != -1) {
     return hideTitlebar;
   }
 
-  if (!Preferences::GetBool("widget.default-hidden-titlebar", false)) {
-    hideTitlebar = false;
+  // When user defined widget.default-hidden-titlebar don't do any
+  // heuristics and just follow it.
+  if (Preferences::HasUserValue("widget.default-hidden-titlebar")) {
+    hideTitlebar = Preferences::GetBool("widget.default-hidden-titlebar", false);
     return hideTitlebar;
   }
 
   const char *currentDesktop = getenv("XDG_CURRENT_DESKTOP");
   hideTitlebar =
       (currentDesktop && GetSystemCSDSupportLevel() != CSD_SUPPORT_NONE);
 
+  // Disable system titlebar for Gnome only for now. It uses window
+  // manager decorations and does not suffer from CSD Bugs #1525850, #1527837.
   if (hideTitlebar) {
     hideTitlebar =
         (strstr(currentDesktop, "GNOME-Flashback:GNOME") != nullptr ||
          strstr(currentDesktop, "GNOME") != nullptr);
   }
 
+  // We use shape mask to render the titlebar by default so check for it.
+  if (hideTitlebar) {
+    hideTitlebar = TitlebarCanUseShapeMask();
+  }
+
   return hideTitlebar;
 }
 
 int32_t nsWindow::RoundsWidgetCoordinatesTo() { return GdkScaleFactor(); }
 
 void nsWindow::GetCompositorWidgetInitData(
     mozilla::widget::CompositorWidgetInitData *aInitData) {
   // Make sure the window XID is propagated to X server, we can fail otherwise
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -384,16 +384,17 @@ class nsWindow final : public nsBaseWidg
   /**
    * Get the support of Client Side Decoration by checking
    * the XDG_CURRENT_DESKTOP environment variable.
    */
   static CSDSupportLevel GetSystemCSDSupportLevel();
 
   static bool HideTitlebarByDefault();
   static bool GetTopLevelWindowActiveState(nsIFrame* aFrame);
+  static bool TitlebarCanUseShapeMask();
 
  protected:
   virtual ~nsWindow();
 
   // event handling code
   void DispatchActivateEvent(void);
   void DispatchDeactivateEvent(void);
   void DispatchResized();