Merge mozilla-central to inbound. a=merge CLOSED TREE
authorNoemi Erli <nerli@mozilla.com>
Tue, 16 Oct 2018 01:22:27 +0300
changeset 499860 510af084fa3132f7a4611e33f0dee6fc6318eb7c
parent 499859 4c77e2acad03dea20d2f1c4a63935e43d146e758 (current diff)
parent 499814 4c11ab0cd98950983cfc957f579ace6c3e918a43 (diff)
child 499861 e1f69bad9ab0d850d7fc9804321fac80a8a4a8ed
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.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 inbound. a=merge CLOSED TREE
devtools/client/inspector/test/head.js
devtools/client/themes/floating-scrollbars-dark-theme.css
devtools/shared/client/tab-client.js
devtools/shared/client/thread-client.js
dom/media/tests/mochitest/test_getUserMedia_stopAudioStream.html
dom/media/tests/mochitest/test_getUserMedia_stopAudioStreamWithFollowupAudio.html
dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStream.html
dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html
dom/media/tests/mochitest/test_getUserMedia_stopVideoStream.html
dom/media/tests/mochitest/test_getUserMedia_stopVideoStreamWithFollowupVideo.html
dom/webidl/LocalMediaStream.webidl
mobile/android/app/mobile.js
toolkit/components/search/tests/xpcshell/data/search_blacklist.json
toolkit/components/search/tests/xpcshell/test_blacklist.js
toolkit/components/search/tests/xpcshell/test_json_cache_blacklist.js
--- a/.hgtags
+++ b/.hgtags
@@ -142,8 +142,13 @@ 5faab9e619901b1513fd4ca137747231be550def
 e33efdb3e1517d521deb949de3fcd6d9946ea440 FIREFOX_BETA_60_BASE
 fdd1a0082c71673239fc2f3a6a93de889c07a1be FIREFOX_NIGHTLY_60_END
 ccfd7b716a91241ddbc084cb7116ec561e56d5d1 FIREFOX_BETA_61_BASE
 93443d36d4bd53dba004f7b73430879f96daa681 FIREFOX_NIGHTLY_61_END
 9b74b9f2939a7ae3a0ea6e711dc32ed5203e03ff FIREFOX_BETA_62_BASE
 4f6e597104dabedfecfafa2ab63dc79fd7f8bc7a FIREFOX_NIGHTLY_62_END
 190b827aaa2b5e6fb9af7a0defb238ccc35f8b9e FIREFOX_BETA_63_BASE
 034c5ef24e98b0ce85fa849face079f568eb397c FIREFOX_NIGHTLY_63_END
+4a230b07f0cbf48e87dcb4265ea2d00893bb1b62 FIREFOX_BETA_64_BASE
+4a230b07f0cbf48e87dcb4265ea2d00893bb1b62 FIREFOX_BETA_64_BASE
+224715760a637bc37c14794839468a954f1f2695 FIREFOX_BETA_64_BASE
+224715760a637bc37c14794839468a954f1f2695 FIREFOX_BETA_64_BASE
+ad179a6fc14cbd41d10a018ac4a3822db119de3b FIREFOX_BETA_64_BASE
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -260,20 +260,17 @@ if (AppConstants.platform != "macosx") {
 Object.defineProperty(this, "gURLBar", {
   configurable: true,
   enumerable: true,
   get() {
     delete this.gURLBar;
 
     let element = document.getElementById("urlbar");
 
-    // For now, always use the legacy implementation in the first window to
-    // have a usable address bar e.g. for accessing about:config.
-    if (BrowserWindowTracker.windowCount <= 1 ||
-        !Services.prefs.getBoolPref("browser.urlbar.quantumbar", false)) {
+    if (!Services.prefs.getBoolPref("browser.urlbar.quantumbar", false)) {
       return this.gURLBar = element;
     }
 
     // Disable the legacy XBL binding.
     element.setAttribute("quantumbar", "true");
 
     // Re-focus the input field if it was focused before switching bindings.
     if (element.hasAttribute("focused")) {
--- a/browser/base/content/test/performance/browser_preferences_usage.js
+++ b/browser/base/content/test/performance/browser_preferences_usage.js
@@ -103,17 +103,17 @@ add_task(async function startup() {
     },
 
     // Disabling screenshots in the default test profile triggers some
     // work in the chrome registry that reads this pref.  This can be removed
     // when bootstrapped extensions are gone, or even when screenshots
     // moves away from bootstrap (bug 1422437)
     "chrome.override_package.global": {
       min: 0,
-      max: 50,
+      max: 70,
     },
   };
 
   let startupRecorder = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject;
   await startupRecorder.done;
 
   ok(startupRecorder.data.prefStats, "startupRecorder has prefStats");
 
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -59,17 +59,17 @@ const whitelist = {
     "resource://gre/modules/Readerable.jsm",
     "resource://gre/modules/WebProgressChild.jsm",
 
     // Pocket
     "chrome://pocket/content/AboutPocket.jsm",
 
     // Telemetry
     "resource://gre/modules/TelemetryController.jsm", // bug 1470339
-    "resource://gre/modules/TelemetrySession.jsm", // bug 1470339
+    "resource://gre/modules/MemoryTelemetry.jsm", // bug 1481812
     "resource://gre/modules/TelemetryUtils.jsm", // bug 1470339
 
     // Extensions
     "resource://gre/modules/ExtensionUtils.jsm",
     "resource://gre/modules/MessageChannel.jsm",
   ]),
 };
 
--- a/browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js
+++ b/browser/components/extensions/test/browser/browser_ext_menus_replace_menu.js
@@ -21,18 +21,18 @@ function checkIsDefaultMenuItemVisible(v
 // - The usual extension filtering behavior (e.g. documentUrlPatterns and
 //   targetUrlPatterns) is still applied; some menu items are therefore hidden.
 // - Calling overrideContext({showDefaults:true}) causes the default menu items
 //   to be shown, but only after the extension's.
 // - overrideContext expires after the menu is opened once.
 // - overrideContext can be called from shadow DOM.
 add_task(async function overrideContext_in_extension_tab() {
   await SpecialPowers.pushPrefEnv({
-    set: [["dom.webcomponents.shadowdom.enabled", true]],
-  });
+    set: [["dom.webcomponents.shadowdom.enabled", true],
+          ["security.allow_eval_with_system_principal", true]]});
 
   function extensionTabScript() {
     document.addEventListener("contextmenu", () => {
       browser.menus.overrideContext({});
       browser.test.sendMessage("oncontextmenu_in_dom_part_1");
     }, {once: true});
 
     let shadowRoot = document.getElementById("shadowHost").attachShadow({mode: "open"});
--- a/browser/components/extensions/test/browser/browser_ext_menus_targetElement_shadow.js
+++ b/browser/components/extensions/test/browser/browser_ext_menus_targetElement_shadow.js
@@ -2,17 +2,21 @@
  * 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 PAGE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context.html";
 
 add_task(async function menuInShadowDOM() {
   Services.prefs.setBoolPref("dom.webcomponents.shadowdom.enabled", true);
-  registerCleanupFunction(() => Services.prefs.clearUserPref("dom.webcomponents.shadowdom.enabled"));
+  Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("dom.webcomponents.shadowdom.enabled");
+    Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
+  });
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE);
   gBrowser.selectedTab = tab;
 
   async function background() {
     browser.menus.onShown.addListener(async (info, tab) => {
       browser.test.assertTrue(Number.isInteger(info.targetElementId), `${info.targetElementId} should be an integer`);
       browser.test.assertEq("all,link", info.contexts.sort().join(","), "Expected context");
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget.js
@@ -1,15 +1,18 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 Services.scriptloader.loadSubScript(new URL("head_webNavigation.js", gTestPath).href,
                                     this);
 
+SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+                                    true]]});
+
 async function background() {
   const tabs = await browser.tabs.query({active: true, currentWindow: true});
   const sourceTabId = tabs[0].id;
 
   const sourceTabFrames = await browser.webNavigation.getAllFrames({tabId: sourceTabId});
 
   browser.webNavigation.onCreatedNavigationTarget.addListener((msg) => {
     browser.test.sendMessage("webNavOnCreated", msg);
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_contextmenu.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_contextmenu.js
@@ -1,15 +1,18 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 Services.scriptloader.loadSubScript(new URL("head_webNavigation.js", gTestPath).href,
                                     this);
 
+SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+                                    true]]});
+
 async function clickContextMenuItem({pageElementSelector, contextMenuItemLabel}) {
   const contentAreaContextMenu = await openContextMenu(pageElementSelector);
   const item = contentAreaContextMenu.getElementsByAttribute("label", contextMenuItemLabel);
   is(item.length, 1, `found contextMenu item for "${contextMenuItemLabel}"`);
   item[0].click();
   await closeContextMenu();
 }
 
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2178,17 +2178,17 @@ BrowserGlue.prototype = {
       }
     }
   },
 
   // eslint-disable-next-line complexity
   _migrateUI: function BG__migrateUI() {
     // Use an increasing number to keep track of the current migration state.
     // Completely unrelated to the current Firefox release number.
-    const UI_VERSION = 75;
+    const UI_VERSION = 76;
     const BROWSER_DOCURL = AppConstants.BROWSER_CHROME_URL;
 
     let currentUIVersion;
     if (Services.prefs.prefHasUserValue("browser.migration.version")) {
       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
     } else {
       // This is a new profile, nothing to migrate.
       Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
@@ -2302,26 +2302,18 @@ BrowserGlue.prototype = {
         let malwareList = Services.prefs.getCharPref(MALWARE_PREF);
         if (malwareList.includes("goog-malware-shavar")) {
           malwareList.replace("goog-malware-shavar", "goog-malware-proto");
           Services.prefs.setCharPref(MALWARE_PREF, malwareList);
         }
       }
     }
 
-    if (currentUIVersion < 54) {
-      // Clear old onboarding prefs from profile (bug 1462415)
-      let onboardingPrefs = Services.prefs.getBranch("browser.onboarding.");
-      if (onboardingPrefs) {
-        let onboardingPrefsArray = onboardingPrefs.getChildList("");
-        for (let item of onboardingPrefsArray) {
-          Services.prefs.clearUserPref("browser.onboarding." + item);
-        }
-      }
-    }
+    // currentUIVersion < 49 and < 54 were originally used for onboarding prefs and
+    // have since then been removed and cleared in currentUIVersion < 76
 
     if (currentUIVersion < 55) {
       Services.prefs.clearUserPref("browser.customizemode.tip0.shown");
     }
 
     if (currentUIVersion < 56) {
       // Prior to the end of the Firefox 57 cycle, the sidebarcommand being present
       // or not was the only thing that distinguished whether the sidebar was open.
@@ -2536,16 +2528,27 @@ BrowserGlue.prototype = {
 
     if (currentUIVersion < 75) {
       // Ensure we try to migrate any live bookmarks the user might have, trying up to
       // 5 times. We set this early, and here, to avoid running the migration on
       // new profile (or, indeed, ever creating the pref there).
       Services.prefs.setIntPref("browser.livebookmarks.migrationAttemptsLeft", 5);
     }
 
+    if (currentUIVersion < 76) {
+      // Clear old onboarding prefs from profile (bug 1462415)
+      let onboardingPrefs = Services.prefs.getBranch("browser.onboarding.");
+      if (onboardingPrefs) {
+        let onboardingPrefsArray = onboardingPrefs.getChildList("");
+        for (let item of onboardingPrefsArray) {
+          Services.prefs.clearUserPref("browser.onboarding." + item);
+        }
+      }
+    }
+
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
   },
 
   _checkForDefaultBrowser() {
     // Perform default browser checking.
     if (!ShellService) {
       return;
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_ui.js
@@ -3,16 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // This test makes sure that the gPrivateBrowsingUI object, the Private Browsing
 // menu item and its XUL <command> element work correctly.
 
 function test() {
   // initialization
   waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+                                      true]]});
   let windowsToClose = [];
   let testURI = "about:blank";
   let pbMenuItem;
   let cmd;
 
   function doTest(aIsPrivateMode, aWindow, aCallback) {
     BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser).then(function() {
       ok(aWindow.gPrivateBrowsingUI, "The gPrivateBrowsingUI object exists");
--- a/browser/extensions/webcompat-reporter/background.js
+++ b/browser/extensions/webcompat-reporter/background.js
@@ -104,17 +104,17 @@ async function openWebCompatTab(compatIn
   };
   if (details["gfx.webrender.all"] || details["gfx.webrender.enabled"]) {
     params.label.push("type-webrender-enabled");
   }
   if (compatInfo.hasTrackingContentBlocked) {
     params.label.push(`type-tracking-protection-${compatInfo.blockList}`);
   }
 
-  const tab = await browser.tabs.create({});
+  const tab = await browser.tabs.create({url: "about:blank"});
   const json = stripNonASCIIChars(JSON.stringify(params));
   await browser.tabExtras.loadURIWithPostData(tab.id, url.href, json,
                                               "application/json");
   await browser.tabs.executeScript(tab.id, {
     runAt: "document_end",
     code: `(function() {
       async function sendScreenshot(dataURI) {
         const res = await fetch(dataURI);
--- a/browser/extensions/webcompat-reporter/experimentalAPIs/tabExtras.js
+++ b/browser/extensions/webcompat-reporter/experimentalAPIs/tabExtras.js
@@ -116,17 +116,19 @@ this.tabExtras = class extends Extension
                        .createInstance(Ci.nsIMIMEInputStream);
           post.addHeader("Content-Type", postDataContentType ||
                                          "application/x-www-form-urlencoded");
           post.setData(stringStream);
 
           return new Promise(resolve => {
             const listener = {
               onLocationChange(browser, webProgress, request, locationURI, flags) {
-                if (webProgress.isTopLevel && browser === tab.browser) {
+                if (webProgress.isTopLevel &&
+                    browser === tab.browser &&
+                    locationURI.spec === url) {
                   windowTracker.removeListener("progress", listener);
                   resolve();
                 }
               },
             };
             windowTracker.addListener("progress", listener);
             tab.browser.webNavigation.loadURIWithOptions(url, null, null, null,
                                                          post, null, null, null);
--- a/browser/themes/shared/controlcenter/connection.svg
+++ b/browser/themes/shared/controlcenter/connection.svg
@@ -1,6 +1,7 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="context-fill" fill-opacity="context-fill-opacity">
-  <path d="M18.75 9.977H18V7A6 6 0 0 0 6 7v2.977h-.75A2.25 2.25 0 0 0 3 12.227v7.523A2.25 2.25 0 0 0 5.25 22h13.5A2.25 2.25 0 0 0 21 19.75v-7.523a2.25 2.25 0 0 0-2.25-2.25zM9 7a3 3 0 0 1 6 0v2.977H9z"/>
+  <path d="M12 0a6 6 0 0 0-6 6v5.29a6 6 0 0 0 12 0V6a6 6 0 0 0-6-6zm4 12.25A3.75 3.75 0 0 1 12.25 16h-.5A3.75 3.75 0 0 1 8 12.25v-6.5A3.75 3.75 0 0 1 11.75 2h.5A3.75 3.75 0 0 1 16 5.75z"/>
+  <rect x="3" y="10" width="18" height="13" rx="2" ry="2"/>
 </svg>
--- a/browser/themes/shared/controlcenter/tracking-protection.svg
+++ b/browser/themes/shared/controlcenter/tracking-protection.svg
@@ -1,7 +1,7 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="context-fill" fill-opacity="context-fill-opacity">
-  <path d="M27 7.154a2.688 2.688 0 0 0-2.226-2.662L16 2.985 7.227 4.491A2.69 2.69 0 0 0 5 7.153c-.006 2.031.007 5.681.155 7.319.425 4.65 1.282 7.191 3.4 10.07a11.4 11.4 0 0 0 7.33 4.452l.112.012.112-.012a11.4 11.4 0 0 0 7.33-4.452c2.12-2.879 2.977-5.42 3.4-10.07.153-1.638.166-5.288.161-7.318zm-2.147 7.137c-.391 4.287-1.125 6.49-3.021 9.065A9.562 9.562 0 0 1 16 26.989a9.568 9.568 0 0 1-5.831-3.633c-1.9-2.575-2.63-4.778-3.021-9.065C7 12.676 7 8.765 7 7.159a.7.7 0 0 1 .563-.7L16 5.015l8.436 1.448a.694.694 0 0 1 .563.7c.001 1.602.001 5.512-.147 7.128z"/>
-  <path d="M10.148 13.584c.465 5.1 1.336 6.611 2.716 8.486A6.459 6.459 0 0 0 16 24.337V7.75l-6 1.03c.021 2.264.073 3.979.148 4.804z"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="context-fill" fill-opacity="context-fill-opacity">
+  <path d="M12 23.81h-.11a10.34 10.34 0 0 1-6.67-4c-1.92-2.61-2.7-4.91-3.08-9.12C2 9.24 2 6.28 2 4a2.53 2.53 0 0 1 2.09-2.5L12 .19l7.91 1.35A2.53 2.53 0 0 1 22 4c0 1.83 0 5.12-.14 6.59-.38 4.21-1.16 6.51-3.08 9.12a10.34 10.34 0 0 1-6.67 4zm0-21.6l-7.58 1.3A.54.54 0 0 0 4 4c0 1.8 0 5 .13 6.41.35 3.84 1 5.81 2.7 8.11A8.53 8.53 0 0 0 12 21.79a8.5 8.5 0 0 0 5.17-3.23c1.69-2.3 2.35-4.27 2.7-8.11C20 9.06 20 5.84 20 4a.52.52 0 0 0-.42-.52z"/>
+  <path d="M12 4.57l-5.4.93c0 2 .07 3.58.13 4.33.42 4.59 1.21 5.95 2.45 7.63a5.71 5.71 0 0 0 2.82 2z"/>
 </svg>
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -471,16 +471,24 @@ NS_IMPL_ISUPPORTS(nsScriptSecurityManage
 ///////////////// Security Checks /////////////////
 
 bool
 nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx,
                                                               JS::HandleValue aValue)
 {
     MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
     nsCOMPtr<nsIPrincipal> subjectPrincipal = nsContentUtils::SubjectPrincipal();
+
+#if defined(DEBUG) && !defined(ANDROID)
+    if (!(Preferences::GetBool("security.allow_eval_with_system_principal"))) {
+      MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(subjectPrincipal),
+               "do not use eval with system privileges");
+    }
+#endif
+
     nsCOMPtr<nsIContentSecurityPolicy> csp;
     nsresult rv = subjectPrincipal->GetCsp(getter_AddRefs(csp));
     NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get CSP from principal.");
 
     // don't do anything unless there's a CSP
     if (!csp)
         return true;
 
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
@@ -86,17 +86,27 @@ add_task(async function testWebExtension
   // 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;
-    let popupFramePromise;
+    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 =
@@ -106,28 +116,16 @@ add_task(async function testWebExtension
             }, { once: true });
           });
         };
 
         dump(`Clicking the menu button\n`);
         await clickNoAutoHideMenu();
         dump(`Clicked the menu button\n`);
 
-        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");
-
         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,
--- a/devtools/client/accessibility/test/browser/head.js
+++ b/devtools/client/accessibility/test/browser/head.js
@@ -403,11 +403,11 @@ function reload(target, waitForTargetEve
 
 /**
  * Navigate to a new URL within the panel target.
  * @param  {Object} target             Panel target.
  * @param  {Srting} url                URL to navigate to.
  * @param  {String} waitForTargetEvent Event to wait for after reload.
  */
 function navigate(target, url, waitForTargetEvent = "navigate") {
-  executeSoon(() => target.activeTab.navigateTo(url));
+  executeSoon(() => target.activeTab.navigateTo({ url }));
   return once(target, waitForTargetEvent);
 }
--- a/devtools/client/application/initializer.js
+++ b/devtools/client/application/initializer.js
@@ -38,18 +38,17 @@ window.Application = {
     this.store = configureStore();
     this.actions = bindActionCreators(actions, this.store.dispatch);
 
     const serviceContainer = {
       selectTool(toolId) {
         return toolbox.selectTool(toolId);
       }
     };
-
-    this.client.addListener("workerListChanged", this.updateWorkers);
+    this.toolbox.target.activeTab.on("workerListChanged", this.updateWorkers);
     this.client.addListener("serviceWorkerRegistrationListChanged", this.updateWorkers);
     this.client.addListener("registration-changed", this.updateWorkers);
     this.client.addListener("processListChanged", this.updateWorkers);
     this.toolbox.target.on("navigate", this.updateDomain);
 
     this.updateDomain();
     await this.updateWorkers();
 
@@ -84,17 +83,17 @@ window.Application = {
     this.actions.updateWorkers(service);
   },
 
   updateDomain() {
     this.actions.updateDomain(this.toolbox.target.url);
   },
 
   destroy() {
-    this.client.removeListener("workerListChanged", this.updateWorkers);
+    this.toolbox.target.activeTab.off("workerListChanged", this.updateWorkers);
     this.client.removeListener("serviceWorkerRegistrationListChanged",
       this.updateWorkers);
     this.client.removeListener("registration-changed", this.updateWorkers);
     this.client.removeListener("processListChanged", this.updateWorkers);
 
     this.toolbox.target.off("navigate", this.updateDomain);
 
     unmountComponentAtNode(this.mount);
--- a/devtools/client/application/test/head.js
+++ b/devtools/client/application/test/head.js
@@ -36,17 +36,17 @@ async function enableApplicationPanel() 
   await pushPref("devtools.application.enabled", true);
 }
 
 function getWorkerContainers(doc) {
   return doc.querySelectorAll(".js-sw-container");
 }
 
 function navigate(target, url, waitForTargetEvent = "navigate") {
-  executeSoon(() => target.activeTab.navigateTo(url));
+  executeSoon(() => target.activeTab.navigateTo({ url }));
   return once(target, waitForTargetEvent);
 }
 
 async function openNewTabAndApplicationPanel(url) {
   const tab = await addTab(url);
   const target = await TargetFactory.forTab(tab);
 
   const toolbox = await gDevTools.showToolbox(target, "application");
--- a/devtools/client/canvasdebugger/test/head.js
+++ b/devtools/client/canvasdebugger/test/head.js
@@ -102,17 +102,17 @@ function isTestingSupported() {
 }
 
 function navigateInHistory(aTarget, aDirection, aWaitForTargetEvent = "navigate") {
   executeSoon(() => content.history[aDirection]());
   return once(aTarget, aWaitForTargetEvent);
 }
 
 function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") {
-  executeSoon(() => aTarget.activeTab.navigateTo(aUrl));
+  executeSoon(() => aTarget.activeTab.navigateTo({ url: aUrl }));
   return once(aTarget, aWaitForTargetEvent);
 }
 
 function reload(aTarget, aWaitForTargetEvent = "navigate") {
   executeSoon(() => aTarget.activeTab.reload());
   return once(aTarget, aWaitForTargetEvent);
 }
 
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -473,21 +473,32 @@ Workers.prototype = {
   },
 
   connect: function () {
     if (!Prefs.workersEnabled) {
       return;
     }
 
     this._updateWorkerList();
-    this._tabClient.addListener("workerListChanged", this._onWorkerListChanged);
+
+    // `_tabClient` can be BrowsingContextTargetFront (protocol.js front) or
+    // WorkerClient/DebuggerClient (old fashion client)
+    if (typeof(this._tabClient.on) == "function") {
+      this._tabClient.on("workerListChanged", this._onWorkerListChanged);
+    } else {
+      this._tabClient.addListener("workerListChanged", this._onWorkerListChanged);
+    }
   },
 
   disconnect: function () {
-    this._tabClient.removeListener("workerListChanged", this._onWorkerListChanged);
+    if (typeof(this._tabClient.on) == "function") {
+      this._tabClient.off("workerListChanged", this._onWorkerListChanged);
+    } else {
+      this._tabClient.removeListener("workerListChanged", this._onWorkerListChanged);
+    }
   },
 
   _updateWorkerList: function () {
     if (!this._tabClient.listWorkers) {
       return;
     }
 
     this._tabClient.listWorkers().then((response) => {
--- a/devtools/client/debugger/new/src/client/firefox/commands.js
+++ b/devtools/client/debugger/new/src/client/firefox/commands.js
@@ -242,17 +242,17 @@ function debuggeeCommand(script) {
   request.emit("json-reply", {});
 
   debuggerClient._activeRequests.delete(consoleActor);
 
   return Promise.resolve();
 }
 
 function navigate(url) {
-  return tabTarget.activeTab.navigateTo(url);
+  return tabTarget.activeTab.navigateTo({ url });
 }
 
 function reload() {
   return tabTarget.activeTab.reload();
 }
 
 function getProperties(grip) {
   const objClient = threadClient.pauseGrip(grip);
@@ -450,9 +450,9 @@ const clientCommands = {
   disablePrettyPrint,
   fetchSources,
   fetchWorkers,
   sendPacket,
   setPausePoints,
   setSkipPausing
 };
 exports.setupCommands = setupCommands;
-exports.clientCommands = clientCommands;
\ No newline at end of file
+exports.clientCommands = clientCommands;
--- a/devtools/client/debugger/new/src/client/firefox/events.js
+++ b/devtools/client/debugger/new/src/client/firefox/events.js
@@ -36,17 +36,23 @@ function setupEvents(dependencies) {
   });
 
   if (threadClient) {
     Object.keys(clientEvents).forEach(eventName => {
       threadClient.addListener(eventName, clientEvents[eventName]);
     });
 
     if (threadClient._parent) {
-      threadClient._parent.addListener("workerListChanged", workerListChanged);
+      // Parent may be BrowsingContextTargetFront and be protocol.js.
+      // Or DebuggerClient/WorkerClient and still be old fashion actor.
+      if (threadClient._parent.on) {
+        threadClient._parent.on("workerListChanged", workerListChanged);
+      } else {
+        threadClient._parent.addListener("workerListChanged", workerListChanged);
+      }
     }
   }
 }
 
 async function paused(_, packet) {
   // If paused by an explicit interrupt, which are generated by the
   // slow script dialog and internal events such as setting
   // breakpoints, ignore the event.
@@ -108,9 +114,9 @@ function workerListChanged() {
 }
 
 const clientEvents = {
   paused,
   resumed,
   newSource
 };
 exports.setupEvents = setupEvents;
-exports.clientEvents = clientEvents;
\ No newline at end of file
+exports.clientEvents = clientEvents;
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-inline-cache.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-inline-cache.js
@@ -71,17 +71,17 @@ add_task(async function() {
   let dbgValue = await findSource(dbg, "inline-cache.html");
   info(`Debugger text: ${dbgValue.text}`);
   ok(
     dbgValue.text.includes(pageValue),
     "Debugger loads from cache, gets value 1 like page"
   );
 
   info("Disable HTTP cache for page");
-  await toolbox.target.activeTab.reconfigure({ cacheDisabled: true });
+  await toolbox.target.activeTab.reconfigure({ options: { cacheDisabled: true } });
   makeChanges();
 
   info("Reload inside debugger with toolbox caching disabled (attempt 1)");
   await reloadTabAndDebugger(tab, dbg);
   pageValue = await getPageValue(tab);
   is(pageValue, "let x = 2;", "Content loads from network, has doc value 2");
   await waitForLoadedSource(dbg, "inline-cache.html");
   dbgValue = await findSource(dbg, "inline-cache.html");
@@ -101,17 +101,17 @@ add_task(async function() {
   dbgValue = await findSource(dbg, "inline-cache.html");
   info(`Debugger text: ${dbgValue.text}`);
   ok(
     dbgValue.text.includes(pageValue),
     "Debugger loads from network, gets value 3 like page"
   );
 
   info("Enable HTTP cache for page");
-  await toolbox.target.activeTab.reconfigure({ cacheDisabled: false });
+  await toolbox.target.activeTab.reconfigure({ options: { cacheDisabled: false } });
   makeChanges();
 
   // Even though the HTTP cache is now enabled, Gecko sets the VALIDATE_ALWAYS flag when
   // reloading the page.  So, it will always make a request to the server for the main
   // document contents.
 
   info("Reload inside debugger with toolbox caching enabled (attempt 1)");
   await reloadTabAndDebugger(tab, dbg);
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -175,18 +175,18 @@ function getAddonActorForId(aClient, aAd
     deferred.resolve(addonTargetActor);
   });
 
   return deferred.promise;
 }
 
 async function attachTargetActorForUrl(aClient, aUrl) {
   let grip = await getTargetActorForUrl(aClient, aUrl);
-  let [ response ] = await aClient.attachTarget(grip.actor);
-  return [grip, response];
+  let [ response, front ] = await aClient.attachTarget(grip.actor);
+  return [grip, response, front];
 }
 
 async function attachThreadActorForUrl(aClient, aUrl) {
   let [grip, response] = await attachTargetActorForUrl(aClient, aUrl);
   let [response2, threadClient] = await aClient.attachThread(response.threadActor);
   await threadClient.resume();
   return threadClient;
 }
@@ -445,17 +445,17 @@ function ensureThreadClientState(aPanel,
     return promise.resolve(null);
   } else {
     return waitForThreadEvents(aPanel, aState);
   }
 }
 
 function reload(aPanel, aUrl) {
   let activeTab = aPanel.panelWin.DebuggerController._target.activeTab;
-  aUrl ? activeTab.navigateTo(aUrl) : activeTab.reload();
+  aUrl ? activeTab.navigateTo({ url: aUrl }) : activeTab.reload();
 }
 
 function navigateActiveTabTo(aPanel, aUrl, aWaitForEventName, aEventRepeat) {
   let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat);
   reload(aPanel, aUrl);
   return finished;
 }
 
@@ -1104,24 +1104,19 @@ function findWorker(workers, url) {
   return null;
 }
 
 function attachWorker(tabClient, worker) {
   info("Attaching to worker with url '" + worker.url + "'.");
   return tabClient.attachWorker(worker.actor);
 }
 
-function waitForWorkerListChanged(tabClient) {
+function waitForWorkerListChanged(targetFront) {
   info("Waiting for worker list to change.");
-  return new Promise(function (resolve) {
-    tabClient.addListener("workerListChanged", function listener() {
-      tabClient.removeListener("workerListChanged", listener);
-      resolve();
-    });
-  });
+  return targetFront.once("workerListChanged");
 }
 
 function attachThread(workerClient, options) {
   info("Attaching to thread.");
   return workerClient.attachThread(options);
 }
 
 function waitForWorkerClose(workerClient) {
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -189,16 +189,17 @@ const TargetFactory = exports.TargetFact
  *                  frame scripts...
  * @param {xul:tab} tab (optional)
  *                  If the target is a local Firefox tab, a reference to the firefox
  *                  frontend tab object.
  */
 function TabTarget({ form, client, chrome, tab = null }) {
   EventEmitter.decorate(this);
   this.destroy = this.destroy.bind(this);
+  this._onTabNavigated = this._onTabNavigated.bind(this);
   this.activeTab = this.activeConsole = null;
 
   this._form = form;
   this._url = form.url;
   this._title = form.title;
 
   this._client = client;
   this._chrome = chrome;
@@ -513,16 +514,22 @@ TabTarget.prototype = {
       return this._attach;
     }
 
     // Attach the target actor
     const attachTarget = async () => {
       const [response, tabClient] = await this._client.attachTarget(this._form.actor);
       this.activeTab = tabClient;
       this.threadActor = response.threadActor;
+
+      this.activeTab.on("tabNavigated", this._onTabNavigated);
+      this._onFrameUpdate = packet => {
+        this.emit("frame-update", packet);
+      };
+      this.activeTab.on("frameUpdate", this._onFrameUpdate);
     };
 
     // Attach the console actor
     const attachConsole = async () => {
       const [, consoleClient] = await this._client.attachConsole(
         this._form.consoleActor, []);
       this.activeConsole = consoleClient;
 
@@ -544,25 +551,27 @@ TabTarget.prototype = {
           to: this._form.actor, type: "connect",
         });
 
         this._form = form;
         this._url = form.url;
         this._title = form.title;
       }
 
-      this._setupRemoteListeners();
-
       // AddonActor and chrome debugging on RootActor don't inherit from
       // BrowsingContextTargetActor (i.e. this.isBrowsingContext=false) and don't need
       // to be attached.
       if (this.isBrowsingContext) {
         await attachTarget();
       }
 
+      // _setupRemoteListeners has to be called after the potential call to `attachTarget`
+      // as it depends on `activeTab` which is set by this method.
+      this._setupRemoteListeners();
+
       // But all target actor have a console actor to attach
       return attachConsole();
     })();
 
     return this._attach;
   },
 
   /**
@@ -581,79 +590,104 @@ TabTarget.prototype = {
     if (this._tab.ownerDocument.defaultView) {
       this._tab.ownerDocument.defaultView.removeEventListener("unload", this);
     }
     this._tab.removeEventListener("TabClose", this);
     this._tab.removeEventListener("TabRemotenessChange", this);
   },
 
   /**
+   * Event listener for tabNavigated packet sent by activeTab's front.
+   */
+  _onTabNavigated: function(packet) {
+    const event = Object.create(null);
+    event.url = packet.url;
+    event.title = packet.title;
+    event.nativeConsoleAPI = packet.nativeConsoleAPI;
+    event.isFrameSwitching = packet.isFrameSwitching;
+
+    // Keep the title unmodified when a developer toolbox switches frame
+    // for a tab (Bug 1261687), but always update the title when the target
+    // is a WebExtension (where the addon name is always included in the title
+    // and the url is supposed to be updated every time the selected frame changes).
+    if (!packet.isFrameSwitching || this.isWebExtension) {
+      this._url = packet.url;
+      this._title = packet.title;
+    }
+
+    // Send any stored event payload (DOMWindow or nsIRequest) for backwards
+    // compatibility with non-remotable tools.
+    if (packet.state == "start") {
+      event._navPayload = this._navRequest;
+      this.emit("will-navigate", event);
+      this._navRequest = null;
+    } else {
+      event._navPayload = this._navWindow;
+      this.emit("navigate", event);
+      this._navWindow = null;
+    }
+  },
+
+  /**
    * Setup listeners for remote debugging, updating existing ones as necessary.
    */
   _setupRemoteListeners: function() {
     this.client.addListener("closed", this.destroy);
 
-    this._onTabDetached = (type, packet) => {
-      // We have to filter message to ensure that this detach is for this tab
-      if (packet.from == this._form.actor) {
-        this.destroy();
-      }
-    };
-    this.client.addListener("tabDetached", this._onTabDetached);
-
-    this._onTabNavigated = (type, packet) => {
-      const event = Object.create(null);
-      event.url = packet.url;
-      event.title = packet.title;
-      event.nativeConsoleAPI = packet.nativeConsoleAPI;
-      event.isFrameSwitching = packet.isFrameSwitching;
+    // For now, only browsing-context inherited actors are using a front,
+    // for which events have to be listened on the front itself.
+    // For other actors (ContentProcessTargetActor and AddonTargetActor), events should
+    // still be listened directly on the client. This should be ultimately cleaned up to
+    // only listen from a front by bug 1465635.
+    if (this.activeTab) {
+      this.activeTab.on("tabDetached", this.destroy);
 
-      // Keep the title unmodified when a developer toolbox switches frame
-      // for a tab (Bug 1261687), but always update the title when the target
-      // is a WebExtension (where the addon name is always included in the title
-      // and the url is supposed to be updated every time the selected frame changes).
-      if (!packet.isFrameSwitching || this.isWebExtension) {
-        this._url = packet.url;
-        this._title = packet.title;
-      }
+      // These events should be ultimately listened from the thread client as
+      // they are coming from it and no longer go through the Target Actor/Front.
+      this._onSourceUpdated = packet => this.emit("source-updated", packet);
+      this.activeTab.on("newSource", this._onSourceUpdated);
+      this.activeTab.on("updatedSource", this._onSourceUpdated);
+    } else {
+      this._onTabDetached = (type, packet) => {
+        // We have to filter message to ensure that this detach is for this tab
+        if (packet.from == this._form.actor) {
+          this.destroy();
+        }
+      };
+      this.client.addListener("tabDetached", this._onTabDetached);
 
-      // Send any stored event payload (DOMWindow or nsIRequest) for backwards
-      // compatibility with non-remotable tools.
-      if (packet.state == "start") {
-        event._navPayload = this._navRequest;
-        this.emit("will-navigate", event);
-        this._navRequest = null;
-      } else {
-        event._navPayload = this._navWindow;
-        this.emit("navigate", event);
-        this._navWindow = null;
-      }
-    };
-    this.client.addListener("tabNavigated", this._onTabNavigated);
-
-    this._onFrameUpdate = (type, packet) => {
-      this.emit("frame-update", packet);
-    };
-    this.client.addListener("frameUpdate", this._onFrameUpdate);
-
-    this._onSourceUpdated = (event, packet) => this.emit("source-updated", packet);
-    this.client.addListener("newSource", this._onSourceUpdated);
-    this.client.addListener("updatedSource", this._onSourceUpdated);
+      this._onSourceUpdated = (type, packet) => this.emit("source-updated", packet);
+      this.client.addListener("newSource", this._onSourceUpdated);
+      this.client.addListener("updatedSource", this._onSourceUpdated);
+    }
   },
 
   /**
    * Teardown listeners for remote debugging.
    */
   _teardownRemoteListeners: function() {
+    // Remove listeners set in _setupRemoteListeners
     this.client.removeListener("closed", this.destroy);
-    this.client.removeListener("tabNavigated", this._onTabNavigated);
-    this.client.removeListener("tabDetached", this._onTabDetached);
-    this.client.removeListener("frameUpdate", this._onFrameUpdate);
-    this.client.removeListener("newSource", this._onSourceUpdated);
-    this.client.removeListener("updatedSource", this._onSourceUpdated);
+    if (this.activeTab) {
+      this.activeTab.off("tabDetached", this.destroy);
+      this.activeTab.off("newSource", this._onSourceUpdated);
+      this.activeTab.off("updatedSource", this._onSourceUpdated);
+    } else {
+      this.client.removeListener("tabDetached", this._onTabDetached);
+      this.client.removeListener("newSource", this._onSourceUpdated);
+      this.client.removeListener("updatedSource", this._onSourceUpdated);
+    }
+
+    // Remove listeners set in attachTarget
+    if (this.activeTab) {
+      this.activeTab.off("tabNavigated", this._onTabNavigated);
+      this.activeTab.off("frameUpdate", this._onFrameUpdate);
+    }
+
+    // Remove listeners set in attachConsole
     if (this.activeConsole && this._onInspectObject) {
       this.activeConsole.off("inspectObject", this._onInspectObject);
     }
   },
 
   /**
    * Handle tabs events.
    */
@@ -736,17 +770,21 @@ TabTarget.prototype = {
           // We started with a local tab and created the client ourselves, so we
           // should close it.
           this._client.close().then(cleanupAndResolve);
         } else if (this.activeTab) {
           // The client was handed to us, so we are not responsible for closing
           // it. We just need to detach from the tab, if already attached.
           // |detach| may fail if the connection is already dead, so proceed with
           // cleanup directly after this.
-          this.activeTab.detach();
+          try {
+            await this.activeTab.detach();
+          } catch (e) {
+            console.warn(`Error while detaching target: ${e.message}`);
+          }
           cleanupAndResolve();
         } else {
           cleanupAndResolve();
         }
       }
     });
 
     return this._destroyer;
@@ -785,32 +823,32 @@ TabTarget.prototype = {
    * @param {String} text
    *                 The text to log.
    * @param {String} category
    *                 The category of the message.  @see nsIScriptError.
    */
   logErrorInPage: function(text, category) {
     if (this.activeTab && this.activeTab.traits.logInPage) {
       const errorFlag = 0;
-      this.activeTab.logInPage(text, category, errorFlag);
+      this.activeTab.logInPage({ text, category, flags: errorFlag });
     }
   },
 
   /**
    * Log a warning of some kind to the tab's console.
    *
    * @param {String} text
    *                 The text to log.
    * @param {String} category
    *                 The category of the message.  @see nsIScriptError.
    */
   logWarningInPage: function(text, category) {
     if (this.activeTab && this.activeTab.traits.logInPage) {
       const warningFlag = 1;
-      this.activeTab.logInPage(text, category, warningFlag);
+      this.activeTab.logInPage({ text, category, flags: warningFlag });
     }
   },
 };
 
 function WorkerTarget(workerClient) {
   EventEmitter.decorate(this);
   this._workerClient = workerClient;
 }
--- a/devtools/client/framework/test/browser_toolbox_target.js
+++ b/devtools/client/framework/test/browser_toolbox_target.js
@@ -32,17 +32,17 @@ add_task(async function() {
   await onLoad;
 
   // Also wait for toolbox-ready, as toolbox document load isn't enough, there
   // is plenty of asynchronous steps during toolbox load
   info("Waiting for toolbox-ready");
   const toolbox = await onToolboxReady;
 
   const onToolboxDestroyed = gDevTools.once("toolbox-destroyed");
-  const onTabDetached = once(toolbox.target.client, "tabDetached");
+  const onTabDetached = toolbox.target.activeTab.once("tabDetached");
 
   info("Removing the iframes");
   toolboxIframe.remove();
 
   // And wait for toolbox-destroyed as toolbox unload is also full of
   // asynchronous operation that outlast unload event
   info("Waiting for toolbox-destroyed");
   await onToolboxDestroyed;
--- a/devtools/client/framework/test/browser_toolbox_tool_remote_reopen.js
+++ b/devtools/client/framework/test/browser_toolbox_tool_remote_reopen.js
@@ -110,20 +110,16 @@ function test() {
       for (const actor of pool.__poolMap.keys()) {
         // Bug 1056342: Profiler fails today because of framerate actor, but
         // this appears more complex to rework, so leave it for that bug to
         // resolve.
         if (actor.includes("framerateActor")) {
           todo(false, "Front for " + actor + " still held in pool!");
           continue;
         }
-        // gcliActor is for the commandline which is separate to the toolbox
-        if (actor.includes("gcliActor")) {
-          continue;
-        }
         ok(false, "Front for " + actor + " still held in pool!");
       }
     }
 
     gBrowser.removeCurrentTab();
     DebuggerServer.destroy();
     toggleAllTools(false);
     finish();
--- a/devtools/client/framework/test/helper_disable_cache.js
+++ b/devtools/client/framework/test/helper_disable_cache.js
@@ -80,21 +80,22 @@ async function checkCacheEnabled(tabX, e
 async function setDisableCacheCheckboxChecked(tabX, state) {
   gBrowser.selectedTab = tabX.tab;
 
   const panel = tabX.toolbox.getCurrentPanel();
   const cbx = panel.panelDoc.getElementById("devtools-disable-cache");
 
   if (cbx.checked !== state) {
     info("Setting disable cache checkbox to " + state + " for " + tabX.title);
+    const onReconfigured = tabX.toolbox.once("cache-reconfigured");
     cbx.click();
 
-    // We need to wait for all checkboxes to be updated and the docshells to
-    // apply the new cache settings.
-    await waitForTick();
+    // We have to wait for the reconfigure request to be finished before reloading
+    // the page.
+    await onReconfigured;
   }
 }
 
 function reloadTab(tabX) {
   const browser = gBrowser.selectedBrowser;
 
   const reloadTabPromise = BrowserTestUtils.browserLoaded(browser).then(function() {
     info("Reloaded tab " + tabX.title);
--- a/devtools/client/framework/toolbox-options.js
+++ b/devtools/client/framework/toolbox-options.js
@@ -455,17 +455,17 @@ OptionsPanel.prototype = {
       prefSelect.addEventListener("change", function(e) {
         const select = e.target;
         SetPref(select.getAttribute("data-pref"),
           select.options[select.selectedIndex].value);
       });
     }
 
     if (this.target.activeTab && !this.target.chrome) {
-      const [ response ] = await this.target.client.attachTarget(this.target.activeTab._actor);
+      const response = await this.target.activeTab.attach();
       this._origJavascriptEnabled = !response.javascriptEnabled;
       this.disableJSNode.checked = this._origJavascriptEnabled;
       this.disableJSNode.addEventListener("click", this._disableJSClicked);
     } else {
       // Hide the checkbox and label
       this.disableJSNode.parentNode.style.display = "none";
 
       const triggersPageRefreshLabel =
@@ -507,17 +507,17 @@ OptionsPanel.prototype = {
    */
   _disableJSClicked: function(event) {
     const checked = event.target.checked;
 
     const options = {
       "javascriptEnabled": !checked
     };
 
-    this.target.activeTab.reconfigure(options);
+    this.target.activeTab.reconfigure({ options });
   },
 
   destroy: function() {
     if (this.destroyed) {
       return;
     }
     this.destroyed = true;
 
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1334,37 +1334,48 @@ Toolbox.prototype = {
 
     return this.pickerButton;
   },
 
   /**
    * Apply the current cache setting from devtools.cache.disabled to this
    * toolbox's tab.
    */
-  _applyCacheSettings: function() {
+  _applyCacheSettings: async function() {
     const pref = "devtools.cache.disabled";
     const cacheDisabled = Services.prefs.getBoolPref(pref);
 
     if (this.target.activeTab) {
-      this.target.activeTab.reconfigure({"cacheDisabled": cacheDisabled});
+      await this.target.activeTab.reconfigure({
+        options: {
+          "cacheDisabled": cacheDisabled
+        }
+      });
+
+      // This event is only emitted for tests in order to know when to reload
+      if (flags.testing) {
+        this.emit("cache-reconfigured");
+      }
     }
   },
 
   /**
    * Apply the current service workers testing setting from
    * devtools.serviceWorkers.testing.enabled to this toolbox's tab.
    */
   _applyServiceWorkersTestingSettings: function() {
     const pref = "devtools.serviceWorkers.testing.enabled";
     const serviceWorkersTestingEnabled =
       Services.prefs.getBoolPref(pref) || false;
 
     if (this.target.activeTab) {
       this.target.activeTab.reconfigure({
-        "serviceWorkersTestingEnabled": serviceWorkersTestingEnabled
+        options: {
+          "serviceWorkersTestingEnabled": serviceWorkersTestingEnabled
+        }
       });
     }
   },
 
   /**
    * Update the visibility of the buttons.
    */
   updateToolboxButtonsVisibility() {
@@ -1400,17 +1411,21 @@ Toolbox.prototype = {
    */
   togglePaintFlashing: function() {
     if (this.isPaintFlashing) {
       this.telemetry.toolOpened("paintflashing", this.sessionId, this);
     } else {
       this.telemetry.toolClosed("paintflashing", this.sessionId, this);
     }
     this.isPaintFlashing = !this.isPaintFlashing;
-    return this.target.activeTab.reconfigure({"paintFlashing": this.isPaintFlashing});
+    return this.target.activeTab.reconfigure({
+      options: {
+        "paintFlashing": this.isPaintFlashing
+      }
+    });
   },
 
   /**
    * Visually update picker button.
    * This function is called on every "select" event. Newly selected panel can
    * update the visual state of the picker button such as disabled state,
    * additional CSS classes (className), and tooltip (description).
    */
@@ -2323,17 +2338,17 @@ Toolbox.prototype = {
   },
 
   /**
    * Select a frame by sending 'switchToFrame' packet to the backend.
    */
   onSelectFrame: function(frameId) {
     // Send packet to the backend to select specified frame and
     // wait for 'frameUpdate' event packet to update the UI.
-    this.target.activeTab.switchToFrame(frameId);
+    this.target.activeTab.switchToFrame({ windowId: frameId });
   },
 
   /**
    * Highlight a frame in the page
    */
   onHighlightFrame: async function(frameId) {
     // Need to initInspector to check presence of getNodeActorFromWindowID
     // and use the highlighter later
@@ -2737,19 +2752,17 @@ Toolbox.prototype = {
         await this.selectTool("inspector");
       }
     } else if (objectActor.type !== "null" &&
                objectActor.type !== "undefined") {
       // Open then split console and inspect the object in the variables view,
       // when the objectActor doesn't represent an undefined or null value.
       await this.openSplitConsole();
       const panel = this.getPanel("webconsole");
-      const jsterm = panel.hud.jsterm;
-
-      jsterm.inspectObjectActor(objectActor);
+      panel.hud.ui.inspectObjectActor(objectActor);
     }
   },
 
   /**
    * Destroy the inspector/walker/selection fronts
    * Returns a promise that resolves when the fronts are destroyed
    * TODO: move to the inspector front once we can have listener hooks into fronts
    */
--- a/devtools/client/inspector/test/browser_inspector_startup.js
+++ b/devtools/client/inspector/test/browser_inspector_startup.js
@@ -37,17 +37,17 @@ add_task(async function() {
   const domContentLoaded = waitForLinkedBrowserEvent(tab, "DOMContentLoaded");
   const pageLoaded = waitForLinkedBrowserEvent(tab, "load");
 
   const markupLoaded = inspector.once("markuploaded");
   const onRequest = onPageResourceRequest();
 
   info("Navigate to the slow loading page");
   const activeTab = inspector.toolbox.target.activeTab;
-  await activeTab.navigateTo(TEST_URL);
+  await activeTab.navigateTo({ url: TEST_URL });
 
   info("Wait for request made to the image");
   const response = await onRequest;
 
   // The request made to the image shouldn't block the DOMContentLoaded event
   info("Wait for DOMContentLoaded");
   await domContentLoaded;
 
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -43,17 +43,17 @@ registerCleanupFunction(function() {
 
 var navigateTo = async function(inspector, url) {
   const markuploaded = inspector.once("markuploaded");
   const onNewRoot = inspector.once("new-root");
   const onUpdated = inspector.once("inspector-updated");
 
   info("Navigating to: " + url);
   const activeTab = inspector.toolbox.target.activeTab;
-  await activeTab.navigateTo(url);
+  await activeTab.navigateTo({ url });
 
   info("Waiting for markup view to load after navigation.");
   await markuploaded;
 
   info("Waiting for new root.");
   await onNewRoot;
 
   info("Waiting for inspector to update after new-root event.");
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -118,16 +118,17 @@ devtools.jar:
     skin/images/aboutdebugging-firefox-beta.svg (themes/images/aboutdebugging-firefox-beta.svg)
     skin/images/aboutdebugging-firefox-logo.svg (themes/images/aboutdebugging-firefox-logo.svg)
     skin/images/aboutdebugging-firefox-nightly.svg (themes/images/aboutdebugging-firefox-nightly.svg)
     skin/images/aboutdebugging-firefox-release.svg (themes/images/aboutdebugging-firefox-release.svg)
     skin/images/aboutdebugging-globe-icon.svg (themes/images/aboutdebugging-globe-icon.svg)
     skin/images/fox-smiling.svg (themes/images/fox-smiling.svg)
     skin/images/grid.svg (themes/images/grid.svg)
     skin/images/angle-swatch.svg (themes/images/angle-swatch.svg)
+    skin/images/flexbox-swatch.svg (themes/images/flexbox-swatch.svg)
     skin/images/pseudo-class.svg (themes/images/pseudo-class.svg)
     skin/images/controls.png (themes/images/controls.png)
     skin/images/controls@2x.png (themes/images/controls@2x.png)
     skin/images/copy.svg (themes/images/copy.svg)
     skin/images/animation-fast-track.svg (themes/images/animation-fast-track.svg)
     skin/images/performance-details-waterfall.svg (themes/images/performance-details-waterfall.svg)
     skin/images/performance-details-call-tree.svg (themes/images/performance-details-call-tree.svg)
     skin/images/performance-details-flamegraph.svg (themes/images/performance-details-flamegraph.svg)
@@ -199,17 +200,16 @@ devtools.jar:
     skin/images/debugger-toggleBreakpoints.svg (themes/images/debugger-toggleBreakpoints.svg)
     skin/images/dock-bottom.svg (themes/images/dock-bottom.svg)
     skin/images/dock-side-left.svg (themes/images/dock-side-left.svg)
     skin/images/dock-side-right.svg (themes/images/dock-side-right.svg)
     skin/images/dock-undock.svg (themes/images/dock-undock.svg)
     skin/images/jump-definition.svg (themes/images/jump-definition.svg)
     skin/images/tracer-icon.png (themes/images/tracer-icon.png)
     skin/images/tracer-icon@2x.png (themes/images/tracer-icon@2x.png)
-    skin/floating-scrollbars-dark-theme.css (themes/floating-scrollbars-dark-theme.css)
     skin/floating-scrollbars-responsive-design.css (themes/floating-scrollbars-responsive-design.css)
     skin/inspector.css (themes/inspector.css)
     skin/images/profiler-stopwatch.svg (themes/images/profiler-stopwatch.svg)
     skin/images/debugging-addons.svg (themes/images/debugging-addons.svg)
     skin/images/debugging-tabs.svg (themes/images/debugging-tabs.svg)
     skin/images/debugging-workers.svg (themes/images/debugging-workers.svg)
     skin/images/globe.svg (themes/images/globe.svg)
     skin/images/sad-face.svg (themes/images/sad-face.svg)
--- a/devtools/client/netmonitor/src/connector/firefox-connector.js
+++ b/devtools/client/netmonitor/src/connector/firefox-connector.js
@@ -311,17 +311,17 @@ class FirefoxConnector {
             resolve();
           });
         });
       });
     };
 
     // Reconfigures the tab, optionally triggering a reload.
     const reconfigureTab = options => {
-      return this.tabTarget.activeTab.reconfigure(options);
+      return this.tabTarget.activeTab.reconfigure({ options });
     };
 
     // Reconfigures the tab and waits for the target to finish navigating.
     const reconfigureTabAndWaitForNavigation = (options) => {
       options.performReload = true;
       const navigationFinished = waitForNavigation();
       return reconfigureTab(options).then(() => navigationFinished);
     };
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -139,17 +139,17 @@ function waitForNavigation(target) {
 
 function toggleCache(target, disabled) {
   const options = { cacheDisabled: disabled, performReload: true };
   const navigationFinished = waitForNavigation(target);
 
   // Disable the cache for any toolbox that it is opened from this point on.
   Services.prefs.setBoolPref("devtools.cache.disabled", disabled);
 
-  return target.activeTab.reconfigure(options).then(() => navigationFinished);
+  return target.activeTab.reconfigure({ options }).then(() => navigationFinished);
 }
 
 /**
  * Wait for 2 markers during document load.
  */
 function waitForTimelineMarkers(monitor) {
   return new Promise(resolve => {
     const markers = [];
--- a/devtools/client/shadereditor/test/head.js
+++ b/devtools/client/shadereditor/test/head.js
@@ -126,23 +126,24 @@ function navigateInHistory(aTarget, aDir
     mm.sendAsyncMessage("devtools:test:history", { direction: aDirection });
   } else {
     executeSoon(() => content.history[aDirection]());
   }
   return once(aTarget, aWaitForTargetEvent);
 }
 
 function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") {
-  executeSoon(() => aTarget.activeTab.navigateTo(aUrl));
+  executeSoon(() => aTarget.activeTab.navigateTo({ url: aUrl }));
   return once(aTarget, aWaitForTargetEvent);
 }
 
-function reload(aTarget, aWaitForTargetEvent = "navigate") {
-  executeSoon(() => aTarget.activeTab.reload());
-  return once(aTarget, aWaitForTargetEvent);
+async function reload(aTarget, aWaitForTargetEvent = "navigate") {
+  const onTargetEvent = once(aTarget, aWaitForTargetEvent);
+  await aTarget.activeTab.reload();
+  return onTargetEvent;
 }
 
 function initBackend(aUrl) {
   info("Initializing a shader editor front.");
 
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
 
--- a/devtools/client/shared/test/browser_dbg_listtabs-03.js
+++ b/devtools/client/shared/test/browser_dbg_listtabs-03.js
@@ -6,56 +6,46 @@
 "use strict";
 
 /**
  * Make sure the listTabs request works as specified.
  */
 
 var { DebuggerServer } = require("devtools/server/main");
 var { DebuggerClient } = require("devtools/shared/client/debugger-client");
-var { Task } = require("devtools/shared/task");
 
 const TAB1_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
 
-var gClient;
-
-function test() {
+add_task(async function test() {
   DebuggerServer.init();
   DebuggerServer.registerAllActors();
 
   const transport = DebuggerServer.connectPipe();
-  gClient = new DebuggerClient(transport);
-  gClient.connect().then(Task.async(function* ([aType, aTraits]) {
-    is(aType, "browser",
-      "Root actor should identify itself as a browser.");
-    const tab = yield addTab(TAB1_URL);
+  const client = new DebuggerClient(transport);
+  const [type] = await client.connect();
+  is(type, "browser", "Root actor should identify itself as a browser.");
+  const tab = await addTab(TAB1_URL);
 
-    let { tabs } = yield gClient.listTabs();
-    is(tabs.length, 2, "Should be two tabs");
-    const tabGrip = tabs.filter(a => a.url == TAB1_URL).pop();
-    ok(tabGrip, "Should have an actor for the tab");
+  let { tabs } = await client.listTabs();
+  is(tabs.length, 2, "Should be two tabs");
+  const tabGrip = tabs.filter(a => a.url == TAB1_URL).pop();
+  ok(tabGrip, "Should have an actor for the tab");
 
-    let response = yield gClient.request({ to: tabGrip.actor, type: "attach" });
-    is(response.type, "tabAttached", "Should have attached");
-
-    response = yield gClient.listTabs();
-    tabs = response.tabs;
+  let [response, targetFront] = await client.attachTarget(tabGrip.actor);
+  is(response.type, "tabAttached", "Should have attached");
 
-    response = yield gClient.request({ to: tabGrip.actor, type: "detach" });
-    is(response.type, "detached", "Should have detached");
+  response = await client.listTabs();
+  tabs = response.tabs;
 
-    const newGrip = tabs.filter(a => a.url == TAB1_URL).pop();
-    is(newGrip.actor, tabGrip.actor, "Should have the same actor for the same tab");
+  response = await targetFront.detach();
+  is(response.type, "detached", "Should have detached");
+
+  const newGrip = tabs.filter(a => a.url == TAB1_URL).pop();
+  is(newGrip.actor, tabGrip.actor, "Should have the same actor for the same tab");
 
-    response = yield gClient.request({ to: tabGrip.actor, type: "attach" });
-    is(response.type, "tabAttached", "Should have attached");
-    response = yield gClient.request({ to: tabGrip.actor, type: "detach" });
-    is(response.type, "detached", "Should have detached");
+  [response, targetFront] = await client.attachTarget(tabGrip.actor);
+  is(response.type, "tabAttached", "Should have attached");
+  response = await targetFront.detach();
+  is(response.type, "detached", "Should have detached");
 
-    yield removeTab(tab);
-    yield gClient.close();
-    finish();
-  }));
-}
-
-registerCleanupFunction(function() {
-  gClient = null;
+  await removeTab(tab);
+  await client.close();
 });
--- a/devtools/client/shared/test/browser_dbg_navigation.js
+++ b/devtools/client/shared/test/browser_dbg_navigation.js
@@ -34,62 +34,62 @@ function test() {
       .then(testDetach)
       .then(finish)
       .catch(error => {
         ok(false, "Got an error: " + error.message + "\n" + error.stack);
       });
   });
 }
 
-function testNavigate([aGrip, aResponse]) {
+function testNavigate(targetFront) {
   const outstanding = [promise.defer(), promise.defer()];
 
-  gClient.addListener("tabNavigated", function onTabNavigated(event, packet) {
+  targetFront.on("tabNavigated", function onTabNavigated(packet) {
     is(packet.url.split("/").pop(), TAB2_FILE,
       "Got a tab navigation notification.");
 
     info(JSON.stringify(packet));
     info(JSON.stringify(event));
 
     if (packet.state == "start") {
       ok(true, "Tab started to navigate.");
       outstanding[0].resolve();
     } else {
       ok(true, "Tab finished navigating.");
-      gClient.removeListener("tabNavigated", onTabNavigated);
+      targetFront.off("tabNavigated", onTabNavigated);
       outstanding[1].resolve();
     }
   });
 
   BrowserTestUtils.loadURI(gBrowser.selectedBrowser, TAB2_URL);
   return promise.all(outstanding.map(e => e.promise))
-                .then(() => aGrip.actor);
+                .then(() => targetFront);
 }
 
-function testDetach(actor) {
-  const deferred = promise.defer();
-
-  gClient.addOneTimeListener("tabDetached", (type, packet) => {
-    ok(true, "Got a tab detach notification.");
-    is(packet.from, actor, "tab detach message comes from the expected actor");
-    deferred.resolve(gClient.close());
-  });
+async function testDetach(targetFront) {
+  const onDetached = targetFront.once("tabDetached");
 
   removeTab(gBrowser.selectedTab);
-  return deferred.promise;
+
+  const packet = await onDetached;
+  ok(true, "Got a tab detach notification.");
+  is(packet.from, targetFront.actorID,
+    "tab detach message comes from the expected actor");
+
+  return gClient.close();
 }
 
 registerCleanupFunction(function() {
   gClient = null;
 });
 
 async function attachTargetActorForUrl(client, url) {
   const grip = await getTargetActorForUrl(client, url);
-  const [ response ] = await client.attachTarget(grip.actor);
-  return [grip, response];
+  const [, targetFront] = await client.attachTarget(grip.actor);
+  return targetFront;
 }
 
 function getTargetActorForUrl(client, url) {
   const deferred = promise.defer();
 
   client.listTabs().then(response => {
     const targetActor = response.tabs.filter(grip => grip.url == url).pop();
     deferred.resolve(targetActor);
--- a/devtools/client/shared/theme-switching.js
+++ b/devtools/client/shared/theme-switching.js
@@ -28,32 +28,16 @@
   // to have per-platform CSS working correctly.
   if (documentElement.getAttribute("no-theme") === "true") {
     return;
   }
 
   const devtoolsStyleSheets = new WeakMap();
   let gOldTheme = "";
 
-  function forceStyle() {
-    const computedStyle = window.getComputedStyle(documentElement);
-    if (!computedStyle) {
-      // Null when documentElement is not ready. This method is anyways not
-      // required then as scrollbars would be in their state without flushing.
-      return;
-    }
-    // Save display value
-    const display = computedStyle.display;
-    documentElement.style.display = "none";
-    // Flush
-    window.getComputedStyle(documentElement).display;
-    // Restore
-    documentElement.style.display = display;
-  }
-
   /*
    * Notify the window that a theme switch finished so tests can check the DOM
    */
   function notifyWindow() {
     window.dispatchEvent(new CustomEvent("theme-switch-complete", {}));
   }
 
   /*
@@ -84,36 +68,16 @@
 
     const loadEvents = [];
     for (const url of newThemeDef.stylesheets) {
       const {styleSheet, loadPromise} = appendStyleSheet(document, url);
       devtoolsStyleSheets.get(newThemeDef).push(styleSheet);
       loadEvents.push(loadPromise);
     }
 
-    if (os !== "win" && os !== "mac") {
-      // Windows & Mac always use native scrollbars, Linux still uses custom floating
-      // scrollbar implementation.
-      try {
-        const StylesheetUtils = require("devtools/shared/layout/utils");
-        const SCROLLBARS_URL = "chrome://devtools/skin/floating-scrollbars-dark-theme.css";
-        if (!Services.appShell.hiddenDOMWindow
-          .matchMedia("(-moz-overlay-scrollbars)").matches) {
-          if (newTheme == "dark") {
-            StylesheetUtils.loadSheet(window, SCROLLBARS_URL, "agent");
-          } else if (oldTheme == "dark") {
-            StylesheetUtils.removeSheet(window, SCROLLBARS_URL, "agent");
-          }
-          forceStyle();
-        }
-      } catch (e) {
-        console.warn("customize scrollbar styles is only supported in firefox");
-      }
-    }
-
     Promise.all(loadEvents).then(() => {
       // Unload all stylesheets and classes from the old theme.
       if (oldThemeDef) {
         for (const name of oldThemeDef.classList) {
           documentElement.classList.remove(name);
         }
 
         for (const sheet of devtoolsStyleSheets.get(oldThemeDef) || []) {
--- a/devtools/client/sourceeditor/codemirror/mozilla.css
+++ b/devtools/client/sourceeditor/codemirror/mozilla.css
@@ -84,32 +84,16 @@
   cursor: text;
   height: 100%;
 }
 
 .CodeMirror-gutters {
   cursor: default;
 }
 
-/* This is to avoid the fake horizontal scrollbar div of codemirror to go 0
-height when floating scrollbars are active. Make sure that this value is equal
-to the maximum of `min-height` specific to the `scrollbar[orient="horizontal"]`
-selector in floating-scrollbar-light.css across all platforms. */
-.CodeMirror-hscrollbar {
-  min-height: 10px;
-}
-
-/* This is to avoid the fake vertical scrollbar div of codemirror to go 0
-width when floating scrollbars are active. Make sure that this value is equal
-to the maximum of `min-width` specific to the `scrollbar[orient="vertical"]`
-selector in floating-scrollbar-light.css across all platforms. */
-.CodeMirror-vscrollbar {
-  min-width: 10px;
-}
-
 .cm-trailingspace {
   background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAACCAYAAAB/qH1jAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QUXCToH00Y1UgAAACFJREFUCNdjPMDBUc/AwNDAAAFMTAwMDA0OP34wQgX/AQBYgwYEx4f9lQAAAABJRU5ErkJggg==");
   opacity: 0.75;
   background-position: left bottom;
   background-repeat: repeat-x;
 }
 
 .cm-highlight {
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -33,16 +33,32 @@
 :root[platform="win"] {
   --monospace-font-family: Consolas, monospace;
 }
 
 :root[platform="linux"] {
   --monospace-font-family: monospace;
 }
 
+/**
+ * Customize the dark theme's scrollbar colors to avoid excessive contrast.
+ */
+:root.theme-dark {
+  scrollbar-color: var(--theme-body-color-inactive) var(--theme-splitter-color);
+}
+
+/**
+ * Customize scrollbar colors on Linux + light theme, to avoid visual conflicts
+ * between the light theme and the selected GTK theme (see bug 1471163).
+ * This removes GTK scrollbars and uses a fallback design (see bug 1464723).
+ */
+:root[platform="linux"].theme-light {
+  scrollbar-color: var(--theme-body-color-inactive) var(--grey-20);
+}
+
 .devtools-monospace {
   font-family: var(--monospace-font-family);
   font-size: var(--theme-code-font-size);
 }
 
 /**
  * Override wrong system font from forms.css
  * Bug 1458224: buttons use a wrong default font-size on Linux
@@ -425,16 +441,26 @@ checkbox:-moz-focusring {
   -moz-appearance: none;
   margin: 1px 3px;
   border: 1px solid;
   border-radius: 2px;
   padding: 4px 6px;
   border-color: var(--theme-splitter-color);
   font: message-box;
   font-size: var(--theme-body-font-size);
+  background-color: white;
+  color: var(--grey-90);
+}
+
+.theme-dark .devtools-textinput,
+.theme-dark .devtools-searchinput,
+.theme-dark .devtools-filterinput {
+  /* in between grey-85 and grey-90 */
+  background-color: #141416;
+  color: var(--grey-20);
 }
 
 :root[platform="mac"] .devtools-textinput,
 :root[platform="mac"] .devtools-searchinput,
 :root[platform="mac"] .devtools-filterinput {
   border-radius: 20px;
 }
 
@@ -484,22 +510,23 @@ checkbox:-moz-focusring {
 }
 
 .devtools-searchinput .textbox-input::placeholder,
 .devtools-filterinput .textbox-input::placeholder {
   font-style: normal;
 }
 
 .devtools-plaininput {
+  color: var(--grey-90);
   border-color: transparent;
   background-color: transparent;
 }
 
 .theme-dark .devtools-plaininput {
-  color: var(--theme-highlight-gray);
+  color: var(--grey-20);
 }
 
 /* Searchbox is a div container element for a search input element */
 .devtools-searchbox {
   display: inline-flex;
   flex: 1;
   height: 23px;
   position: relative;
@@ -581,32 +608,27 @@ checkbox:-moz-focusring {
 
 .devtools-style-searchbox-no-match {
   background-color: var(--searcbox-no-match-background-color) !important;
   border-color: var(--searcbox-no-match-border-color) !important;
 }
 
 .devtools-searchinput-clear {
   position: absolute;
-  top: 3.5px;
+  top: calc(50% - 8px);
   inset-inline-end: 7px;
   padding: 0;
   border: 0;
   width: 16px;
   height: 16px;
   background-position: 0 0;
   background-repeat: no-repeat;
   background-color: transparent;
 }
 
-.devtools-searchinput-clear:dir(rtl) {
-  right: unset;
-  left: 7px;
-}
-
 .theme-dark .devtools-searchinput-clear {
   background-image: url("chrome://devtools/skin/images/search-clear-dark.svg");
 }
 
 .theme-light .devtools-searchinput-clear {
   background-image: url("chrome://devtools/skin/images/search-clear-light.svg");
 }
 
--- a/devtools/client/themes/dark-theme.css
+++ b/devtools/client/themes/dark-theme.css
@@ -22,23 +22,16 @@ body {
   color: var(--theme-body-color);
 }
 
 ::-moz-selection {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
-:root[platform="win"],
-:root[platform="mac"] {
-  /* Set colors for native scrollbars on Windows dark theme */
-  /* Other platforms support for scrollbar theming is Bug 1460109 */
-  scrollbar-color: var(--theme-body-color-inactive) var(--theme-splitter-color);
-}
-
 .theme-selected,
 .CodeMirror-hint-active {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
 .theme-bg-contrast,
 .variable-or-property:not([overridden])[changed] {
@@ -253,24 +246,16 @@ div.CodeMirror span.eval-text {
   background-color: var(--theme-sidebar-background);
 }
 
 .cm-s-markup-view pre {
   line-height: 1.4em;
   min-height: 1.4em;
 }
 
-.devtools-textinput,
-.devtools-searchinput,
-.devtools-filterinput {
-  /* in between grey-85 and grey-90 */
-  background-color: #141416;
-  color: var(--theme-highlight-gray);
-}
-
 .CodeMirror-Tern-fname {
   color: #f7f7f7;
 }
 
 .CodeMirror-hints,
 .CodeMirror-Tern-tooltip {
   box-shadow: 0 0 4px rgba(255, 255, 255, .3);
   background-color: #0f171f;
deleted file mode 100644
--- a/devtools/client/themes/floating-scrollbars-dark-theme.css
+++ /dev/null
@@ -1,55 +0,0 @@
-@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
-@namespace html url("http://www.w3.org/1999/xhtml");
-
-/* Restrict all styles to `*|*:not(html|select) > scrollbar` so that scrollbars
-   inside a <select> are excluded (including them hides the select arrow on
-   Windows).  We want to include both the root scrollbars for the document as
-   well as any overflow: scroll elements within the page, while excluding
-   <select>. */
-*|*:not(html|select) > scrollbar {
-  -moz-appearance: none !important;
-  position: relative;
-  background-color: transparent;
-  background-image: none;
-  z-index: 2147483647;
-  padding: 2px;
-  pointer-events: auto;
-}
-
-*|*:root[platform="mac"] > scrollbar,
-*|*:root[platform="mac"] *|*:not(html|select) > scrollbar {
-  border: none;
-}
-
-/* Scrollbar code will reset the margin to the correct side depending on
-   where layout actually puts the scrollbar */
-*|*:not(html|select) > scrollbar[orient="vertical"] {
-  margin-left: -10px;
-  min-width: 10px;
-  max-width: 10px;
-}
-
-*|*:not(html|select) > scrollbar[orient="horizontal"] {
-  margin-top: -10px;
-  min-height: 10px;
-  max-height: 10px;
-}
-
-*|*:not(html|select) > scrollbar thumb {
-  background-color: rgba(170, 170, 170, .2) !important; /* --toolbar-tab-hover */
-  -moz-appearance: none !important;
-  border-width: 0px !important;
-  border-radius: 3px !important;
-}
-
-*|*:root[platform="mac"] > scrollbar slider,
-*|*:root[platform="mac"] *|*:not(html|select) > scrollbar slider {
-  -moz-appearance: none !important;
-}
-
-*|*:root[platform="linux"] > scrollbar scrollbarbutton,
-*|*:root[platform="linux"] > scrollbar gripper,
-*|*:root[platform="linux"] *|*:not(html|select) > scrollbar scrollbarbutton,
-*|*:root[platform="linux"] *|*:not(html|select) > scrollbar gripper {
-  display: none;
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/flexbox-swatch.svg
@@ -0,0 +1,11 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 11">
+  <g fill="none" stroke="context-stroke #0c0c0d" stroke-width="1" stroke-linejoin="round">
+    <rect x="0.5" y="0.5" width="12" height="10"
+      stroke-dasharray="1 1" stroke-dashoffset="0.5" />
+    <rect x="2.5" y="3.5" width="8" height="4"/>
+    <line x1="6.5" x2="6.5" y1="3" y2="8"/>
+  </g>
+</svg>
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -421,21 +421,23 @@
   /* align the swatch with its value */
   margin-top: -1px;
   margin-inline-end: 5px;
   display: inline-block;
   position: relative;
 }
 
 .ruleview-flex {
-  background: url("chrome://devtools/skin/images/command-frames.svg");
-  -moz-context-properties: fill;
-  fill: var(--rule-flex-toggle-color);
+  background-image: url("chrome://devtools/skin/images/flexbox-swatch.svg");
+  background-size: 13px 11px;
+  -moz-context-properties: stroke;
+  stroke: var(--rule-flex-toggle-color);
+  width: 13px;
+  height: 11px;
   border-radius: 0;
-  background-size: 1em;
 }
 
 .ruleview-grid {
   background: url("chrome://devtools/skin/images/grid.svg");
   border-radius: 0;
 }
 
 .ruleview-shape-point.active,
--- a/devtools/client/webaudioeditor/test/head.js
+++ b/devtools/client/webaudioeditor/test/head.js
@@ -43,17 +43,17 @@ registerCleanupFunction(() => {
 });
 
 function reload(aTarget, aWaitForTargetEvent = "navigate") {
   aTarget.activeTab.reload();
   return once(aTarget, aWaitForTargetEvent);
 }
 
 function navigate(aTarget, aUrl, aWaitForTargetEvent = "navigate") {
-  executeSoon(() => aTarget.activeTab.navigateTo(aUrl));
+  executeSoon(() => aTarget.activeTab.navigateTo({ url: aUrl }));
   return once(aTarget, aWaitForTargetEvent);
 }
 
 /**
  * Adds a new tab, and instantiate a WebAudiFront object.
  * This requires calling removeTab before the test ends.
  */
 function initBackend(aUrl) {
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -470,17 +470,17 @@ class JSTerm extends Component {
       switch (helperResult.type) {
         case "clearOutput":
           this.hud.clearOutput();
           break;
         case "clearHistory":
           this.props.clearHistory();
           break;
         case "inspectObject":
-          this.inspectObjectActor(helperResult.object);
+          this.hud.inspectObjectActor(helperResult.object);
           break;
         case "error":
           try {
             errorMessage = l10n.getStr(helperResult.message);
           } catch (ex) {
             errorMessage = helperResult.message;
           }
           break;
@@ -508,26 +508,16 @@ class JSTerm extends Component {
 
     if (this.hud.consoleOutput) {
       return this.hud.consoleOutput.dispatchMessageAdd(response, true);
     }
 
     return null;
   }
 
-  inspectObjectActor(objectActor) {
-    this.hud.consoleOutput.dispatchMessageAdd({
-      helperResult: {
-        type: "inspectObject",
-        object: objectActor
-      }
-    }, true);
-    return this.hud.consoleOutput;
-  }
-
   screenshotNotify(results) {
     const wrappedResults = results.map(message => ({ message, type: "logMessage" }));
     this.hud.consoleOutput.dispatchMessagesAdd(wrappedResults);
   }
 
   /**
    * Execute a string. Execution happens asynchronously in the content process.
    *
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -215,16 +215,17 @@ skip-if = verify
 [browser_jsterm_content_defined_helpers.js]
 [browser_jsterm_copy_command.js]
 [browser_jsterm_ctrl_a_select_all.js]
 [browser_jsterm_ctrl_key_nav.js]
 skip-if = os != 'mac' # The tested ctrl+key shortcuts are OSX only
 [browser_jsterm_document_no_xray.js]
 [browser_jsterm_error_docs.js]
 [browser_jsterm_error_outside_valid_range.js]
+[browser_jsterm_focus_reload.js]
 [browser_jsterm_helper_clear.js]
 [browser_jsterm_helper_dollar_dollar.js]
 [browser_jsterm_helper_dollar_x.js]
 [browser_jsterm_helper_dollar.js]
 [browser_jsterm_helper_help.js]
 [browser_jsterm_helper_keys_values.js]
 [browser_jsterm_helper_pprint.js]
 [browser_jsterm_hide_when_devtools_chrome_enabled_false.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_focus_reload.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Check that the console does not steal the focus when reloading a page, if the focus
+// is on the content page.
+
+"use strict";
+
+const TEST_URI = `data:text/html,<meta charset=utf8>Focus test`;
+
+add_task(async function() {
+  // Run test with legacy JsTerm
+  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() {
+  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");
+
+  info("Put the focus on the content page");
+  ContentTask.spawn(gBrowser.selectedBrowser, null, () => content.focus());
+  await waitFor(() => isJstermFocused(jsterm) === 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");
+}
--- a/devtools/client/webconsole/webconsole-frame.js
+++ b/devtools/client/webconsole/webconsole-frame.js
@@ -135,32 +135,41 @@ WebConsoleFrame.prototype = {
   clearOutput(clearStorage) {
     if (this.consoleOutput) {
       this.consoleOutput.dispatchMessagesClear();
     }
     this.webConsoleClient.clearNetworkRequests();
     if (clearStorage) {
       this.webConsoleClient.clearMessagesCache();
     }
-    this.jsterm.focus();
     this.emit("messages-cleared");
   },
 
   /**
    * Remove all of the private messages from the Web Console output.
    *
    * This method emits the "private-messages-cleared" notification.
    */
   clearPrivateMessages() {
     if (this.consoleOutput) {
       this.consoleOutput.dispatchPrivateMessagesClear();
       this.emit("private-messages-cleared");
     }
   },
 
+  inspectObjectActor(objectActor) {
+    this.consoleOutput.dispatchMessageAdd({
+      helperResult: {
+        type: "inspectObject",
+        object: objectActor
+      }
+    }, true);
+    return this.consoleOutput;
+  },
+
   _onUpdateListeners() {
 
   },
 
   logWarningAboutReplacedAPI() {
     this.owner.target.logWarningInPage(l10n.getStr("ConsoleAPIDisabled"),
       "ConsoleAPIDisabled");
   },
--- a/devtools/client/webide/modules/tab-store.js
+++ b/devtools/client/webide/modules/tab-store.js
@@ -56,25 +56,21 @@ TabStore.prototype = {
     this._selectedTabTargetPromise = null;
   },
 
   _onStatusChanged: function() {
     if (this._connection.status == Connection.Status.CONNECTED) {
       // Watch for changes to remote browser tabs
       this._connection.client.addListener("tabListChanged",
                                           this._onTabListChanged);
-      this._connection.client.addListener("tabNavigated",
-                                          this._onTabNavigated);
       this.listTabs();
     } else {
       if (this._connection.client) {
         this._connection.client.removeListener("tabListChanged",
                                                this._onTabListChanged);
-        this._connection.client.removeListener("tabNavigated",
-                                               this._onTabNavigated);
       }
       this._resetStore();
     }
   },
 
   _onTabListChanged: function() {
     this.listTabs().then(() => this.emit("tab-list"))
                    .catch(console.error);
--- a/devtools/docs/backend/client-api.md
+++ b/devtools/docs/backend/client-api.md
@@ -16,18 +16,16 @@ function start() {
   DebuggerServer.registerAllActors();
 
   // Listen to an nsIPipe
   let transport = DebuggerServer.connectPipe();
 
   // Start the client.
   client = new DebuggerClient(transport);
 
-  // Attach listeners for client events.
-  client.addListener("tabNavigated", onTab);
   client.connect((type, traits) => {
     // Now the client is conected to the server.
     debugTab();
   });
 }
 ```
 
 If a TCP socket is required, the function should be split in two parts, a server-side and a client-side, like this:
@@ -46,19 +44,16 @@ function startServer() {
 }
 
 async function startClient() {
   let transport = await DebuggerClient.socketConnect({ host: "localhost", port: 2929 });
 
   // Start the client.
   client = new DebuggerClient(transport);
 
-  // Attach listeners for client events.
-  client.addListener("tabNavigated", onTab);
-
   client.connect((type, traits) => {
     // Now the client is conected to the server.
     debugTab();
   });
 }
 ```
 
 ## Shutting down
@@ -84,33 +79,36 @@ function attachToTab() {
 
     // Attach to the tab.
     client.attachTarget(tab.actor).then(([response, tabClient]) => {
       if (!tabClient) {
         return;
       }
 
       // Now the tabClient is ready and can be used.
+
+      // Attach listeners for client events.
+      tabClient.addListener("tabNavigated", onTab);
     });
   });
 }
 ```
 
 The debugger client will send event notifications for a number of events the application may be interested in. These events include state changes in the debugger, like pausing and resuming, stack frames or source scripts being ready for retrieval, etc.
 
 ## Handling location changes
 
 When the user navigates away from a page, a `tabNavigated` event will be fired. The proper way to handle this event is to detach from the previous thread and tab and attach to the new ones:
 
 ```javascript
 async function onTab() {
   // Detach from the previous thread.
   await client.activeThread.detach();
   // Detach from the previous tab.
-  await client.activeTab.detach();
+  await tabClient.activeTab.detach();
   // Start debugging the new tab.
   start();
 }
 ```
 
 ## Debugging JavaScript running in a browser tab
 
 Once the application is attached to a tab, it can attach to its thread in order to interact with the JavaScript debugger:
@@ -164,18 +162,16 @@ function startDebugger() {
   let transport = DebuggerServer.connectPipe();
   // For an nsIServerSocket we do this:
   // DebuggerServer.openListener(port);
   // ...and this at the client:
   // let transport = debuggerSocketConnect(host, port);
 
   // Start the client.
   client = new DebuggerClient(transport);
-  // Attach listeners for client events.
-  client.addListener("tabNavigated", onTab);
   client.connect((type, traits) => {
     // Now the client is conected to the server.
     debugTab();
   });
 }
 
 function shutdownDebugger() {
   client.close();
--- a/devtools/server/actors/targets/browsing-context.js
+++ b/devtools/server/actors/targets/browsing-context.js
@@ -961,17 +961,17 @@ const browsingContextTargetPrototype = {
   /**
    * Navigate this browsing context to a new location
    */
   navigateTo(request) {
     // Wait a tick so that the response packet can be dispatched before the
     // subsequent navigation event packet.
     Services.tm.dispatchToMainThread(DevToolsUtils.makeInfallible(() => {
       this.window.location = request.url;
-    }, "BrowsingContextTargetActor.prototype.navigateTo's delayed body"));
+    }, "BrowsingContextTargetActor.prototype.navigateTo's delayed body:" + request.url));
     return {};
   },
 
   /**
    * Reconfigure options.
    */
   reconfigure(request) {
     const options = request.options || {};
--- a/devtools/server/tests/browser/browser_navigateEvents.js
+++ b/devtools/server/tests/browser/browser_navigateEvents.js
@@ -97,22 +97,22 @@ function onMessage({ data }) {
 
 async function connectAndAttachTab() {
   // Ensure having a minimal server
   initDebuggerServer();
 
   // Connect to this tab
   const transport = DebuggerServer.connectPipe();
   const client = new DebuggerClient(transport);
-  client.addListener("tabNavigated", function(event, packet) {
+  const form = await connectDebuggerClient(client);
+  const actorID = form.actor;
+  const [, targetFront ] = await client.attachTarget(actorID);
+  targetFront.on("tabNavigated", function(packet) {
     assertEvent("tabNavigated", packet);
   });
-  const form = await connectDebuggerClient(client);
-  const actorID = form.actor;
-  await client.attachTarget(actorID);
   return { client, actorID };
 }
 
 add_task(async function() {
   // Open a test tab
   const browser = await addTab(URL1);
 
   // Listen for alert() call being made in navigate-first during unload
--- a/devtools/server/tests/browser/browser_webextension_inspected_window.js
+++ b/devtools/server/tests/browser/browser_webextension_inspected_window.js
@@ -21,42 +21,42 @@ async function setup(pageUrl) {
     lineNumber: 1,
     addonId: extension.id,
   };
 
   const target = await addTabTarget(pageUrl);
 
   const { client, form } = target;
 
-  const [, tabClient] = await client.attachTarget(form.actor);
+  const [, targetFront] = await client.attachTarget(form.actor);
 
   const [, consoleClient] = await client.attachConsole(form.consoleActor, []);
 
   const inspectedWindowFront = target.getFront("webExtensionInspectedWindow");
 
   return {
     client, form,
-    tabClient, consoleClient,
+    targetFront, consoleClient,
     inspectedWindowFront,
     extension, fakeExtCallerInfo,
   };
 }
 
 async function teardown({client, extension}) {
   await client.close();
   DebuggerServer.destroy();
   gBrowser.removeCurrentTab();
   await extension.unload();
 }
 
-function waitForNextTabNavigated(client) {
+function waitForNextTabNavigated(targetFront) {
   return new Promise(resolve => {
-    client.addListener("tabNavigated", function tabNavigatedListener(evt, pkt) {
+    targetFront.on("tabNavigated", function tabNavigatedListener(pkt) {
       if (pkt.state == "stop" && !pkt.isFrameSwitching) {
-        client.removeListener("tabNavigated", tabNavigatedListener);
+        targetFront.off("tabNavigated", tabNavigatedListener);
         resolve();
       }
     });
   });
 }
 
 function consoleEvalJS(consoleClient, jsCode) {
   return new Promise(resolve => {
@@ -222,111 +222,111 @@ add_task(async function test_exception_i
      "Got the expected stack trace in the exception message");
 
   await teardown({client, extension});
 });
 
 add_task(async function test_exception_inspectedWindowReload() {
   const {
     client, consoleClient, inspectedWindowFront,
-    extension, fakeExtCallerInfo,
+    extension, fakeExtCallerInfo, targetFront,
   } = await setup(`${TEST_RELOAD_URL}?test=cache`);
 
   // Test reload with bypassCache=false.
 
-  const waitForNoBypassCacheReload = waitForNextTabNavigated(client);
+  const waitForNoBypassCacheReload = waitForNextTabNavigated(targetFront);
   const reloadResult = await inspectedWindowFront.reload(fakeExtCallerInfo,
                                                          {ignoreCache: false});
 
   ok(!reloadResult, "Got the expected undefined result from inspectedWindow reload");
 
   await waitForNoBypassCacheReload;
 
   const noBypassCacheEval = await consoleEvalJS(consoleClient,
                                                 "document.body.textContent");
 
   is(noBypassCacheEval.result, "empty cache headers",
      "Got the expected result with reload forceBypassCache=false");
 
   // Test reload with bypassCache=true.
 
-  const waitForForceBypassCacheReload = waitForNextTabNavigated(client);
+  const waitForForceBypassCacheReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo, {ignoreCache: true});
 
   await waitForForceBypassCacheReload;
 
   const forceBypassCacheEval = await consoleEvalJS(consoleClient,
                                                    "document.body.textContent");
 
   is(forceBypassCacheEval.result, "no-cache:no-cache",
      "Got the expected result with reload forceBypassCache=true");
 
   await teardown({client, extension});
 });
 
 add_task(async function test_exception_inspectedWindowReload_customUserAgent() {
   const {
     client, consoleClient, inspectedWindowFront,
-    extension, fakeExtCallerInfo,
+    extension, fakeExtCallerInfo, targetFront,
   } = await setup(`${TEST_RELOAD_URL}?test=user-agent`);
 
   // Test reload with custom userAgent.
 
-  const waitForCustomUserAgentReload = waitForNextTabNavigated(client);
+  const waitForCustomUserAgentReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo,
                                     {userAgent: "Customized User Agent"});
 
   await waitForCustomUserAgentReload;
 
   const customUserAgentEval = await consoleEvalJS(consoleClient,
                                                   "document.body.textContent");
 
   is(customUserAgentEval.result, "Customized User Agent",
      "Got the expected result on reload with a customized userAgent");
 
   // Test reload with no custom userAgent.
 
-  const waitForNoCustomUserAgentReload = waitForNextTabNavigated(client);
+  const waitForNoCustomUserAgentReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo, {});
 
   await waitForNoCustomUserAgentReload;
 
   const noCustomUserAgentEval = await consoleEvalJS(consoleClient,
                                                     "document.body.textContent");
 
   is(noCustomUserAgentEval.result, window.navigator.userAgent,
      "Got the expected result with reload without a customized userAgent");
 
   await teardown({client, extension});
 });
 
 add_task(async function test_exception_inspectedWindowReload_injectedScript() {
   const {
     client, consoleClient, inspectedWindowFront,
-    extension, fakeExtCallerInfo,
+    extension, fakeExtCallerInfo, targetFront,
   } = await setup(`${TEST_RELOAD_URL}?test=injected-script&frames=3`);
 
   // Test reload with an injectedScript.
 
-  const waitForInjectedScriptReload = waitForNextTabNavigated(client);
+  const waitForInjectedScriptReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo,
                                     {injectedScript: `new ${injectedScript}`});
   await waitForInjectedScriptReload;
 
   const injectedScriptEval = await consoleEvalJS(consoleClient,
                                                  `(${collectEvalResults})()`);
 
   const expectedResult = (new Array(5)).fill("injected script executed first");
 
   SimpleTest.isDeeply(JSON.parse(injectedScriptEval.result), expectedResult,
      "Got the expected result on reload with an injected script");
 
   // Test reload without an injectedScript.
 
-  const waitForNoInjectedScriptReload = waitForNextTabNavigated(client);
+  const waitForNoInjectedScriptReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo, {});
   await waitForNoInjectedScriptReload;
 
   const noInjectedScriptEval = await consoleEvalJS(consoleClient,
                                                    `(${collectEvalResults})()`);
 
   const newExpectedResult = (new Array(5)).fill("injected script NOT executed");
 
@@ -334,68 +334,68 @@ add_task(async function test_exception_i
                       "Got the expected result on reload with no injected script");
 
   await teardown({client, extension});
 });
 
 add_task(async function test_exception_inspectedWindowReload_multiple_calls() {
   const {
     client, consoleClient, inspectedWindowFront,
-    extension, fakeExtCallerInfo,
+    extension, fakeExtCallerInfo, targetFront,
   } = await setup(`${TEST_RELOAD_URL}?test=user-agent`);
 
   // Test reload with custom userAgent three times (and then
   // check that only the first one has affected the page reload.
 
-  const waitForCustomUserAgentReload = waitForNextTabNavigated(client);
+  const waitForCustomUserAgentReload = waitForNextTabNavigated(targetFront);
 
   inspectedWindowFront.reload(fakeExtCallerInfo, {userAgent: "Customized User Agent 1"});
   inspectedWindowFront.reload(fakeExtCallerInfo, {userAgent: "Customized User Agent 2"});
 
   await waitForCustomUserAgentReload;
 
   const customUserAgentEval = await consoleEvalJS(consoleClient,
                                                   "document.body.textContent");
 
   is(customUserAgentEval.result, "Customized User Agent 1",
      "Got the expected result on reload with a customized userAgent");
 
   // Test reload with no custom userAgent.
 
-  const waitForNoCustomUserAgentReload = waitForNextTabNavigated(client);
+  const waitForNoCustomUserAgentReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo, {});
 
   await waitForNoCustomUserAgentReload;
 
   const noCustomUserAgentEval = await consoleEvalJS(consoleClient,
                                                     "document.body.textContent");
 
   is(noCustomUserAgentEval.result, window.navigator.userAgent,
      "Got the expected result with reload without a customized userAgent");
 
   await teardown({client, extension});
 });
 
 add_task(async function test_exception_inspectedWindowReload_stopped() {
   const {
     client, consoleClient, inspectedWindowFront,
-    extension, fakeExtCallerInfo,
+    extension, fakeExtCallerInfo, targetFront,
   } = await setup(`${TEST_RELOAD_URL}?test=injected-script&frames=3`);
 
   // Test reload on a page that calls window.stop() immediately during the page loading
 
-  const waitForPageLoad = waitForNextTabNavigated(client);
+  const waitForPageLoad = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.eval(fakeExtCallerInfo,
                                   "window.location += '&stop=windowStop'");
 
   info("Load a webpage that calls 'window.stop()' while is still loading");
   await waitForPageLoad;
 
   info("Starting a reload with an injectedScript");
-  const waitForInjectedScriptReload = waitForNextTabNavigated(client);
+  const waitForInjectedScriptReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo,
                                     {injectedScript: `new ${injectedScript}`});
   await waitForInjectedScriptReload;
 
   const injectedScriptEval = await consoleEvalJS(consoleClient,
                                                  `(${collectEvalResults})()`);
 
   // The page should have stopped during the reload and only one injected script
@@ -403,17 +403,17 @@ add_task(async function test_exception_i
   const expectedResult = (new Array(1)).fill("injected script executed first");
 
   SimpleTest.isDeeply(JSON.parse(injectedScriptEval.result), expectedResult,
      "The injected script has been executed on the 'stopped' page reload");
 
   // Reload again with no options.
 
   info("Reload the tab again without any reload options");
-  const waitForNoInjectedScriptReload = waitForNextTabNavigated(client);
+  const waitForNoInjectedScriptReload = waitForNextTabNavigated(targetFront);
   await inspectedWindowFront.reload(fakeExtCallerInfo, {});
   await waitForNoInjectedScriptReload;
 
   const noInjectedScriptEval = await consoleEvalJS(consoleClient,
                                                    `(${collectEvalResults})()`);
 
   // The page should have stopped during the reload and no injected script should
   // have been executed during this second reload (or it would mean that the previous
--- a/devtools/server/tests/mochitest/test_webextension-addon-debugging-connect.html
+++ b/devtools/server/tests/mochitest/test_webextension-addon-debugging-connect.html
@@ -37,25 +37,24 @@ async function test_connect_addon(oopMod
   await client.connect();
 
   // List addons and assertions on the expected addon actor.
   const {addons} = await client.mainRoot.listAddons();
   const addonTargetActor = addons.filter(actor => actor.id === extension.id).pop();
   ok(addonTargetActor, "The expected webextension addon actor has been found");
 
   // Connect to the target addon actor and wait for the updated list of frames.
-  const waitFramesUpdated = waitForFramesUpdated({client});
   const addonTarget = await TargetFactory.forRemoteTab({
     form: addonTargetActor,
     client,
     chrome: true,
   });
   is(addonTarget.form.isOOP, oopMode,
      "Got the expected oop mode in the webextension actor form");
-  const frames = await waitFramesUpdated;
+  const frames = await waitForFramesUpdated(addonTarget);
   const backgroundPageFrame = frames.filter((frame) => {
     return frame.url && frame.url.endsWith("/_generated_background_page.html");
   }).pop();
   is(backgroundPageFrame.addonID, extension.id, "Got an extension frame");
   ok(addonTarget.activeTab, "The addon target has an activeTab");
 
   // When running in oop mode we can explicitly attach the thread without locking
   // the main process.
--- a/devtools/server/tests/mochitest/webextension-helpers.js
+++ b/devtools/server/tests/mochitest/webextension-helpers.js
@@ -46,29 +46,29 @@ SimpleTest.registerCleanupFunction(funct
 function setWebExtensionOOPMode(oopMode) {
   return SpecialPowers.pushPrefEnv({
     "set": [
       ["extensions.webextensions.remote", oopMode],
     ]
   });
 }
 
-function waitForFramesUpdated({client}, matchFn) {
+function waitForFramesUpdated(target, matchFn) {
   return new Promise(resolve => {
-    const listener = (evt, data) => {
+    const listener = data => {
       if (typeof matchFn === "function" && !matchFn(data)) {
         return;
       } else if (!data.frames) {
         return;
       }
 
-      client.removeListener("frameUpdate", listener);
+      target.activeTab.off("frameUpdate", listener);
       resolve(data.frames);
     };
-    client.addListener("frameUpdate", listener);
+    target.activeTab.on("frameUpdate", listener);
   });
 }
 
 function collectFrameUpdates({client}, matchFn) {
   const collected = [];
 
   const listener = (evt, data) => {
     if (matchFn(data)) {
--- a/devtools/server/tests/unit/test_sourcemaps-01.js
+++ b/devtools/server/tests/unit/test_sourcemaps-01.js
@@ -39,17 +39,17 @@ function test_simple_source_map() {
     Assert.equal(packet.type, "newSource");
     Assert.ok(!!packet.source);
 
     Assert.ok(expectedSources.has(packet.source.url),
               "The source url should be one of our original sources.");
     expectedSources.delete(packet.source.url);
 
     if (expectedSources.size === 0) {
-      gClient.removeListener("newSource", _onNewSource);
+      gThreadClient.removeListener("newSource", _onNewSource);
       finishClient(gClient);
     }
   });
 
   let { code, map } = (new SourceNode(null, null, null, [
     new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"),
     new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"),
     new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"),
--- a/devtools/shared/client/constants.js
+++ b/devtools/shared/client/constants.js
@@ -28,23 +28,26 @@ const UnsolicitedNotifications = {
   "networkEventUpdate": "networkEventUpdate",
   "documentEvent": "documentEvent",
   "tabDetached": "tabDetached",
   "tabListChanged": "tabListChanged",
   "reflowActivity": "reflowActivity",
   "addonListChanged": "addonListChanged",
   "workerListChanged": "workerListChanged",
   "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
-  "tabNavigated": "tabNavigated",
-  "frameUpdate": "frameUpdate",
   "pageError": "pageError",
   "evaluationResult": "evaluationResult",
+  "updatedSource": "updatedSource",
+  "inspectObject": "inspectObject",
+
+  // newSource is still emitted on the ThreadActor, in addition to the
+  // BrowsingContextActor we have to keep it here until ThreadClient is converted to
+  // ThreadFront and/or we stop emitting this duplicated events.
+  // See ThreadActor.onNewSourceEvent.
   "newSource": "newSource",
-  "updatedSource": "updatedSource",
-  "inspectObject": "inspectObject"
 };
 
 /**
  * Set of pause types that are sent by the server and not as an immediate
  * response to a client request.
  */
 const UnsolicitedPauses = {
   "resumeLimit": "resumeLimit",
--- a/devtools/shared/client/debugger-client.js
+++ b/devtools/shared/client/debugger-client.js
@@ -19,20 +19,22 @@ const {
 
 loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
 loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
 loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
 loader.lazyRequireGetter(this, "AddonClient", "devtools/shared/client/addon-client");
 loader.lazyRequireGetter(this, "RootClient", "devtools/shared/client/root-client");
-loader.lazyRequireGetter(this, "TabClient", "devtools/shared/client/tab-client");
+loader.lazyRequireGetter(this, "BrowsingContextFront", "devtools/shared/fronts/targets/browsing-context", true);
 loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
 loader.lazyRequireGetter(this, "WorkerClient", "devtools/shared/client/worker-client");
 loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/object-client");
+loader.lazyRequireGetter(this, "Pool", "devtools/shared/protocol", true);
+loader.lazyRequireGetter(this, "Front", "devtools/shared/protocol", true);
 
 // Retrieve the major platform version, i.e. if we are on Firefox 64.0a1, it will be 64.
 const PLATFORM_MAJOR_VERSION = AppConstants.MOZ_APP_VERSION.match(/\d+/)[0];
 
 // Define the minimum officially supported version of Firefox when connecting to a remote
 // runtime. (Use ".0a1" to support the very first nightly version)
 // This matches the release channel's version when we are on nightly,
 // or 2 versions before when we are on other channels.
@@ -45,18 +47,29 @@ const MS_PER_DAY = 86400000;
  * provides the means to communicate with the server and exchange the messages
  * required by the protocol in a traditional JavaScript API.
  */
 function DebuggerClient(transport) {
   this._transport = transport;
   this._transport.hooks = this;
 
   // Map actor ID to client instance for each actor type.
+  // To be removed once all clients are refactored to protocol.js
   this._clients = new Map();
 
+  // Pool of fronts instanciated by this class.
+  // This is useful for actors that have already been transitioned to protocol.js
+  // Once RootClient becomes a protocol.js actor, these actors can be attached to it
+  // instead of this pool.
+  // This Pool will automatically be added to this._pools via addActorPool once the first
+  // Front will be added to it (in attachTarget, attachWorker,...).
+  // And it does not need to destroyed explicitly as all Pools are destroyed on client
+  // closing.
+  this._frontPool = new Pool(this);
+
   this._pendingRequests = new Map();
   this._activeRequests = new Map();
   this._eventsEnabled = true;
 
   this.traits = {};
 
   this.request = this.request.bind(this);
   this.localTransport = this._transport.onOutputStreamReady === undefined;
@@ -279,17 +292,19 @@ DebuggerClient.prototype = {
       deferred.promise.then(onClosed);
     }
 
     // Disable detach event notifications, because event handlers will be in a
     // cleared scope by the time they run.
     this._eventsEnabled = false;
 
     const cleanup = () => {
-      this._transport.close();
+      if (this._transport) {
+        this._transport.close();
+      }
       this._transport = null;
     };
 
     // If the connection is already closed,
     // there is no need to detach client
     // as we won't be able to send any message.
     if (this._closed) {
       cleanup();
@@ -349,39 +364,25 @@ DebuggerClient.prototype = {
    *  - start watching for inner iframe updates (emits `frameUpdate` messages)
    *  - retrieve the thread actor:
    *    Instantiates a new ThreadActor that can be later attached to in order to
    *    debug JS sources in the document.
    *
    * @param string targetActor
    *        The target actor ID for the tab to attach.
    */
-  attachTarget: function(targetActor) {
-    if (this._clients.has(targetActor)) {
-      const cachedTarget = this._clients.get(targetActor);
-      const cachedResponse = {
-        cacheDisabled: cachedTarget.cacheDisabled,
-        javascriptEnabled: cachedTarget.javascriptEnabled,
-        traits: cachedTarget.traits,
-      };
-      return promise.resolve([cachedResponse, cachedTarget]);
+  attachTarget: async function(targetActor) {
+    let front = this._frontPool.actor(targetActor);
+    if (!front) {
+      front = new BrowsingContextFront(this, { actor: targetActor });
+      this._frontPool.manage(front);
     }
 
-    const packet = {
-      to: targetActor,
-      type: "attach"
-    };
-    return this.request(packet).then(response => {
-      // TabClient can actually represent targets other than a tab.
-      // It is planned to be renamed while being converted to a front
-      // in bug 1485660.
-      const targetClient = new TabClient(this, response);
-      this.registerClient(targetClient);
-      return [response, targetClient];
-    });
+    const response = await front.attach();
+    return [response, front];
   },
 
   attachWorker: function(workerTargetActor) {
     let workerClient = this._clients.get(workerTargetActor);
     if (workerClient !== undefined) {
       const response = {
         from: workerClient.actor,
         type: "attached",
@@ -1041,17 +1042,21 @@ DebuggerClient.prototype = {
     // Use a Set because some fronts (like domwalker) seem to have multiple parents.
     const fronts = new Set();
     const poolsToVisit = [...this._pools];
 
     // With protocol.js, each front can potentially have it's own pools containing child
     // fronts, forming a tree.  Descend through all the pools to locate all child fronts.
     while (poolsToVisit.length) {
       const pool = poolsToVisit.shift();
-      fronts.add(pool);
+      // `_pools` contains either Front's or Pool's, we only want to collect Fronts here.
+      // Front inherits from Pool which exposes `poolChildren`.
+      if (pool instanceof Front) {
+        fronts.add(pool);
+      }
       for (const child of pool.poolChildren()) {
         poolsToVisit.push(child);
       }
     }
 
     // For each front, wait for its requests to settle
     for (const front of fronts) {
       if (front.hasRequests()) {
--- a/devtools/shared/client/moz.build
+++ b/devtools/shared/client/moz.build
@@ -14,12 +14,11 @@ DevToolsModules(
     'environment-client.js',
     'event-source.js',
     'long-string-client.js',
     'object-client.js',
     'property-iterator-client.js',
     'root-client.js',
     'source-client.js',
     'symbol-iterator-client.js',
-    'tab-client.js',
     'thread-client.js',
     'worker-client.js',
 )
deleted file mode 100644
--- a/devtools/shared/client/tab-client.js
+++ /dev/null
@@ -1,163 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const promise = require("devtools/shared/deprecated-sync-thenables");
-
-const eventSource = require("devtools/shared/client/event-source");
-const {arg, DebuggerClient} = require("devtools/shared/client/debugger-client");
-loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
-
-/**
- * Creates a tab client for the remote debugging protocol server. This client is a front
- * to the target actor for a tab created in the server side, hiding the protocol details
- * in a traditional JavaScript API.
- *
- * @param client DebuggerClient
- *        The debugger client parent.
- * @param form object
- *        The protocol form for this tab.
- */
-function TabClient(client, form) {
-  this.client = client;
-  this._actor = form.from;
-  this._threadActor = form.threadActor;
-  this.javascriptEnabled = form.javascriptEnabled;
-  this.cacheDisabled = form.cacheDisabled;
-  this.thread = null;
-  this.request = this.client.request;
-  this.traits = form.traits || {};
-  this.events = ["workerListChanged"];
-}
-
-TabClient.prototype = {
-  get actor() {
-    return this._actor;
-  },
-  get _transport() {
-    return this.client._transport;
-  },
-
-  /**
-   * Attach to a thread actor.
-   *
-   * @param object options
-   *        Configuration options.
-   *        - useSourceMaps: whether to use source maps or not.
-   */
-  attachThread: function(options = {}) {
-    if (this.thread) {
-      return promise.resolve([{}, this.thread]);
-    }
-
-    const packet = {
-      to: this._threadActor,
-      type: "attach",
-      options,
-    };
-    return this.request(packet).then(response => {
-      this.thread = new ThreadClient(this, this._threadActor);
-      this.client.registerClient(this.thread);
-      return [response, this.thread];
-    });
-  },
-
-  /**
-   * Detach the client from the target actor.
-   */
-  detach: DebuggerClient.requester({
-    type: "detach"
-  }, {
-    before: function(packet) {
-      if (this.thread) {
-        this.thread.detach();
-      }
-      return packet;
-    },
-    after: function(response) {
-      this.client.unregisterClient(this);
-      return response;
-    },
-  }),
-
-  /**
-   * Bring the window to the front.
-   */
-  focus: DebuggerClient.requester({
-    type: "focus"
-  }, {}),
-
-  /**
-   * Ensure relevant pages have error reporting enabled.
-   */
-  ensureCSSErrorReportingEnabled: DebuggerClient.requester({
-    type: "ensureCSSErrorReportingEnabled",
-  }, {}),
-
-  /**
-   * Reload the page in this tab.
-   *
-   * @param [optional] object options
-   *        An object with a `force` property indicating whether or not
-   *        this reload should skip the cache
-   */
-  reload: function(options = { force: false }) {
-    return this._reload(options);
-  },
-  _reload: DebuggerClient.requester({
-    type: "reload",
-    options: arg(0)
-  }),
-
-  /**
-   * Navigate to another URL.
-   *
-   * @param string url
-   *        The URL to navigate to.
-   */
-  navigateTo: DebuggerClient.requester({
-    type: "navigateTo",
-    url: arg(0)
-  }),
-
-  /**
-   * Reconfigure the target actor.
-   *
-   * @param object options
-   *        A dictionary object of the new options to use in the target actor.
-   */
-  reconfigure: DebuggerClient.requester({
-    type: "reconfigure",
-    options: arg(0)
-  }),
-
-  listWorkers: DebuggerClient.requester({
-    type: "listWorkers"
-  }),
-
-  attachWorker: function(workerTargetActor) {
-    return this.client.attachWorker(workerTargetActor);
-  },
-
-  logInPage: DebuggerClient.requester({
-    type: "logInPage",
-    text: arg(0),
-    category: arg(1),
-    flags: arg(2),
-  }),
-
-  listFrames: DebuggerClient.requester({
-    type: "listFrames",
-  }),
-
-  switchToFrame: DebuggerClient.requester({
-    type: "switchToFrame",
-    windowId: arg(0),
-  }),
-};
-
-eventSource(TabClient.prototype);
-
-module.exports = TabClient;
--- a/devtools/shared/client/thread-client.js
+++ b/devtools/shared/client/thread-client.js
@@ -20,17 +20,17 @@ loader.lazyRequireGetter(this, "SourceCl
 
 const noop = () => {};
 
 /**
  * Creates a thread client for the remote debugging protocol server. This client
  * is a front to the thread actor created in the server side, hiding the
  * protocol details in a traditional JavaScript API.
  *
- * @param client DebuggerClient|TabClient
+ * @param client DebuggerClient, WorkerClient or BrowsingContextFront
  *        The parent of the thread (tab for target-scoped debuggers,
  *        DebuggerClient for chrome debuggers).
  * @param actor string
  *        The actor ID for this thread.
  */
 function ThreadClient(client, actor) {
   this._parent = client;
   this.client = client instanceof DebuggerClient ? client : client.client;
--- a/devtools/shared/fronts/memory.js
+++ b/devtools/shared/fronts/memory.js
@@ -57,35 +57,50 @@ const MemoryFront = protocol.FrontClassW
    * Given that we have taken a heap snapshot with the given id, transfer the
    * heap snapshot file to the client. The path to the client's local file is
    * returned.
    *
    * @param {String} snapshotId
    *
    * @returns Promise<String>
    */
-  transferHeapSnapshot: protocol.custom(function(snapshotId) {
+  transferHeapSnapshot: protocol.custom(async function(snapshotId) {
     if (!this.heapSnapshotFileActorID) {
       throw new Error("MemoryFront initialized without a rootForm");
     }
 
-    const request = this._client.request({
-      to: this.heapSnapshotFileActorID,
-      type: "transferHeapSnapshot",
-      snapshotId
-    });
+    try {
+      const request = this._client.request({
+        to: this.heapSnapshotFileActorID,
+        type: "transferHeapSnapshot",
+        snapshotId
+      });
 
-    return new Promise((resolve, reject) => {
       const outFilePath =
         HeapSnapshotFileUtils.getNewUniqueHeapSnapshotTempFilePath();
       const outFile = new FileUtils.File(outFilePath);
+      const outFileStream = FileUtils.openSafeFileOutputStream(outFile);
 
-      const outFileStream = FileUtils.openSafeFileOutputStream(outFile);
-      request.on("bulk-reply", async function({ copyTo }) {
-        await copyTo(outFileStream);
-        FileUtils.closeSafeFileOutputStream(outFileStream);
-        resolve(outFilePath);
-      });
-    });
+      // This request is a bulk request. That's why the result of the request is
+      // an object with the `copyTo` function that can transfer the data to
+      // another stream.
+      // See devtools/shared/transport/transport.js to know more about this mode.
+      const { copyTo } = await request;
+      await copyTo(outFileStream);
+
+      FileUtils.closeSafeFileOutputStream(outFileStream);
+      return outFilePath;
+    } catch (e) {
+      if (e.error) {
+        // This isn't a real error, rather this is a message coming from the
+        // server. So let's throw a real error instead.
+        throw new Error(
+          `The server's actor threw an error: (${e.error}) ${e.message}`
+        );
+      }
+
+      // Otherwise, rethrow the error
+      throw e;
+    }
   })
 });
 
 exports.MemoryFront = MemoryFront;
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -1,16 +1,17 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'addon',
+    'targets',
 ]
 
 DevToolsModules(
     'accessibility.js',
     'actor-registry.js',
     'animation.js',
     'canvas.js',
     'css-properties.js',
new file mode 100644
--- /dev/null
+++ b/devtools/shared/fronts/targets/browsing-context.js
@@ -0,0 +1,88 @@
+/* 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 {browsingContextTargetSpec} = require("devtools/shared/specs/targets/browsing-context");
+const protocol = require("devtools/shared/protocol");
+const {custom} = protocol;
+
+loader.lazyRequireGetter(this, "ThreadClient", "devtools/shared/client/thread-client");
+
+const BrowsingContextFront = protocol.FrontClassWithSpec(browsingContextTargetSpec, {
+  initialize: function(client, form) {
+    protocol.Front.prototype.initialize.call(this, client, form);
+
+    this.thread = null;
+
+    // TODO: remove once ThreadClient becomes a front
+    this.client = client;
+  },
+
+  /**
+   * Attach to a thread actor.
+   *
+   * @param object options
+   *        Configuration options.
+   *        - useSourceMaps: whether to use source maps or not.
+   */
+  attachThread: function(options = {}) {
+    if (this.thread) {
+      return Promise.resolve([{}, this.thread]);
+    }
+
+    const packet = {
+      to: this._threadActor,
+      type: "attach",
+      options,
+    };
+    return this.client.request(packet).then(response => {
+      this.thread = new ThreadClient(this, this._threadActor);
+      this.client.registerClient(this.thread);
+      return [response, this.thread];
+    });
+  },
+
+  attach: custom(async function() {
+    const response = await this._attach();
+
+    this._threadActor = response.threadActor;
+    this.javascriptEnabled = response.javascriptEnabled;
+    this.cacheDisabled = response.cacheDisabled;
+    this.traits = response.traits || {};
+
+    return response;
+  }, {
+    impl: "_attach"
+  }),
+
+  detach: custom(async function() {
+    let response;
+    try {
+      response = await this._detach();
+    } catch (e) {
+      console.warn(
+        `Error while detaching the browsing context target front: ${e.message}`);
+    }
+
+    if (this.thread) {
+      try {
+        await this.thread.detach();
+      } catch (e) {
+        console.warn(`Error while detaching the thread front: ${e.message}`);
+      }
+    }
+
+    this.destroy();
+
+    return response;
+  }, {
+    impl: "_detach"
+  }),
+
+  attachWorker: function(workerTargetActor) {
+    return this.client.attachWorker(workerTargetActor);
+  },
+});
+
+exports.BrowsingContextFront = BrowsingContextFront;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/fronts/targets/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+    'browsing-context.js',
+)
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -1347,17 +1347,20 @@ Front.prototype = extend(Pool.prototype,
    * Send a packet on the connection.
    */
   send: function(packet) {
     if (packet.to) {
       this.conn._transport.send(packet);
     } else {
       this.actor().then(actorID => {
         packet.to = actorID;
-        this.conn._transport.send(packet);
+        // The connection might be closed during the promise resolution
+        if (this.conn._transport) {
+          this.conn._transport.send(packet);
+        }
       }).catch(console.error);
     }
   },
 
   /**
    * Send a two-way request on the connection.
    */
   request: function(packet) {
--- a/devtools/shared/specs/targets/browsing-context.js
+++ b/devtools/shared/specs/targets/browsing-context.js
@@ -122,20 +122,27 @@ const browsingContextTargetSpecPrototype
     },
     frameUpdate: {
       type: "frameUpdate",
       frames: Option(0, "nullable:array:browsingContextTarget.window"),
       selected: Option(0, "nullable:number"),
       destroyAll: Option(0, "nullable:boolean")
     },
     tabDetached: {
-      type: "tabDetached"
+      type: "tabDetached",
+      // This is to make browser_dbg_navigation.js to work as it expect to
+      // see a packet object when listening for tabDetached
+      from: Option(0, "string"),
     },
     workerListChanged: {
       type: "workerListChanged"
+    },
+    newSource: {
+      type: "newSource",
+      source: Option(0, "json")
     }
   }
 };
 
 const browsingContextTargetSpec = generateActorSpec(browsingContextTargetSpecPrototype);
 
 exports.browsingContextTargetSpecPrototype = browsingContextTargetSpecPrototype;
 exports.browsingContextTargetSpec = browsingContextTargetSpec;
--- a/devtools/shared/webconsole/test/test_object_actor.html
+++ b/devtools/shared/webconsole/test/test_object_actor.html
@@ -9,16 +9,19 @@
      - http://creativecommons.org/publicdomain/zero/1.0/ -->
 </head>
 <body>
 <p>Test for the object actor</p>
 
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
+SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+																		true]]});
+
 let expectedProps = [];
 
 function startTest() {
   removeEventListener("load", startTest);
 
   attachConsoleToTab(["ConsoleAPI"], onAttach);
 }
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2983,16 +2983,22 @@ nsDocShell::SetDocLoaderParent(nsDocLoad
   nsCOMPtr<nsIURIContentListener> parentURIListener(do_GetInterface(parent));
   if (parentURIListener) {
     mContentListener->SetParentContentListener(parentURIListener);
   }
 
   // Our parent has changed. Recompute scriptability.
   RecomputeCanExecuteScripts();
 
+  nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow();
+  if (window) {
+    auto* win = nsGlobalWindowOuter::Cast(window);
+    win->ParentWindowChanged();
+  }
+
   NS_ASSERTION(mInheritPrivateBrowsingId || wasPrivate == UsePrivateBrowsing(),
                "Private browsing state changed while inheritance was disabled");
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetSameTypeParent(nsIDocShellTreeItem** aParent)
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -13667,41 +13667,16 @@ nsIDocument::HasStorageAccess(mozilla::E
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
   if (topLevelDoc->NodePrincipal()->Equals(NodePrincipal())) {
     promise->MaybeResolve(true);
     return promise.forget();
   }
 
-  if (AntiTrackingCommon::ShouldHonorContentBlockingCookieRestrictions() &&
-      StaticPrefs::network_cookie_cookieBehavior() ==
-        nsICookieService::BEHAVIOR_REJECT_TRACKER) {
-    // If we need to abide by Content Blocking cookie restrictions, ensure to
-    // first do all of our storage access checks.  If storage access isn't
-    // disabled in our document, given that we're a third-party, we must either
-    // not be a tracker, or be whitelisted for some reason (e.g. a storage
-    // access permission being granted).  In that case, resolve the promise and
-    // say we have obtained storage access.
-    if (!nsContentUtils::StorageDisabledByAntiTracking(this, nullptr)) {
-      // Note, storage might be allowed because the top-level document is on
-      // the content blocking allowlist!  In that case, don't provide special
-      // treatment here.
-      bool isOnAllowList = false;
-      if (NS_SUCCEEDED(AntiTrackingCommon::IsOnContentBlockingAllowList(
-                         topLevelDoc->GetDocumentURI(),
-                         AntiTrackingCommon::eStorageChecks,
-                         isOnAllowList)) &&
-          !isOnAllowList) {
-        promise->MaybeResolve(true);
-        return promise.forget();
-      }
-    }
-  }
-
   nsPIDOMWindowInner* inner = GetInnerWindow();
   nsGlobalWindowOuter* outer = nullptr;
   if (inner) {
     outer = nsGlobalWindowOuter::Cast(inner->GetOuterWindow());
     promise->MaybeResolve(outer->HasStorageAccess());
   } else {
     promise->MaybeRejectWithUndefined();
   }
@@ -13829,16 +13804,17 @@ nsIDocument::RequestStorageAccess(mozill
                  outer->SetHasStorageAccess(true);
                  promise->MaybeResolveWithUndefined();
                },
                [outer, promise] (bool) {
                  outer->SetHasStorageAccess(false);
                  promise->MaybeRejectWithUndefined();
                });
     } else {
+      outer->SetHasStorageAccess(true);
       promise->MaybeResolveWithUndefined();
     }
   }
   return promise.forget();
 }
 
 void
 nsIDocument::RecordNavigationTiming(ReadyState aReadyState)
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -13,16 +13,18 @@
 // Local Includes
 #include "Navigator.h"
 #include "nsContentSecurityManager.h"
 #include "nsScreen.h"
 #include "nsHistory.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsICookieService.h"
 #include "nsIDOMStorageManager.h"
+#include "nsIPermission.h"
+#include "nsIPermissionManager.h"
 #include "nsISecureBrowserUI.h"
 #include "nsIWebProgressListener.h"
 #include "mozilla/AntiTrackingCommon.h"
 #include "mozilla/dom/ContentFrameMessageManager.h"
 #include "mozilla/dom/EventTarget.h"
 #include "mozilla/dom/LocalStorage.h"
 #include "mozilla/dom/Storage.h"
 #include "mozilla/dom/IdleRequest.h"
@@ -973,16 +975,21 @@ nsGlobalWindowOuter::~nsGlobalWindowOute
   // Outer windows are always supposed to call CleanUp before letting themselves
   // be destroyed.
   MOZ_ASSERT(mCleanedUp);
 
   nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
   if (ac)
     ac->RemoveWindowAsListener(this);
 
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, PERM_CHANGE_NOTIFICATION);
+  }
+
   nsLayoutStatics::Release();
 }
 
 // static
 void
 nsGlobalWindowOuter::ShutDown()
 {
   AssertIsOnMainThread();
@@ -1084,16 +1091,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject)
   NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
   NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
   NS_INTERFACE_MAP_ENTRY(nsPIDOMWindowOuter)
   NS_INTERFACE_MAP_ENTRY(mozIDOMWindowProxy)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMChromeWindow, IsChromeWindow())
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
 NS_INTERFACE_MAP_END
 
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindowOuter)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindowOuter)
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindowOuter)
   if (tmp->IsBlackForCC(false)) {
@@ -6797,16 +6805,66 @@ nsGlobalWindowOuter::GetInterface(const 
 {
   nsresult rv = GetInterfaceInternal(aIID, aSink);
   if (rv == NS_ERROR_NO_INTERFACE) {
     return QueryInterface(aIID, aSink);
   }
   return rv;
 }
 
+//*****************************************************************************
+// nsGlobalWindowOuter::nsIObserver
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsGlobalWindowOuter::Observe(nsISupports* aSupports, const char* aTopic,
+                             const char16_t* aData)
+{
+  if (!nsCRT::strcmp(aTopic, PERM_CHANGE_NOTIFICATION)) {
+    if (!nsCRT::strcmp(aData, u"cleared") && !aSupports) {
+      // All permissions have been cleared.
+      mHasStorageAccess = false;
+      return NS_OK;
+    }
+    nsCOMPtr<nsIPermission> permission = do_QueryInterface(aSupports);
+    if (!permission) {
+      return NS_OK;
+    }
+    nsIPrincipal* principal = GetPrincipal();
+    if (!principal) {
+      return NS_OK;
+    }
+    if (!AntiTrackingCommon::IsStorageAccessPermission(permission, principal)) {
+      return NS_OK;
+    }
+    if (!nsCRT::strcmp(aData, u"deleted")) {
+      // The storage access permission was deleted.
+      mHasStorageAccess = false;
+      return NS_OK;
+    }
+    if (!nsCRT::strcmp(aData, u"added") ||
+        !nsCRT::strcmp(aData, u"changed")) {
+      // The storage access permission was granted or modified.
+      uint32_t expireType = 0;
+      int64_t expireTime = 0;
+      MOZ_ALWAYS_SUCCEEDS(permission->GetExpireType(&expireType));
+      MOZ_ALWAYS_SUCCEEDS(permission->GetExpireTime(&expireTime));
+      if ((expireType == nsIPermissionManager::EXPIRE_TIME &&
+           expireTime >= PR_Now() / 1000) ||
+          (expireType == nsIPermissionManager::EXPIRE_SESSION &&
+           expireTime != 0)) {
+        // Permission hasn't expired yet.
+        mHasStorageAccess = true;
+        return NS_OK;
+      }
+    }
+  }
+  return NS_OK;
+}
+
 bool
 nsGlobalWindowOuter::IsSuspended() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   // No inner means we are effectively suspended
   if (!mInnerWindow) {
     return true;
   }
@@ -7668,16 +7726,20 @@ nsGlobalWindowOuter::Create(nsIDocShell*
   uint64_t outerWindowID = aDocShell->GetOuterWindowID();
   RefPtr<nsGlobalWindowOuter> window = new nsGlobalWindowOuter(outerWindowID);
   if (aIsChrome) {
     window->mIsChrome = true;
   }
   window->SetDocShell(aDocShell);
 
   window->InitWasOffline();
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->AddObserver(window, PERM_CHANGE_NOTIFICATION, true);
+  }
   return window.forget();
 }
 
 nsIURI*
 nsPIDOMWindowOuter::GetDocumentURI() const
 {
   return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get();
 }
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -22,16 +22,17 @@
 #include "nsDataHashtable.h"
 #include "nsJSThingHashtable.h"
 #include "nsCycleCollectionParticipant.h"
 
 // Interfaces Needed
 #include "nsIBrowserDOMWindow.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIDOMChromeWindow.h"
+#include "nsIObserver.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsITimer.h"
 #include "mozilla/EventListenerManager.h"
 #include "nsIPrincipal.h"
 #include "nsSize.h"
 #include "mozilla/FlushType.h"
 #include "prclist.h"
@@ -167,16 +168,17 @@ class nsGlobalWindowOuter final
     // NOTE: This interface is private, as it's only
     // implemented on chrome windows.
   , private nsIDOMChromeWindow
   , public nsIScriptGlobalObject
   , public nsIScriptObjectPrincipal
   , public nsSupportsWeakReference
   , public nsIInterfaceRequestor
   , public PRCListStr
+  , public nsIObserver
 {
 public:
   typedef nsDataHashtable<nsUint64HashKey, nsGlobalWindowOuter*> OuterWindowByIdTable;
 
   static void
   AssertIsOnMainThread()
 #ifdef DEBUG
   ;
@@ -356,16 +358,19 @@ public:
   void FinishFullscreenChange(bool aIsFullscreen) final;
   bool SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen,
                            nsIWidget* aWidget, nsIScreen* aScreen);
   bool Fullscreen() const;
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
 
+  // nsIObserver
+  NS_DECL_NSIOBSERVER
+
   already_AddRefed<nsPIDOMWindowOuter> IndexedGetterOuter(uint32_t aIndex);
 
   already_AddRefed<nsPIDOMWindowOuter> GetTop() override;
   nsPIDOMWindowOuter* GetScriptableTop() override;
   inline nsGlobalWindowOuter *GetTopInternal();
 
   inline nsGlobalWindowOuter* GetScriptableTopInternal();
 
@@ -727,16 +732,22 @@ public:
 
   nsIDOMWindowUtils* WindowUtils();
 
   virtual bool IsInSyncOperation() override
   {
     return GetExtantDoc() && GetExtantDoc()->IsInSyncOperation();
   }
 
+  void ParentWindowChanged()
+  {
+    // Reset our storage access flag when we get reparented.
+    mHasStorageAccess = false;
+  }
+
 public:
   int32_t GetInnerWidthOuter(mozilla::ErrorResult& aError);
 protected:
   nsresult GetInnerWidth(int32_t* aWidth) override;
   void SetInnerWidthOuter(int32_t aInnerWidth,
                           mozilla::dom::CallerType aCallerType,
                           mozilla::ErrorResult& aError);
 public:
--- a/dom/base/test/browser_bug1058164.js
+++ b/dom/base/test/browser_bug1058164.js
@@ -1,14 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+																		true]]});
+
 const PAGE = "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
 
 /**
  * Returns a Promise that resolves when it sees a pageshow and
  * pagehide events in a particular order, where each event must
  * have the persisted property set to true. Will cause a test
  * failure to be logged if it sees an event out of order.
  *
--- a/dom/base/test/chrome/bug418986-1.js
+++ b/dom/base/test/chrome/bug418986-1.js
@@ -1,12 +1,15 @@
 // The main test function.
 var test = function (isContent) {
   SimpleTest.waitForExplicitFinish();
 
+	SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+																		  true]]});
+
   let { ww } = SpecialPowers.Services;
   window.chromeWindow = ww.activeWindow;
 
   // The pairs of values expected to be the same when
   // fingerprinting resistance is enabled.
   let pairs = [
     ["screenX", 0],
     ["screenY", 0],
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -502,21 +502,16 @@ DOMInterfaces = {
     'concrete': False
 },
 
 'LegacyMozTCPSocket': {
     'headerFile': 'TCPSocket.h',
     'wrapperCache': False,
 },
 
-'LocalMediaStream': {
-    'headerFile': 'DOMMediaStream.h',
-    'nativeType': 'mozilla::DOMLocalMediaStream'
-},
-
 'MatchGlob': {
     'nativeType': 'mozilla::extensions::MatchGlob',
 },
 
 'MatchPattern': {
     'nativeType': 'mozilla::extensions::MatchPattern',
 },
 
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -136,18 +136,16 @@ MediaDecodeWarning=Media resource %1$S c
 MediaCannotPlayNoDecoders=Cannot play media. No decoders for requested formats: %S
 # LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
 MediaNoDecoders=No decoders for some of the requested formats: %S
 MediaCannotInitializePulseAudio=Unable to use PulseAudio
 # LOCALIZATION NOTE: Do not translate "MediaRecorder".
 MediaRecorderMultiTracksNotSupported=MediaRecorder does not support recording multiple tracks of the same type at this time.
 # LOCALIZATION NOTE: %S is the ID of the MediaStreamTrack passed to MediaStream.addTrack(). Do not translate "MediaStreamTrack" and "AudioChannel".
 MediaStreamAddTrackDifferentAudioChannel=MediaStreamTrack %S could not be added since it belongs to a different AudioChannel.
-# LOCALIZATION NOTE: Do not translate "MediaStream", "stop()" and "MediaStreamTrack"
-MediaStreamStopDeprecatedWarning=MediaStream.stop() is deprecated and will soon be removed. Use MediaStreamTrack.stop() instead.
 # LOCALIZATION NOTE: %S is the URL of the web page which is not served on HTTPS and thus is not encrypted and considered insecure.
 MediaEMEInsecureContextDeprecatedWarning=Using Encrypted Media Extensions at %S on an insecure (i.e. non-HTTPS) context is deprecated and will soon be removed. You should consider switching to a secure origin such as HTTPS.
 # LOCALIZATION NOTE: %S is the URL of the web page which is calling web APIs without passing data (either an audioCapabilities or a videoCapabilities) that will soon be required. See https://bugzilla.mozilla.org/show_bug.cgi?id=1368583#c21 for explanation of this string.
 MediaEMENoCapabilitiesDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) without passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities is deprecated and will soon become unsupported.
 # LOCALIZATION NOTE: %S is the URL of the web page which is calling web APIs without passing data (a "codecs" string in the "contentType") that will soon be required. See https://bugzilla.mozilla.org/show_bug.cgi?id=1368583#c21 for explanation of this string.
 MediaEMENoCodecsDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities without a contentType with a “codecs” string is deprecated and will soon become unsupported.
 # LOCALIZATION NOTE: Do not translate "Mutation Event" and "MutationObserver"
 MutationEventWarning=Use of Mutation Events is deprecated. Use MutationObserver instead.
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -13,17 +13,16 @@
 #include "MediaStreamGraphImpl.h"
 #include "MediaStreamListener.h"
 #include "VideoStreamTrack.h"
 #include "mozilla/dom/AudioNode.h"
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
-#include "mozilla/dom/LocalMediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamBinding.h"
 #include "mozilla/dom/MediaStreamTrackEvent.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 #include "mozilla/media/MediaUtils.h"
 #include "nsContentUtils.h"
 #include "nsIScriptError.h"
@@ -399,23 +398,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(DOMMediaStream, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(DOMMediaStream, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMMediaStream)
   NS_INTERFACE_MAP_ENTRY(DOMMediaStream)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
-NS_IMPL_ADDREF_INHERITED(DOMLocalMediaStream, DOMMediaStream)
-NS_IMPL_RELEASE_INHERITED(DOMLocalMediaStream, DOMMediaStream)
-
-NS_INTERFACE_MAP_BEGIN(DOMLocalMediaStream)
-  NS_INTERFACE_MAP_ENTRY(DOMLocalMediaStream)
-NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
-
 NS_IMPL_CYCLE_COLLECTION_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream,
                                    mStreamNode)
 
 NS_IMPL_ADDREF_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
 NS_IMPL_RELEASE_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMAudioNodeMediaStream)
 NS_INTERFACE_MAP_END_INHERITING(DOMMediaStream)
@@ -1528,75 +1520,16 @@ DOMMediaStream::NotifyPlaybackTrackBlock
     // waiting for any further tracks to get blocked. It is now safe to
     // recompute the principal based on our main thread track set state.
     LOG(LogLevel::Debug, ("DOMMediaStream %p saw all tracks pending removal "
                           "finish. Recomputing principal.", this));
     RecomputePrincipal();
   }
 }
 
-DOMLocalMediaStream::~DOMLocalMediaStream()
-{
-  if (mInputStream) {
-    // Make sure Listeners of this stream know it's going away
-    StopImpl();
-  }
-}
-
-JSObject*
-DOMLocalMediaStream::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return dom::LocalMediaStream_Binding::Wrap(aCx, this, aGivenProto);
-}
-
-void
-DOMLocalMediaStream::Stop()
-{
-  LOG(LogLevel::Debug, ("DOMMediaStream %p Stop()", this));
-  nsCOMPtr<nsPIDOMWindowInner> pWindow = GetParentObject();
-  nsIDocument* document = pWindow ? pWindow->GetExtantDoc() : nullptr;
-  nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
-                                  NS_LITERAL_CSTRING("Media"),
-                                  document,
-                                  nsContentUtils::eDOM_PROPERTIES,
-                                  "MediaStreamStopDeprecatedWarning");
-
-  StopImpl();
-}
-
-void
-DOMLocalMediaStream::StopImpl()
-{
-  if (mInputStream && mInputStream->AsSourceStream()) {
-    mInputStream->AsSourceStream()->EndAllTrackAndFinish();
-  }
-}
-
-already_AddRefed<DOMLocalMediaStream>
-DOMLocalMediaStream::CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow,
-                                               MediaStreamGraph* aGraph,
-                                               MediaStreamTrackSourceGetter* aTrackSourceGetter)
-{
-  RefPtr<DOMLocalMediaStream> stream =
-    new DOMLocalMediaStream(aWindow, aTrackSourceGetter);
-  stream->InitSourceStream(aGraph);
-  return stream.forget();
-}
-
-already_AddRefed<DOMLocalMediaStream>
-DOMLocalMediaStream::CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow,
-                                                   MediaStreamGraph* aGraph,
-                                                   MediaStreamTrackSourceGetter* aTrackSourceGetter)
-{
-  RefPtr<DOMLocalMediaStream> stream =
-    new DOMLocalMediaStream(aWindow, aTrackSourceGetter);
-  stream->InitTrackUnionStream(aGraph);
-  return stream.forget();
-}
-
 DOMAudioNodeMediaStream::DOMAudioNodeMediaStream(nsPIDOMWindowInner* aWindow, AudioNode* aNode)
   : DOMMediaStream(aWindow, nullptr),
     mStreamNode(aNode)
 {
 }
 
 DOMAudioNodeMediaStream::~DOMAudioNodeMediaStream()
 {
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -23,17 +23,16 @@
 // See dom/media/webaudio/AudioContext.h for more fun!
 #ifdef CurrentTime
 #undef CurrentTime
 #endif
 
 namespace mozilla {
 
 class AbstractThread;
-class DOMLocalMediaStream;
 class DOMMediaStream;
 class MediaStream;
 class MediaInputPort;
 class MediaStreamGraph;
 class ProcessedMediaStream;
 
 enum class BlockingMode;
 
@@ -201,17 +200,16 @@ protected:
  *                   \                                 (pointing to t1 in A')
  *                    ----> t2 ------------> t2     <- MediaStreamTrack Z'
  *                                                     (pointing to t2 in A')
  */
 class DOMMediaStream : public DOMEventTargetHelper,
                        public dom::PrincipalChangeObserver<dom::MediaStreamTrack>,
                        public RelativeTimeline
 {
-  friend class DOMLocalMediaStream;
   friend class dom::MediaStreamTrack;
   typedef dom::MediaStreamTrack MediaStreamTrack;
   typedef dom::AudioStreamTrack AudioStreamTrack;
   typedef dom::VideoStreamTrack VideoStreamTrack;
   typedef dom::MediaStreamTrackSource MediaStreamTrackSource;
   typedef dom::AudioTrack AudioTrack;
   typedef dom::VideoTrack VideoTrack;
   typedef dom::AudioTrackList AudioTrackList;
@@ -751,59 +749,16 @@ private:
   nsCOMPtr<nsIPrincipal> mVideoPrincipal;
   nsTArray<dom::PrincipalChangeObserver<DOMMediaStream>*> mPrincipalChangeObservers;
   CORSMode mCORSMode;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(DOMMediaStream,
                               NS_DOMMEDIASTREAM_IID)
 
-#define NS_DOMLOCALMEDIASTREAM_IID \
-{ 0xb1437260, 0xec61, 0x4dfa, \
-  { 0x92, 0x54, 0x04, 0x44, 0xe2, 0xb5, 0x94, 0x9c } }
-
-class DOMLocalMediaStream : public DOMMediaStream
-{
-public:
-  explicit DOMLocalMediaStream(nsPIDOMWindowInner* aWindow,
-                               MediaStreamTrackSourceGetter* aTrackSourceGetter)
-    : DOMMediaStream(aWindow, aTrackSourceGetter) {}
-
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOMLOCALMEDIASTREAM_IID)
-
-  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
-
-  void Stop();
-
-  /**
-   * Create an nsDOMLocalMediaStream whose underlying stream is a SourceMediaStream.
-   */
-  static already_AddRefed<DOMLocalMediaStream>
-  CreateSourceStreamAsInput(nsPIDOMWindowInner* aWindow,
-                            MediaStreamGraph* aGraph,
-                            MediaStreamTrackSourceGetter* aTrackSourceGetter = nullptr);
-
-  /**
-   * Create an nsDOMLocalMediaStream whose underlying stream is a TrackUnionStream.
-   */
-  static already_AddRefed<DOMLocalMediaStream>
-  CreateTrackUnionStreamAsInput(nsPIDOMWindowInner* aWindow,
-                                MediaStreamGraph* aGraph,
-                                MediaStreamTrackSourceGetter* aTrackSourceGetter = nullptr);
-
-protected:
-  virtual ~DOMLocalMediaStream();
-
-  void StopImpl();
-};
-
-NS_DEFINE_STATIC_IID_ACCESSOR(DOMLocalMediaStream,
-                              NS_DOMLOCALMEDIASTREAM_IID)
-
 class DOMAudioNodeMediaStream : public DOMMediaStream
 {
   typedef dom::AudioNode AudioNode;
 public:
   DOMAudioNodeMediaStream(nsPIDOMWindowInner* aWindow, AudioNode* aNode);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DOMAudioNodeMediaStream, DOMMediaStream)
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1515,18 +1515,18 @@ public:
         principal = window->GetExtantDoc()->NodePrincipal();
       }
 
       // Normal case, connect the source stream to the track union stream to
       // avoid us blocking. Pass a simple TrackSourceGetter for potential
       // fake tracks. Apart from them gUM never adds tracks dynamically.
       domStream = new nsMainThreadPtrHolder<DOMMediaStream>(
         "GetUserMediaStreamRunnable::DOMMediaStreamMainThreadHolder",
-        DOMLocalMediaStream::CreateSourceStreamAsInput(window, msg,
-                                                       new FakeTrackSourceGetter(principal)));
+        DOMMediaStream::CreateSourceStreamAsInput(window, msg,
+                                                  new FakeTrackSourceGetter(principal)));
       stream = domStream->GetInputStream()->AsSourceStream();
 
       if (mAudioDevice) {
         nsString audioDeviceName;
         mAudioDevice->GetName(audioDeviceName);
         const MediaSourceEnum source = mAudioDevice->GetMediaSource();
         RefPtr<MediaStreamTrackSource> audioSource =
           new LocalTrackSource(principal, audioDeviceName, mSourceListener,
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -820,18 +820,18 @@ public:
                       MediaSegment* aRawSegment = nullptr) override {
     mMutex.AssertCurrentThreadOwns();
     MediaStream::ApplyTrackDisabling(aTrackID, aSegment, aRawSegment);
   }
 
   void RemoveAllDirectListenersImpl() override;
 
   /**
-   * End all tracks and Finish() this stream.  Used to voluntarily revoke access
-   * to a LocalMediaStream.
+   * End all tracks and Finish() this stream. Used to voluntarily revoke access
+   * to a MediaStream.
    */
   void EndAllTrackAndFinish();
 
   /**
    * Returns true if this SourceMediaStream contains at least one audio track
    * that is in pending state.
    * This is thread safe, and takes the SourceMediaStream mutex.
    */
--- a/dom/media/ReaderProxy.cpp
+++ b/dom/media/ReaderProxy.cpp
@@ -57,19 +57,18 @@ RefPtr<ReaderProxy::AudioDataPromise>
 ReaderProxy::OnAudioDataRequestCompleted(RefPtr<AudioData> aAudio)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
 
   // Subtract the start time and add the looping-offset time.
   int64_t offset =
     StartTime().ToMicroseconds() - mLoopingOffset.ToMicroseconds();
   aAudio->AdjustForStartTime(offset);
-  if (aAudio->mTime.IsValid() && aAudio->GetEndTime().IsValid() &&
-      CorrectTimeOfAudioDataIfNeeded(aAudio)) {
-    UpdateLastAudioEndTime(aAudio);
+  if (aAudio->mTime.IsValid()) {
+    mLastAudioEndTime = aAudio->mTime;
     return AudioDataPromise::CreateAndResolve(aAudio.forget(), __func__);
   }
   return AudioDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
                                            __func__);
 }
 
 RefPtr<ReaderProxy::AudioDataPromise>
 ReaderProxy::OnAudioDataRequestFailed(const MediaResult& aError)
@@ -110,38 +109,16 @@ ReaderProxy::OnAudioDataRequestFailed(co
            [self](RefPtr<AudioData> aAudio) {
              return self->OnAudioDataRequestCompleted(aAudio.forget());
            },
            [](const MediaResult& aError) {
              return AudioDataPromise::CreateAndReject(aError, __func__);
            });
 }
 
-bool
-ReaderProxy::CorrectTimeOfAudioDataIfNeeded(const RefPtr<AudioData>& aAudio)
-{
-  MOZ_ASSERT(aAudio->mTime.IsValid() && mLastAudioEndTime.IsValid());
-  // The start time of the current audio data should be greater than the end
-  // time of the previous audio data.
-  if (aAudio->mTime < mLastAudioEndTime) {
-    aAudio->mTime = mLastAudioEndTime;
-  }
-  return aAudio->GetEndTime().IsValid();
-}
-
-void
-ReaderProxy::UpdateLastAudioEndTime(const AudioData* aAudio)
-{
-  MOZ_ASSERT(aAudio);
-  MOZ_ASSERT(aAudio->GetEndTime().IsValid() && mLastAudioEndTime.IsValid());
-  // Make sure the end time of the audio data are non-decreasing.
-  MOZ_ASSERT(aAudio->GetEndTime() >= mLastAudioEndTime);
-  mLastAudioEndTime = aAudio->GetEndTime();
-}
-
 RefPtr<ReaderProxy::AudioDataPromise>
 ReaderProxy::RequestAudioData()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
 
   mSeamlessLoopingBlocked = false;
   return InvokeAsync(mReader->OwnerThread(),
--- a/dom/media/ReaderProxy.h
+++ b/dom/media/ReaderProxy.h
@@ -95,22 +95,16 @@ private:
   void UpdateDuration();
   RefPtr<SeekPromise> SeekInternal(const SeekTarget& aTarget);
 
   RefPtr<ReaderProxy::AudioDataPromise> OnAudioDataRequestCompleted(
     RefPtr<AudioData> aAudio);
   RefPtr<ReaderProxy::AudioDataPromise> OnAudioDataRequestFailed(
     const MediaResult& aError);
 
-  // Make sure the timestamp of the audio data increase monotonically by
-  // adjusting it according to mLastAudioEndTime. Returns true if the
-  // endtime is valid after correction and false otherwise.
-  bool CorrectTimeOfAudioDataIfNeeded(const RefPtr<AudioData>& aAudio);
-  void UpdateLastAudioEndTime(const AudioData* aAudio);
-
   const RefPtr<AbstractThread> mOwnerThread;
   const RefPtr<MediaFormatReader> mReader;
 
   bool mShutdown = false;
   Maybe<media::TimeUnit> mStartTime;
 
   // State-watching manager.
   WatchManager<ReaderProxy> mWatchManager;
--- a/dom/media/encoder/ContainerWriter.h
+++ b/dom/media/encoder/ContainerWriter.h
@@ -16,17 +16,17 @@ namespace mozilla {
  */
 class ContainerWriter {
 public:
   ContainerWriter()
     : mInitialized(false)
     , mIsWritingComplete(false)
   {}
   virtual ~ContainerWriter() {}
-  // Mapping to DOMLocalMediaStream::TrackTypeHints
+  // Mapping to DOMMediaStream::TrackTypeHints
   enum {
     CREATE_AUDIO_TRACK = 1 << 0,
     CREATE_VIDEO_TRACK = 1 << 1,
   };
   enum {
     END_OF_STREAM = 1 << 0
   };
 
--- a/dom/media/nsIDOMNavigatorUserMedia.idl
+++ b/dom/media/nsIDOMNavigatorUserMedia.idl
@@ -22,17 +22,17 @@ interface nsIGetUserMediaDevicesSuccessC
   void onSuccess(in nsIVariant devices);
 };
 
 [scriptable, function, uuid(f2a144fc-3534-4761-8c5d-989ae720f89a)]
 interface nsIDOMGetUserMediaSuccessCallback : nsISupports
 {
   /*
    * value must be a Blob if picture is true and a
-   * DOMLocalMediaStream if either audio or video are true.
+   * DOMMediaStream if either audio or video are true.
    */
   void onSuccess(in nsISupports value);
 };
 
 [scriptable, function, uuid(39e96c61-2636-4f0e-918e-9bb64276492a)]
 interface nsIDOMGetUserMediaErrorCallback : nsISupports
 {
   void onError(in nsISupports error);
--- a/dom/media/test/test_mediarecorder_avoid_recursion.html
+++ b/dom/media/test/test_mediarecorder_avoid_recursion.html
@@ -25,18 +25,18 @@ async function startTest() {
       ok(false, 'Unexpected onwarning callback fired');
     };
     mediaRecorder.ondataavailable = function (e) {
       ++count;
       info("got ondataavailable data size = " + e.data.size);
       // no more requestData() to prevent busy main thread from starving
       // the encoding thread
       if (count == 30) {
-        info("stream.stop");
-        stream.stop();
+        info("track.stop");
+        stream.getTracks()[0].stop();
       } else if (count < 30 && mediaRecorder.state == 'recording') {
         info("requestData again");
         mediaRecorder.requestData();
       }
     };
     mediaRecorder.onstop = function () {
       ok(true, "requestData within ondataavailable successfully avoided infinite recursion");
       SimpleTest.finish();
--- a/dom/media/test/test_mediatrack_consuming_mediastream.html
+++ b/dom/media/test/test_mediatrack_consuming_mediastream.html
@@ -126,17 +126,17 @@ async function startTest() {
       element.pause();
       checkTrackAdded();
     } else if (steps == 2) {
       element.onpause = onpause;
       element.pause();
       checkTrackChanged(1, false);
     } else if (steps == 3) {
       checkTrackChanged(2, true);
-      stream.stop();
+      stream.getTracks().forEach(t => t.stop());
     }
   }
 
   element.onplaying = onplaying;
   element.srcObject = stream;
 
   steps++;
   await element.play();
--- a/dom/media/test/test_mediatrack_events.html
+++ b/dom/media/test/test_mediatrack_events.html
@@ -100,17 +100,17 @@ async function startTest() {
     element.onplaying = null;
     if (element.ended) {
       return;
     }
     if (steps == 1) {
       element.onpause = onpause;
       element.pause();
     } else if (steps == 2) {
-      stream.stop();
+      stream.getTracks().forEach(t => t.stop());
     }
   }
 
   element.onplaying = onplaying;
   element.srcObject = stream;
 
   isnot(element.audioTracks, undefined,
         'HTMLMediaElement::AudioTracks() property should be available.');
--- a/dom/media/tests/crashtests/791330.html
+++ b/dom/media/tests/crashtests/791330.html
@@ -16,17 +16,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       pc.close();
 
       navigator.mozGetUserMedia({audio: true, fake: true}, function (stream) {
         try {
           pc.addStream(stream);
           pc.createOffer(function (offer) {});
         }
         finally {
-          stream.stop();
+          stream.getTracks().forEach(t => t.stop());
 
           finish();
         }
       }, function () {});
     }
   </script>
 </head>
 
--- a/dom/media/tests/mochitest/mediaStreamPlayback.js
+++ b/dom/media/tests/mochitest/mediaStreamPlayback.js
@@ -138,86 +138,16 @@ MediaStreamPlayback.prototype = {
    *               being played.
    */
   detachFromMediaElement : function() {
     this.mediaElement.pause();
     this.mediaElement.srcObject = null;
   }
 }
 
-
-/**
- * This class is basically the same as MediaStreamPlayback except
- * ensures that the instance provided startMedia is a MediaStream.
- *
- * @param {HTMLMediaElement} mediaElement the media element for playback
- * @param {LocalMediaStream} mediaStream the media stream used in
- *                                       the mediaElement for playback
- */
-function LocalMediaStreamPlayback(mediaElement, mediaStream) {
-  ok(mediaStream instanceof LocalMediaStream,
-     "Stream should be a LocalMediaStream");
-  MediaStreamPlayback.call(this, mediaElement, mediaStream);
-}
-
-LocalMediaStreamPlayback.prototype = Object.create(MediaStreamPlayback.prototype, {
-
-  /**
-   * DEPRECATED - MediaStream.stop() is going away. Use MediaStreamTrack.stop()!
-   *
-   * Starts media with a media stream, runs it until a canplaythrough and
-   * timeupdate event fires, and calls stop() on the stream.
-   *
-   * @param {Boolean} isResume specifies if this media element is being resumed
-   *                           from a previous run
-   */
-  playMediaWithDeprecatedStreamStop : {
-    value: function(isResume) {
-      this.startMedia(isResume);
-      return this.verifyPlaying()
-        .then(() => this.deprecatedStopStreamInMediaPlayback())
-        .then(() => this.detachFromMediaElement());
-    }
-  },
-
-  /**
-   * DEPRECATED - MediaStream.stop() is going away. Use MediaStreamTrack.stop()!
-   *
-   * Stops the local media stream while it's currently in playback in
-   * a media element.
-   *
-   * Precondition: The media stream and element should both be actively
-   *               being played.
-   *
-   */
-  deprecatedStopStreamInMediaPlayback : {
-    value: function () {
-      return new Promise((resolve, reject) => {
-        /**
-         * Callback fired when the ended event fires when stop() is called on the
-         * stream.
-         */
-        var endedCallback = () => {
-          this.mediaElement.removeEventListener('ended', endedCallback);
-          ok(true, "ended event successfully fired");
-          resolve();
-        };
-
-        this.mediaElement.addEventListener('ended', endedCallback);
-        this.mediaStream.stop();
-
-        // If ended doesn't fire in enough time, then we fail the test
-        setTimeout(() => {
-          reject(new Error("ended event never fired"));
-        }, ENDED_TIMEOUT_LENGTH);
-      });
-    }
-  }
-});
-
 // haxx to prevent SimpleTest from failing at window.onload
 function addLoadEvent() {}
 
 var scriptsReady = Promise.all([
   "/tests/SimpleTest/SimpleTest.js",
   "head.js"
 ].map(script  => {
   var el = document.createElement("script");
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -94,22 +94,16 @@ skip-if = android_version == '18' # andr
 [test_getUserMedia_mediaStreamTrackClone.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_getUserMedia_playAudioTwice.html]
 [test_getUserMedia_playVideoAudioTwice.html]
 [test_getUserMedia_playVideoTwice.html]
 [test_getUserMedia_scarySources.html]
 skip-if = toolkit == 'android' # no screenshare or windowshare on android
 [test_getUserMedia_spinEventLoop.html]
-[test_getUserMedia_stopAudioStream.html]
-[test_getUserMedia_stopAudioStreamWithFollowupAudio.html]
-[test_getUserMedia_stopVideoAudioStream.html]
-[test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html]
-[test_getUserMedia_stopVideoStream.html]
-[test_getUserMedia_stopVideoStreamWithFollowupVideo.html]
 [test_getUserMedia_trackCloneCleanup.html]
 [test_getUserMedia_trackEnded.html]
 [test_getUserMedia_peerIdentity.html]
 [test_peerConnection_addIceCandidate.html]
 [test_peerConnection_addtrack_removetrack_events.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_audioCodecs.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
--- a/dom/media/tests/mochitest/test_getUserMedia_addTrackRemoveTrack.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_addTrackRemoveTrack.html
@@ -22,33 +22,33 @@
 
         stream.addTrack(track);
         checkMediaStreamContains(stream, [track], "Re-added audio");
 
         stream.addTrack(otherTrack);
         checkMediaStreamContains(stream, [track, otherTrack], "Added video");
 
         var testElem = createMediaElement('video', 'testAddTrackAudioVideo');
-        var playback = new LocalMediaStreamPlayback(testElem, stream);
+        var playback = new MediaStreamPlayback(testElem, stream);
         return playback.playMedia(false);
     }))
     .then(() => getUserMedia({video: true})).then(stream =>
       getUserMedia({video: true}).then(otherStream => {
         info("Test addTrack()ing a video track to a video-only gUM stream");
         var track = stream.getTracks()[0];
         var otherTrack = otherStream.getTracks()[0];
 
         stream.addTrack(track);
         checkMediaStreamContains(stream, [track], "Re-added video");
 
         stream.addTrack(otherTrack);
         checkMediaStreamContains(stream, [track, otherTrack], "Added video");
 
         var test = createMediaElement('video', 'testAddTrackDoubleVideo');
-        var playback = new LocalMediaStreamPlayback(test, stream);
+        var playback = new MediaStreamPlayback(test, stream);
         return playback.playMedia(false);
     }))
     .then(() => getUserMedia({video: true})).then(stream =>
       getUserMedia({video: true}).then(otherStream => {
         info("Test removeTrack() existing and added video tracks from a video-only gUM stream");
         var track = stream.getTracks()[0];
         var otherTrack = otherStream.getTracks()[0];
 
@@ -75,17 +75,17 @@
         return wait(500).then(() => {
           ok(!loadeddata, "Stream without tracks shall not raise 'loadeddata' on media element");
           elem.pause();
           elem.srcObject = null;
         })
         .then(() => {
           stream.addTrack(track);
           checkMediaStreamContains(stream, [track], "Re-added added-then-removed track");
-          var playback = new LocalMediaStreamPlayback(elem, stream);
+          var playback = new MediaStreamPlayback(elem, stream);
           return playback.playMedia(false);
         })
         .then(() => otherTrack.stop());
     }))
     .then(() => getUserMedia({ audio: true })).then(audioStream =>
       getUserMedia({ video: true }).then(videoStream => {
         info("Test adding track and removing the original");
         var audioTrack = audioStream.getTracks()[0];
@@ -93,17 +93,17 @@
         videoStream.removeTrack(videoTrack);
         audioStream.addTrack(videoTrack);
 
         checkMediaStreamContains(videoStream, [], "1, Removed original track");
         checkMediaStreamContains(audioStream, [audioTrack, videoTrack],
                                  "2, Added external track");
 
         var elem = createMediaElement('video', 'testAddRemoveOriginalTrackVideo');
-        var playback = new LocalMediaStreamPlayback(elem, audioStream);
+        var playback = new MediaStreamPlayback(elem, audioStream);
         return playback.playMedia(false);
       }))
     .then(() => getUserMedia({ audio: true, video: true })).then(stream => {
       info("Test removing stopped tracks");
       stream.getTracks().forEach(t => {
         t.stop();
         stream.removeTrack(t);
       });
--- a/dom/media/tests/mochitest/test_getUserMedia_audioConstraints_concurrentIframes.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_audioConstraints_concurrentIframes.html
@@ -129,17 +129,17 @@ runTest(async function() {
     }
 
     // We do not currently have tests to verify the behaviour of the different
     // constraints. Once we do we should do further verification here. See
     // bug 1406372, bug 1406376, and bug 1406377.
 
     for (let testCase of testCases) {
       let testAudio = createMediaElement("audio", `testAudio.${testCase.id}`);
-      let playback = new LocalMediaStreamPlayback(testAudio, testCase.gumStream);
+      let playback = new MediaStreamPlayback(testAudio, testCase.gumStream);
       await playback.playMediaWithoutStoppingTracks(false);
     }
 
     // Stop the tracks for each stream, we left them running above via
     // playMediaWithoutStoppingTracks to make sure they can play concurrently.
     for (let testCase of testCases) {
       testCase.gumStream.getTracks().map(t => t.stop());
     }
--- a/dom/media/tests/mochitest/test_getUserMedia_audioConstraints_concurrentStreams.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_audioConstraints_concurrentStreams.html
@@ -102,17 +102,17 @@ runTest(async function() {
   }
 
   // We do not currently have tests to verify the behaviour of the different
   // constraints. Once we do we should do further verificaiton here. See
   // bug 1406372, bug 1406376, and bug 1406377.
 
   for (let testCase of testCases) {
     let testAudio = createMediaElement("audio", `testAudio.${testCase.id}`);
-    let playback = new LocalMediaStreamPlayback(testAudio, testCase.gumStream);
+    let playback = new MediaStreamPlayback(testAudio, testCase.gumStream);
     await playback.playMediaWithoutStoppingTracks(false);
   }
 
   // Stop the tracks for each stream, we left them running above via
   // playMediaWithoutStoppingTracks to make sure they can play concurrently.
   for (let testCase of testCases) {
     testCase.gumStream.getTracks().map(t => t.stop());
   }
--- a/dom/media/tests/mochitest/test_getUserMedia_basicAudio.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_basicAudio.html
@@ -4,24 +4,24 @@
   <script type="application/javascript" src="mediaStreamPlayback.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({ title: "getUserMedia Basic Audio Test", bug: "781534" });
   /**
    * Run a test to verify that we can complete a start and stop media playback
-   * cycle for an audio LocalMediaStream on an audio HTMLMediaElement.
+   * cycle for an audio MediaStream on an audio HTMLMediaElement.
    */
   runTest(function () {
     var testAudio = createMediaElement('audio', 'testAudio');
     var constraints = {audio: true};
 
     return getUserMedia(constraints).then(stream => {
-      var playback = new LocalMediaStreamPlayback(testAudio, stream);
+      var playback = new MediaStreamPlayback(testAudio, stream);
       return playback.playMedia(false);
     });
   });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_getUserMedia_basicScreenshare.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_basicScreenshare.html
@@ -101,17 +101,17 @@
     let stream = await getUserMedia({video: {mediaSource: "screen"}});
     let settings = stream.getTracks()[0].getSettings();
     ok(settings.width <= 8192,
        `Width setting ${settings.width} should be set after gUM (or 0 per bug 1453247)`);
     ok(settings.height <= 8192,
        `Height setting ${settings.height} should be set after gUM (or 0 per bug 1453247)`);
     draw(helper.red, helper.blue,
          helper.green, helper.grey);
-    let playback = new LocalMediaStreamPlayback(testVideo, stream);
+    let playback = new MediaStreamPlayback(testVideo, stream);
     playback.startMedia();
     await playback.verifyPlaying();
     settings = stream.getTracks()[0].getSettings();
     is(settings.width, testVideo.videoWidth,
        "Width setting should match video width");
     is(settings.height, testVideo.videoHeight,
        "Height setting should match video height");
     let screenWidth = testVideo.videoWidth;
@@ -144,17 +144,17 @@
     });
     settings = stream.getTracks()[0].getSettings();
     ok(settings.width == 0 || (settings.width >= 10 && settings.width <= 100),
        `Width setting ${settings.width} should be correct after gUM (or 0 per bug 1453247)`);
     ok(settings.height == 0 || (settings.height >= 10 && settings.height <= 100),
        `Height setting ${settings.height} should be correct after gUM (or 0 per bug 1453247)`);
     draw(helper.green, helper.red,
          helper.grey, helper.blue);
-    playback = new LocalMediaStreamPlayback(testVideo, stream);
+    playback = new MediaStreamPlayback(testVideo, stream);
     playback.startMedia();
     await playback.verifyPlaying();
     settings = stream.getTracks()[0].getSettings();
     ok(settings.width >= 10 && settings.width <= 100,
        `Width setting ${settings.width} should be within constraints`);
     ok(settings.height >= 10 && settings.height <= 100,
        `Height setting ${settings.height} should be within constraints`);
     is(settings.width, testVideo.videoWidth,
@@ -201,17 +201,17 @@
     ok(Math.abs(expectedHeight - settings.height) <= 1,
        "Aspect ratio after applying constraints should be close enough");
     draw(helper.grey, helper.green,
          helper.blue, helper.red);
     await playback.verifyPlaying(); // still playing
     await verifyScreenshare(testVideo, helper,
                             helper.grey, helper.green,
                             helper.blue, helper.red);
-    await playback.deprecatedStopStreamInMediaPlayback();
+    await playback.stopTracksForStreamInMediaPlayback();
     playback.detachFromMediaElement();
 
     SpecialPowers.wrap(document).exitFullscreen();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_getUserMedia_basicTabshare.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_basicTabshare.html
@@ -7,60 +7,60 @@
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     title: "getUserMedia Basic Tabshare Test",
     bug: "1193075"
   });
   /**
    * Run a test to verify that we can complete a start and stop media playback
-   * cycle for a tabshare LocalMediaStream on a video HTMLMediaElement.
+   * cycle for a tabshare MediaStream on a video HTMLMediaElement.
    *
    * Additionally, exercise applyConstraints code for tabshare viewport offset.
    */
   runTest(function () {
     var testVideo = createMediaElement('video', 'testVideo');
 
     return Promise.resolve()
       .then(() => pushPrefs(["media.getusermedia.browser.enabled", true]))
       .then(() => getUserMedia({
         video: { mediaSource: "browser",
                  scrollWithPage: true },
         fake: false
       }))
       .then(stream => {
-        var playback = new LocalMediaStreamPlayback(testVideo, stream);
-        return playback.playMediaWithDeprecatedStreamStop(false);
+        var playback = new MediaStreamPlayback(testVideo, stream);
+        return playback.playMedia(false);
       })
       .then(() => getUserMedia({
         video: {
           mediaSource: "browser",
           viewportOffsetX: 0,
           viewportOffsetY: 0,
           viewportWidth: 100,
           viewportHeight: 100
         },
         fake: false
       }))
       .then(stream => {
-        var playback = new LocalMediaStreamPlayback(testVideo, stream);
+        var playback = new MediaStreamPlayback(testVideo, stream);
         playback.startMedia(false);
         return playback.verifyPlaying()
           .then(() => Promise.all([
             () => testVideo.srcObject.getVideoTracks()[0].applyConstraints({
               mediaSource: "browser",
               viewportOffsetX: 10,
               viewportOffsetY: 50,
               viewportWidth: 90,
               viewportHeight: 50
             }),
             () => listenUntil(testVideo, "resize", () => true)
           ]))
           .then(() => playback.verifyPlaying()) // still playing
-          .then(() => playback.deprecatedStopStreamInMediaPlayback())
+          .then(() => playback.stopTracksForStreamInMediaPlayback())
           .then(() => playback.detachFromMediaElement());
       });
   });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_getUserMedia_basicVideo.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_basicVideo.html
@@ -7,24 +7,24 @@
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     title: "getUserMedia Basic Video Test",
     bug: "781534"
   });
   /**
    * Run a test to verify that we can complete a start and stop media playback
-   * cycle for an video LocalMediaStream on a video HTMLMediaElement.
+   * cycle for an video MediaStream on a video HTMLMediaElement.
    */
   runTest(function () {
     var testVideo = createMediaElement('video', 'testVideo');
     var constraints = {video: true};
 
     return getUserMedia(constraints).then(stream => {
-      var playback = new LocalMediaStreamPlayback(testVideo, stream);
+      var playback = new MediaStreamPlayback(testVideo, stream);
       return playback.playMedia(false);
     });
   });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_getUserMedia_basicVideoAudio.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_basicVideoAudio.html
@@ -7,24 +7,24 @@
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     title: "getUserMedia Basic Video & Audio Test",
     bug: "781534"
   });
   /**
    * Run a test to verify that we can complete a start and stop media playback
-   * cycle for a video and audio LocalMediaStream on a video HTMLMediaElement.
+   * cycle for a video and audio MediaStream on a video HTMLMediaElement.
    */
   runTest(function () {
     var testVideoAudio = createMediaElement('video', 'testVideoAudio');
     var constraints = {video: true, audio: true};
 
     return getUserMedia(constraints).then(stream => {
-      var playback = new LocalMediaStreamPlayback(testVideoAudio, stream);
+      var playback = new MediaStreamPlayback(testVideoAudio, stream);
       return playback.playMedia(false);
     });
   });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html
@@ -14,17 +14,17 @@
    * Run a test to verify that we will always get 'loadedmetadata' from a video
    * HTMLMediaElement playing a gUM MediaStream.
    */
   runTest(() => {
     var testVideo = createMediaElement('video', 'testVideo');
     var constraints = {video: true};
 
     return getUserMedia(constraints).then(stream => {
-      var playback = new LocalMediaStreamPlayback(testVideo, stream);
+      var playback = new MediaStreamPlayback(testVideo, stream);
       var video = playback.mediaElement;
 
       video.srcObject = stream;
       return new Promise(resolve => {
         ok(playback.mediaElement.paused,
            "Media element should be paused before play()ing");
         video.addEventListener('loadedmetadata', function() {
           ok(video.videoWidth > 0, "Expected nonzero video width");
--- a/dom/media/tests/mochitest/test_getUserMedia_basicWindowshare.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_basicWindowshare.html
@@ -7,31 +7,31 @@
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     title: "getUserMedia Basic Windowshare Test",
     bug: "1038926"
   });
   /**
    * Run a test to verify that we can complete a start and stop media playback
-   * cycle for an screenshare LocalMediaStream on a video HTMLMediaElement.
+   * cycle for an screenshare MediaStream on a video HTMLMediaElement.
    */
   runTest(function () {
     var testVideo = createMediaElement('video', 'testVideo');
     var constraints = {
       video: {
          mozMediaSource: "window",
          mediaSource: "window"
       },
       fake: false
     };
 
     return getUserMedia(constraints).then(stream => {
-      var playback = new LocalMediaStreamPlayback(testVideo, stream);
-      return playback.playMediaWithDeprecatedStreamStop(false);
+      var playback = new MediaStreamPlayback(testVideo, stream);
+      return playback.playMedia(false);
     });
 
   });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_getUserMedia_callbacks.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_callbacks.html
@@ -17,17 +17,17 @@
     var testAudio = createMediaElement('audio', 'testAudio');
     var constraints = {audio: true};
 
     SimpleTest.waitForExplicitFinish();
     return new Promise(resolve =>
       navigator.mozGetUserMedia(constraints, stream => {
         checkMediaStreamTracks(constraints, stream);
 
-        var playback = new LocalMediaStreamPlayback(testAudio, stream);
+        var playback = new MediaStreamPlayback(testAudio, stream);
         return playback.playMedia(false)
           .then(() => resolve(), generateErrorCallback());
       }, generateErrorCallback())
     );
   });
 
 </script>
 </pre>
--- a/dom/media/tests/mochitest/test_getUserMedia_cubebDisabledFakeStreams.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_cubebDisabledFakeStreams.html
@@ -27,17 +27,17 @@
     try {
       stream = await getUserMedia(constraints);
     } catch (e) {
       // We've got no audio backend, so we expect gUM to fail
       ok(false, `Did not expect to fail, but got ${e}`);
       return;
     }
     ok(stream, "getUserMedia should get a stream!");
-    let playback = new LocalMediaStreamPlayback(testAudio, stream);
+    let playback = new MediaStreamPlayback(testAudio, stream);
     return playback.playMedia(false);
   });
 
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_getUserMedia_gumWithinGum.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_gumWithinGum.html
@@ -11,29 +11,28 @@
    * Run a test that we can complete a playback cycle for a video,
    * then upon completion, do a playback cycle with audio, such that
    * the audio gum call happens within the video gum call.
    */
   runTest(function () {
     return getUserMedia({video: true})
       .then(videoStream => {
         var testVideo = createMediaElement('video', 'testVideo');
-        var videoPlayback = new LocalMediaStreamPlayback(testVideo,
-                                                         videoStream);
+        var videoPlayback = new MediaStreamPlayback(testVideo,
+                                                    videoStream);
 
-        return videoPlayback.playMedia(false)
+        return videoPlayback.playMediaWithoutStoppingTracks(false)
           .then(() => getUserMedia({audio: true}))
           .then(audioStream => {
             var testAudio = createMediaElement('audio', 'testAudio');
-            var audioPlayback = new LocalMediaStreamPlayback(testAudio,
-                                                             audioStream);
+            var audioPlayback = new MediaStreamPlayback(testAudio,
+                                                        audioStream);
 
-            return audioPlayback.playMedia(false)
-              .then(() => audioStream.stop());
+            return audioPlayback.playMedia(false);
           })
-          .then(() => videoStream.stop());
+          .then(() => videoStream.getTracks().forEach(t => t.stop()));
       });
   });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_tracks.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaElementCapture_tracks.html
@@ -152,18 +152,16 @@ runTest(() => getUserMedia({audio: true,
     streamUntilEnded = untilEndedElement.mozCaptureStreamUntilEnded();
 
     is(streamUntilEnded.getAudioTracks().length, 3,
        "video element should capture all 3 audio tracks until ended");
     is(streamUntilEnded.getVideoTracks().length, 1,
        "video element should capture only 1 video track until ended");
 
     untilEndedElement.srcObject.getTracks().forEach(t => t.stop());
-    // TODO(1208316) We stop the stream to make the media element end.
-    untilEndedElement.srcObject.stop();
 
     return Promise.all([
       haveEvent(untilEndedElement, "ended", wait(50000, new Error("Timeout"))),
       ...streamUntilEnded.getTracks()
            .map(t => haveEvent(t, "ended", wait(50000, new Error("Timeout"))))
     ]);
   })
   .then(() => {
--- a/dom/media/tests/mochitest/test_getUserMedia_mediaStreamClone.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_mediaStreamClone.html
@@ -64,17 +64,16 @@ runTest(async () => {
     streamClone.addTrack(track);
     checkMediaStreamContains(streamClone, [trackClone, track],
                              "Added video to clone");
     checkMediaStreamContains(stream, [otherTrack],
                              "Original not affected");
 
     // Not part of streamClone. Does not get stopped by the playback test.
     otherTrack.stop();
-    otherStream.stop();
 
     let test = createMediaElement('video', 'testClonePlayback');
     let playback = new MediaStreamPlayback(test, streamClone);
     await playback.playMedia(false);
   }
 
   {
     info("Test cloning a stream into inception");
--- a/dom/media/tests/mochitest/test_getUserMedia_playAudioTwice.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_playAudioTwice.html
@@ -8,17 +8,17 @@
 <script type="application/javascript">
   createHTML({title: "getUserMedia Play Audio Twice", bug: "822109" });
   /**
    * Run a test that we can complete an audio playback cycle twice in a row.
    */
   runTest(function () {
     return getUserMedia({audio: true}).then(audioStream => {
       var testAudio = createMediaElement('audio', 'testAudio');
-      var playback = new LocalMediaStreamPlayback(testAudio, audioStream);
+      var playback = new MediaStreamPlayback(testAudio, audioStream);
 
       return playback.playMediaWithoutStoppingTracks(false)
         .then(() => playback.playMedia(true));
     });
   });
 </script>
 </pre>
 </body>
--- a/dom/media/tests/mochitest/test_getUserMedia_playVideoAudioTwice.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_playVideoAudioTwice.html
@@ -8,17 +8,17 @@
 <script type="application/javascript">
   createHTML({title: "getUserMedia Play Video and Audio Twice", bug: "822109" });
   /**
    * Run a test that we can complete a video playback cycle twice in a row.
    */
   runTest(function () {
     return getUserMedia({video: true, audio: true}).then(stream => {
       var testVideo = createMediaElement('video', 'testVideo');
-      var playback = new LocalMediaStreamPlayback(testVideo, stream);
+      var playback = new MediaStreamPlayback(testVideo, stream);
 
       return playback.playMediaWithoutStoppingTracks(false)
         .then(() => playback.playMedia(true));
     });
   });
 
 </script>
 </pre>
--- a/dom/media/tests/mochitest/test_getUserMedia_playVideoTwice.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_playVideoTwice.html
@@ -8,17 +8,17 @@
 <script type="application/javascript">
   createHTML({ title: "getUserMedia Play Video Twice", bug: "822109" });
   /**
    * Run a test that we can complete a video playback cycle twice in a row.
    */
   runTest(function () {
     return getUserMedia({video: true}).then(stream => {
       var testVideo = createMediaElement('video', 'testVideo');
-      var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream);
+      var streamPlayback = new MediaStreamPlayback(testVideo, stream);
 
       return streamPlayback.playMediaWithoutStoppingTracks(false)
         .then(() => streamPlayback.playMedia(true));
     });
   });
 
 </script>
 </pre>
deleted file mode 100644
--- a/dom/media/tests/mochitest/test_getUserMedia_stopAudioStream.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
-</head>
-<body>
-<pre id="test">
-<script type="application/javascript">
-  createHTML({ title: "getUserMedia Stop Audio Stream", bug: "822109" });
-  /**
-   * Run a test to verify that we can start an audio stream in a media element,
-   * call stop() on the stream, and successfully get an ended event fired.
-   */
-  runTest(function () {
-    return getUserMedia({audio: true})
-      .then(stream => {
-        var testAudio = createMediaElement('audio', 'testAudio');
-        var streamPlayback = new LocalMediaStreamPlayback(testAudio, stream);
-
-        return streamPlayback.playMediaWithDeprecatedStreamStop(false);
-      });
-  });
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/tests/mochitest/test_getUserMedia_stopAudioStreamWithFollowupAudio.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
-</head>
-<body>
-<pre id="test">
-<script type="application/javascript">
-  createHTML({ title: "getUserMedia Stop Audio Stream With Followup Audio", bug: "822109" });
-  /**
-   * Run a test to verify that I can complete an audio gum playback in a media
-   * element, stop the stream, and then complete another audio gum playback
-   * in a media element.
-   */
-  runTest(function () {
-    return getUserMedia({audio: true})
-      .then(firstStream => {
-        var testAudio = createMediaElement('audio', 'testAudio');
-        var streamPlayback = new LocalMediaStreamPlayback(testAudio, firstStream);
-
-        return streamPlayback.playMediaWithDeprecatedStreamStop(false)
-          .then(() => getUserMedia({audio: true}))
-          .then(secondStream => {
-            streamPlayback.mediaStream = secondStream;
-
-            return streamPlayback.playMedia(false)
-              .then(() => secondStream.stop());
-          });
-      });
-  });
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStream.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
-</head>
-<body>
-<pre id="test">
-<script type="application/javascript">
-  createHTML({ title: "getUserMedia Stop Video Audio Stream", bug: "822109" });
-  /**
-   * Run a test to verify that we can start a video+audio stream in a
-   * media element, call stop() on the stream, and successfully get an
-   * ended event fired.
-   */
-  runTest(function () {
-    return getUserMedia({video: true, audio: true})
-      .then(stream => {
-        var testVideo = createMediaElement('video', 'testVideo');
-        var playback = new LocalMediaStreamPlayback(testVideo, stream);
-
-        return playback.playMediaWithDeprecatedStreamStop(false);
-      });
-  });
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
-</head>
-<body>
-<pre id="test">
-<script type="application/javascript">
-  createHTML({
-    title: "getUserMedia Stop Video+Audio Stream With Followup Video+Audio",
-    bug: "822109"
-  });
-  /**
-   * Run a test to verify that I can complete an video+audio gum playback in a
-   * media element, stop the stream, and then complete another video+audio gum
-   * playback in a media element.
-   */
-  runTest(function () {
-    return getUserMedia({video: true, audio: true})
-      .then(stream => {
-        var testVideo = createMediaElement('video', 'testVideo');
-        var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream);
-
-        return streamPlayback.playMediaWithDeprecatedStreamStop(false)
-          .then(() => getUserMedia({video: true, audio: true}))
-          .then(secondStream => {
-            streamPlayback.mediaStream = secondStream;
-
-            return streamPlayback.playMedia(false)
-              .then(() => secondStream.stop());
-          });
-      });
-  });
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/tests/mochitest/test_getUserMedia_stopVideoStream.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
-</head>
-<body>
-<pre id="test">
-<script type="application/javascript">
-  createHTML({ title: "getUserMedia Stop Video Stream", bug: "822109" });
-  /**
-   * Run a test to verify that we can start a video stream in a
-   * media element, call stop() on the stream, and successfully get an
-   * ended event fired.
-   */
-  runTest(function () {
-    return getUserMedia({video: true})
-      .then(stream => {
-        var testVideo = createMediaElement('video', 'testVideo');
-        var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream);
-
-        return streamPlayback.playMediaWithDeprecatedStreamStop(false);
-      });
-  });
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/tests/mochitest/test_getUserMedia_stopVideoStreamWithFollowupVideo.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <script src="mediaStreamPlayback.js"></script>
-</head>
-<body>
-<pre id="test">
-<script type="application/javascript">
-  createHTML({ title: "getUserMedia Stop Video Stream With Followup Video", bug: "822109" });
-  /**
-   * Run a test to verify that I can complete an video gum playback in a
-   * media element, stop the stream, and then complete another video gum
-   * playback in a media element.
-   */
-  runTest(function () {
-    return getUserMedia({video: true})
-      .then(stream => {
-        var testVideo = createMediaElement('video', 'testVideo');
-        var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream);
-
-        return streamPlayback.playMediaWithDeprecatedStreamStop(false)
-          .then(() => getUserMedia({video: true}))
-          .then(secondStream => {
-            streamPlayback.mediaStream = secondStream;
-
-            return streamPlayback.playMedia(false)
-              .then(() => secondStream.stop());
-          });
-      });
-  });
-
-</script>
-</pre>
-</body>
-</html>
--- a/dom/security/test/cors/file_CrossSiteXHR_cache_server.sjs
+++ b/dom/security/test/cors/file_CrossSiteXHR_cache_server.sjs
@@ -1,8 +1,11 @@
+Cu.import("resource://gre/modules/Services.jsm");
+Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
+
 function handleRequest(request, response)
 {
   var query = {};
   request.queryString.split('&').forEach(function (val) {
     var [name, value] = val.split('=');
     query[name] = unescape(value);
   });
 
--- a/dom/security/test/cors/file_CrossSiteXHR_server.sjs
+++ b/dom/security/test/cors/file_CrossSiteXHR_server.sjs
@@ -1,12 +1,14 @@
 const CC = Components.Constructor;
 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
                              "nsIBinaryInputStream",
                              "setInputStream");
+Cu.import("resource://gre/modules/Services.jsm");
+Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
 
 function handleRequest(request, response)
 {
   var query = {};
   request.queryString.split('&').forEach(function (val) {
     var [name, value] = val.split('=');
     query[name] = unescape(value);
   });
--- a/dom/security/test/cors/test_CrossSiteXHR.html
+++ b/dom/security/test/cors/test_CrossSiteXHR.html
@@ -20,17 +20,19 @@ const runPreflightTests = 1;
 const runCookieTests = 1;
 const runRedirectTests = 1;
 
 var gen;
 
 function initTest() {
   SimpleTest.waitForExplicitFinish();
   // Allow all cookies, then do the actual test initialization
-  SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]}, initTestCallback);
+  SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0],
+  																	 ["security.allow_eval_with_system_principal", true]]},
+  																	  initTestCallback);
 }
 
 function initTestCallback() {
   window.addEventListener("message", function(e) {
     gen.next(e.data);
   });
 
   gen = runTest();
--- a/dom/security/test/cors/test_CrossSiteXHR_cache.html
+++ b/dom/security/test/cors/test_CrossSiteXHR_cache.html
@@ -11,19 +11,23 @@
 <iframe id=loader></iframe>
 </p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript">
 
+let gen;
 SimpleTest.waitForExplicitFinish();
 SimpleTest.requestFlakyTimeout("This test needs to generate artificial pauses, hence it uses timeouts.  There is no way around it, unfortunately. :(");
 
+SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+                                     true]]});
+
 window.addEventListener("message", function(e) {
   gen.next(e.data);
 });
 
 gen = runTest();
 
 function* runTest() {
   var loader = document.getElementById('loader');
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -602,18 +602,16 @@ var interfaceNamesInGlobalScope =
     {name: "IntersectionObserverEntry", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "KeyEvent", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "KeyboardEvent", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "KeyframeEffect", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "LocalMediaStream", insecureContext: true},
-// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Location", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MediaCapabilities", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MediaCapabilitiesInfo", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MediaDeviceInfo", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
deleted file mode 100644
--- a/dom/webidl/LocalMediaStream.webidl
+++ /dev/null
@@ -1,15 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/.
- *
- * The origins of this IDL file are
- * http://dev.w3.org/2011/webrtc/editor/getusermedia.html
- *
- * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
- * liability, trademark and document use rules apply.
- */
-
-interface LocalMediaStream : MediaStream {
-    void stop();
-};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -167,19 +167,16 @@ with Files("Key*Event*"):
     BUG_COMPONENT = ("Core", "DOM: Events")
 
 with Files("KeyIdsInitData.webidl"):
     BUG_COMPONENT = ("Core", "Audio/Video: Playback")
 
 with Files("Keyframe*"):
     BUG_COMPONENT = ("Core", "DOM: Animation")
 
-with Files("LocalMediaStream.webidl"):
-    BUG_COMPONENT = ("Core", "Audio/Video")
-
 with Files("MediaDevice*"):
     BUG_COMPONENT = ("Core", "WebRTC")
 
 with Files("Media*Source*"):
     BUG_COMPONENT = ("Core", "Web Audio")
 
 with Files("MediaStream*"):
     BUG_COMPONENT = ("Core", "WebRTC")
@@ -633,17 +630,16 @@ WEBIDL_FILES = [
     'KeyboardEvent.webidl',
     'KeyEvent.webidl',
     'KeyframeAnimationOptions.webidl',
     'KeyframeEffect.webidl',
     'KeyIdsInitData.webidl',
     'L10nUtils.webidl',
     'LegacyQueryInterface.webidl',
     'LinkStyle.webidl',
-    'LocalMediaStream.webidl',
     'Location.webidl',
     'MediaCapabilities.webidl',
     'MediaDeviceInfo.webidl',
     'MediaDevices.webidl',
     'MediaElementAudioSourceNode.webidl',
     'MediaEncryptedEvent.webidl',
     'MediaError.webidl',
     'MediaKeyError.webidl',
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -17,16 +17,19 @@
 #include "Layers.h"                     // for Layer, etc
 #include "mozilla/dom/MouseEventBinding.h" // for MouseEvent constants
 #include "mozilla/dom/TabParent.h"      // for AreRecordReplayTabsActive
 #include "mozilla/dom/Touch.h"          // for Touch
 #include "mozilla/gfx/gfxVars.h"        // for gfxVars
 #include "mozilla/gfx/GPUParent.h"      // for GPUParent
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "mozilla/gfx/Point.h"          // for Point
+#ifdef MOZ_WIDGET_ANDROID
+#include "mozilla/jni/Utils.h"          // for jni::IsFennec
+#endif
 #include "mozilla/layers/APZSampler.h"  // for APZSampler
 #include "mozilla/layers/APZThreadUtils.h"  // for AssertOnControllerThread, etc
 #include "mozilla/layers/APZUpdater.h"  // for APZUpdater
 #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform
 #include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics
 #include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
 #include "mozilla/layers/LayerMetricsWrapper.h"
 #include "mozilla/layers/WebRenderScrollDataWrapper.h"
@@ -257,17 +260,19 @@ APZCTreeManager::APZCTreeManager(LayersI
   RefPtr<APZCTreeManager> self(this);
   NS_DispatchToMainThread(
     NS_NewRunnableFunction("layers::APZCTreeManager::APZCTreeManager", [self] {
       self->mFlushObserver = new CheckerboardFlushObserver(self);
     }));
   AsyncPanZoomController::InitializeGlobalState();
   mApzcTreeLog.ConditionOnPrefFunction(gfxPrefs::APZPrintTree);
 #if defined(MOZ_WIDGET_ANDROID)
-  mToolbarAnimator = new AndroidDynamicToolbarAnimator(this);
+  if (jni::IsFennec()) {
+    mToolbarAnimator = new AndroidDynamicToolbarAnimator(this);
+  }
 #endif // (MOZ_WIDGET_ANDROID)
 }
 
 APZCTreeManager::~APZCTreeManager() = default;
 
 void
 APZCTreeManager::SetSampler(APZSampler* aSampler)
 {
@@ -1157,32 +1162,33 @@ APZCTreeManager::ReceiveInputEvent(Input
   if (dom::TabParent::AreRecordReplayTabsActive()) {
     return nsEventStatus_eIgnore;
   }
 
   // Use a RAII class for updating the focus sequence number of this event
   AutoFocusSequenceNumberSetter focusSetter(mFocusState, aEvent);
 
 #if defined(MOZ_WIDGET_ANDROID)
-  MOZ_ASSERT(mToolbarAnimator);
-  ScreenPoint scrollOffset;
-  {
-    RecursiveMutexAutoLock lock(mTreeLock);
-    RefPtr<AsyncPanZoomController> apzc = FindRootContentOrRootApzc();
-    if (apzc) {
-      scrollOffset = ViewAs<ScreenPixel>(apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting),
-                                         PixelCastJustification::ScreenIsParentLayerForRoot);
+  if (mToolbarAnimator) {
+    ScreenPoint scrollOffset;
+    {
+      RecursiveMutexAutoLock lock(mTreeLock);
+      RefPtr<AsyncPanZoomController> apzc = FindRootContentOrRootApzc();
+      if (apzc) {
+        scrollOffset = ViewAs<ScreenPixel>(apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting),
+                                           PixelCastJustification::ScreenIsParentLayerForRoot);
+      }
     }
-  }
-  RefPtr<APZCTreeManager> self = this;
-  nsEventStatus isConsumed = mToolbarAnimator->ReceiveInputEvent(self, aEvent, scrollOffset);
-  // Check if the mToolbarAnimator consumed the event.
-  if (isConsumed == nsEventStatus_eConsumeNoDefault) {
-    APZCTM_LOG("Dynamic toolbar consumed event");
-    return isConsumed;
+    RefPtr<APZCTreeManager> self = this;
+    nsEventStatus isConsumed = mToolbarAnimator->ReceiveInputEvent(self, aEvent, scrollOffset);
+    // Check if the mToolbarAnimator consumed the event.
+    if (isConsumed == nsEventStatus_eConsumeNoDefault) {
+      APZCTM_LOG("Dynamic toolbar consumed event");
+      return isConsumed;
+    }
   }
 #endif // (MOZ_WIDGET_ANDROID)
 
   // Initialize aOutInputBlockId to a sane value, and then later we overwrite
   // it if the input event goes into a block.
   if (aOutInputBlockId) {
     *aOutInputBlockId = InputBlockState::NO_BLOCK_ID;
   }
@@ -2184,17 +2190,19 @@ APZCTreeManager::AdjustScrollForSurfaceS
 }
 
 void
 APZCTreeManager::ClearTree()
 {
   AssertOnUpdaterThread();
 
 #if defined(MOZ_WIDGET_ANDROID)
-  mToolbarAnimator->ClearTreeManager();
+  if (mToolbarAnimator) {
+    mToolbarAnimator->ClearTreeManager();
+  }
 #endif
 
   // Ensure that no references to APZCs are alive in any lingering input
   // blocks. This breaks cycles from InputBlockState::mTargetApzc back to
   // the InputQueue.
   APZThreadUtils::RunOnControllerThread(NewRunnableMethod(
     "layers::InputQueue::Clear", mInputQueue, &InputQueue::Clear));
 
--- a/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
@@ -61,17 +61,16 @@ AndroidDynamicToolbarAnimator::AndroidDy
   , mControllerRootScrollY(0.0f)
   , mControllerLastDragDirection(0)
   , mControllerTouchCount(0)
   , mControllerLastEventTimeStamp(0)
   , mControllerState(eNothingPending)
   // Compositor thread only
   , mCompositorShutdown(false)
   , mCompositorAnimationDeferred(false)
-  , mCompositorLayersUpdateEnabled(false)
   , mCompositorAnimationStarted(false)
   , mCompositorReceivedFirstPaint(false)
   , mCompositorWaitForPageResize(false)
   , mCompositorToolbarShowRequested(false)
   , mCompositorSendResponseForSnapshotUpdate(false)
   , mCompositorAnimationStyle(eAnimate)
   , mCompositorMaxToolbarHeight(0)
   , mCompositorToolbarHeight(0)
@@ -438,17 +437,16 @@ AndroidDynamicToolbarAnimator::UpdateAni
   return continueAnimating;
 }
 
 void
 AndroidDynamicToolbarAnimator::FirstPaint()
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   mCompositorReceivedFirstPaint = true;
-  PostMessage(FIRST_PAINT);
 }
 
 void
 AndroidDynamicToolbarAnimator::UpdateRootFrameMetrics(const FrameMetrics& aMetrics)
 {
   CSSToScreenScale scale = ViewTargetAs<ScreenPixel>(aMetrics.GetZoom().ToScaleFactor(),
                                                      PixelCastJustification::ScreenIsParentLayerForRoot);
   ScreenPoint scrollOffset = aMetrics.GetScrollOffset() * scale;
@@ -481,33 +479,16 @@ AndroidDynamicToolbarAnimator::MaybeUpda
   if (prevSize.height != size.height) {
     UpdateControllerCompositionHeight(size.height);
     UpdateFixedLayerMargins();
   }
 
   UpdateRootFrameMetrics(aMetrics);
 }
 
-// Layers updates are need by Robocop test which enables them
-void
-AndroidDynamicToolbarAnimator::EnableLayersUpdateNotifications(bool aEnable)
-{
-  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
-  mCompositorLayersUpdateEnabled = aEnable;
-}
-
-void
-AndroidDynamicToolbarAnimator::NotifyLayersUpdated()
-{
-  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
-  if (mCompositorLayersUpdateEnabled) {
-    PostMessage(LAYERS_UPDATED);
-  }
-}
-
 void
 AndroidDynamicToolbarAnimator::AdoptToolbarPixels(mozilla::ipc::Shmem&& aMem, const ScreenIntSize& aSize)
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   mCompositorToolbarPixels = Some(std::move(aMem));
   mCompositorToolbarPixelsSize = aSize;
 }
 
--- a/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.h
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.h
@@ -92,21 +92,16 @@ public:
   // Returns true if the animation will continue and false if it has completed.
   bool UpdateAnimation(const TimeStamp& aCurrentFrame);
   // Called to signify the first paint has occurred.
   void FirstPaint();
   // Called whenever the root document's FrameMetrics have reached a steady state.
   void UpdateRootFrameMetrics(const FrameMetrics& aMetrics);
   // Only update the frame metrics if the root composition size has changed
   void MaybeUpdateCompositionSizeAndRootFrameMetrics(const FrameMetrics& aMetrics);
-  // When aEnable is set to true, it informs the animator that the UI thread expects to
-  // be notified when the layer tree  has been updated. Enabled currently by robocop tests.
-  void EnableLayersUpdateNotifications(bool aEnable);
-  // Called when a layer has been updated so the UI thread may be notified if necessary.
-  void NotifyLayersUpdated();
   // Adopts the Shmem containing the toolbar snapshot sent from the UI thread.
   // The AndroidDynamicToolbarAnimator is responsible for deallocating the Shmem when
   // it is done being used.
   void AdoptToolbarPixels(mozilla::ipc::Shmem&& aMem, const ScreenIntSize& aSize);
   // Updates the toolbar snapshot texture and notifies the UI thread that the static toolbar is
   // now ready to be displayed.
   void UpdateToolbarSnapshotTexture(CompositorOGL* gl);
   // Returns the Effect object used by the compositor to render the toolbar snapshot.
@@ -221,17 +216,16 @@ protected:
     QueuedMessage() = delete;
     QueuedMessage(const QueuedMessage&) = delete;
     QueuedMessage& operator=(const QueuedMessage&) = delete;
   };
 
   // Compositor thread only
   bool mCompositorShutdown;             // Set to true when the compositor has been shutdown
   bool mCompositorAnimationDeferred;    // An animation has been deferred until the toolbar is unlocked
-  bool mCompositorLayersUpdateEnabled;  // Flag set to true when the UI thread is expecting to be notified when a layer has been updated
   bool mCompositorAnimationStarted;     // Set to true when the compositor has actually started animating the static snapshot.
   bool mCompositorReceivedFirstPaint;   // Set to true when a first paint occurs. Used by toolbar animator to detect a new page load.
   bool mCompositorWaitForPageResize;    // Set to true if the bottom of the page has been reached and the toolbar animator should wait for the page to resize before ending animation.
   bool mCompositorToolbarShowRequested; // Set to true if the animator has already requested the real toolbar chrome be shown
   bool mCompositorSendResponseForSnapshotUpdate;  // Set to true when a message should be sent after a static toolbar snapshot update
   AnimationStyle mCompositorAnimationStyle;       // Set to true when the snapshot should be immediately hidden or shown in the animation update
   ScreenIntCoord mCompositorMaxToolbarHeight;     // Should contain the same value as mControllerMaxToolbarHeight
   ScreenIntCoord mCompositorToolbarHeight;        // This value is only updated by the compositor thread when the mToolbarState == ToolbarAnimating
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2954,19 +2954,19 @@ bool AsyncPanZoomController::AttemptScro
     }
 
     if (!IsZero(adjustedDisplacement)) {
       ScrollBy(adjustedDisplacement / Metrics().GetZoom());
       if (InputBlockState* block = GetCurrentInputBlock()) {
 #if defined(MOZ_WIDGET_ANDROID)
         if (block->AsTouchBlock() && (block->GetScrolledApzc() != this) && IsRootContent()) {
           if (APZCTreeManager* manager = GetApzcTreeManager()) {
-            AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
-            MOZ_ASSERT(animator);
-            animator->SetScrollingRootContent();
+            if (AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator()) {
+              animator->SetScrollingRootContent();
+            }
           }
         }
 #endif
         block->SetScrolledApzc(this);
       }
       ScheduleCompositeAndMaybeRepaint();
       UpdateSharedCompositorFrameMetrics();
     }
@@ -4264,19 +4264,19 @@ void AsyncPanZoomController::NotifyLayer
     if (viewportUpdated || scrollOffsetUpdated) {
       Metrics().SetViewport(aLayerMetrics.GetViewport());
     }
   }
 
 #if defined(MOZ_WIDGET_ANDROID)
   if (aLayerMetrics.IsRootContent()) {
     if (APZCTreeManager* manager = GetApzcTreeManager()) {
-      AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
-      MOZ_ASSERT(animator);
-      animator->MaybeUpdateCompositionSizeAndRootFrameMetrics(aLayerMetrics);
+      if (AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator()) {
+        animator->MaybeUpdateCompositionSizeAndRootFrameMetrics(aLayerMetrics);
+      }
     }
   }
 #endif
 
   if ((aIsFirstPaint && aThisLayerTreeUpdated) || isDefault) {
     // Initialize our internal state to something sane when the content
     // that was just painted is something we knew nothing about previously
     CancelAnimation();
@@ -4704,19 +4704,19 @@ void AsyncPanZoomController::DispatchSta
       }
 #endif
     } else if (IsTransformingState(aOldState) && !IsTransformingState(aNewState)) {
 #if defined(MOZ_WIDGET_ANDROID)
       // The Android UI thread only shows overlay UI elements when the content is not being
       // panned or zoomed and it is in a steady state. So the FrameMetrics only need to be
       // updated when the transform ends.
       if (APZCTreeManager* manager = GetApzcTreeManager()) {
-        AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator();
-        MOZ_ASSERT(animator);
-        animator->UpdateRootFrameMetrics(Metrics());
+        if (AndroidDynamicToolbarAnimator* animator = manager->GetAndroidDynamicToolbarAnimator()) {
+          animator->UpdateRootFrameMetrics(Metrics());
+        }
       }
 #endif
 
       controller->NotifyAPZStateChange(
           GetGuid(), APZStateChange::eTransformEnd);
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
       if (gfxPrefs::HidePluginsForScroll() && mCompositorController) {
         mCompositorController->ScheduleShowAllPluginWindows();
--- a/gfx/layers/apz/test/mochitest/helper_bug982141.html
+++ b/gfx/layers/apz/test/mochitest/helper_bug982141.html
@@ -3,49 +3,37 @@
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=982141
 -->
 <head>
   <meta charset="utf-8">
   <meta name="viewport" content="user-scalable=no">
   <title>Test for Bug 982141, helper page</title>
   <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
   <script type="application/javascript">
 
     // -------------------------------------------------------------------
     // Infrastructure to get the test assertions to run at the right time.
     // -------------------------------------------------------------------
     var SimpleTest = window.opener.SimpleTest;
 
-    window.onload = function() {
-        window.addEventListener("MozAfterPaint", afterPaint);
-    };
-    var utils = SpecialPowers.getDOMWindowUtils(window);
-    function afterPaint(e) {
-      // If there is another paint pending, wait for it.
-      if (utils.isMozAfterPaintPending) {
-          return;
-      }
-
-      // Once there are no more paints pending, remove the
-      // MozAfterPaint listener and run the test logic.
-      window.removeEventListener("MozAfterPaint", afterPaint);
-      testBug982141();
-    }
+    waitUntilApzStable().then(testBug982141);
 
     // --------------------------------------------------------------------
     // The actual logic for testing bug 982141.
     //
     // In this test we have a simple page with a scrollable <div> which has
     // enough content to make it scrollable. We test that this <div> got
     // a displayport.
     // --------------------------------------------------------------------
 
     function testBug982141() {
       // Get the content- and compositor-side test data from nsIDOMWindowUtils.
+      var utils = SpecialPowers.getDOMWindowUtils(window);
       var contentTestData = utils.getContentAPZTestData();
       var compositorTestData = utils.getCompositorAPZTestData();
 
       // Get the sequence number of the last paint on the compositor side.
       // We do this before converting the APZ test data because the conversion
       // loses the order of the paints.
       SimpleTest.ok(compositorTestData.paints.length > 0,
                     "expected at least one paint in compositor test data");
--- a/gfx/layers/apz/test/mochitest/test_group_zoom.html
+++ b/gfx/layers/apz/test/mochitest/test_group_zoom.html
@@ -29,17 +29,17 @@ var prefs = [
   // Explicitly enable pinch-zooming, so this test can run on desktop
   // even though zooming isn't enabled by default on desktop yet.
   ["apz.allow_zooming", true],
   // Pinch-zooming currently requires meta viewport support (this requirement
   // will eventually be removed).
   ["dom.meta-viewport.enabled", true],
   // Pinch-zooming currently requires container scrolling (this requirement
   // will eventually be removed).
-  ["layout.scroll.root-frame-containers", true],
+  ["layout.scroll.root-frame-containers", 1],
   // Retained displaylists don't work well with container scrolling, so
   // they too need to be disabled for now.
   ["layout.display-list.retain", false],
   ["layout.display-list.retain.chrome", false],
 ];
 
 // Increase the tap timeouts so the double-tap is still detected in case of
 // random delays during testing.
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -1011,29 +1011,37 @@ AsyncCompositionManager::ApplyAsyncConte
               *aOutFoundRoot = metrics.IsRootContent() ||       /* RCD */
                     (layer->GetParent() == nullptr &&          /* rootmost metrics */
                      i + 1 >= layer->GetScrollMetadataCount());
               if (*aOutFoundRoot) {
                 mRootScrollableId = metrics.GetScrollId();
                 Compositor* compositor = mLayerManager->GetCompositor();
                 if (CompositorBridgeParent* bridge = compositor->GetCompositorBridgeParent()) {
                   AndroidDynamicToolbarAnimator* animator = bridge->GetAndroidDynamicToolbarAnimator();
-                  MOZ_ASSERT(animator);
                   if (mIsFirstPaint) {
-                    animator->UpdateRootFrameMetrics(metrics);
-                    animator->FirstPaint();
+                    if (animator) {
+                      animator->UpdateRootFrameMetrics(metrics);
+                      animator->FirstPaint();
+                    }
+                    LayersId rootLayerTreeId = bridge->RootLayerTreeId();
+                    if (RefPtr<UiCompositorControllerParent> uiController = UiCompositorControllerParent::GetFromRootLayerTreeId(rootLayerTreeId)) {
+                      uiController->NotifyFirstPaint();
+                    }
                     mIsFirstPaint = false;
                   }
                   if (mLayersUpdated) {
-                    animator->NotifyLayersUpdated();
+                    LayersId rootLayerTreeId = bridge->RootLayerTreeId();
+                    if (RefPtr<UiCompositorControllerParent> uiController = UiCompositorControllerParent::GetFromRootLayerTreeId(rootLayerTreeId)) {
+                      uiController->NotifyLayersUpdated();
+                    }
                     mLayersUpdated = false;
                   }
                   // If this is not actually the root content then the animator is not getting updated in AsyncPanZoomController::NotifyLayersUpdated
                   // because the root content document is not scrollable. So update it here so it knows if the root composition size has changed.
-                  if (!metrics.IsRootContent()) {
+                  if (animator && !metrics.IsRootContent()) {
                     animator->MaybeUpdateCompositionSizeAndRootFrameMetrics(metrics);
                   }
                 }
                 fixedLayerMargins = mFixedLayerMargins;
               }
             }
 #else
             *aOutFoundRoot = false;
@@ -1308,19 +1316,19 @@ AsyncCompositionManager::TransformShadow
   MOZ_ASSERT(aVsyncRate != TimeDuration::Forever());
   if (aVsyncRate != TimeDuration::Forever()) {
     nextFrame += aVsyncRate;
   }
 
 #if defined(MOZ_WIDGET_ANDROID)
   Compositor* compositor = mLayerManager->GetCompositor();
   if (CompositorBridgeParent* bridge = compositor->GetCompositorBridgeParent()) {
-    AndroidDynamicToolbarAnimator* animator = bridge->GetAndroidDynamicToolbarAnimator();
-    MOZ_ASSERT(animator);
-    wantNextFrame |= animator->UpdateAnimation(nextFrame);
+    if (AndroidDynamicToolbarAnimator* animator = bridge->GetAndroidDynamicToolbarAnimator()) {
+      wantNextFrame |= animator->UpdateAnimation(nextFrame);
+    }
   }
 #endif // defined(MOZ_WIDGET_ANDROID)
 
   // Reset the previous time stamp if we don't already have any running
   // animations to avoid using the time which is far behind for newly
   // started animations.
   mPreviousFrameTimeStamp = wantNextFrame ? aCurrentFrame : TimeStamp();
 
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -53,23 +53,23 @@
 #include "nsDebug.h"                    // for NS_WARNING, etc
 #include "nsISupportsImpl.h"            // for Layer::AddRef, etc
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsRegion.h"                   // for nsIntRegion, etc
 #if defined(MOZ_WIDGET_ANDROID)
 #include <android/log.h>
 #include <android/native_window.h>
+#include "mozilla/jni/Utils.h"
 #include "mozilla/widget/AndroidCompositorWidget.h"
 #include "opengl/CompositorOGL.h"
 #include "GLConsts.h"
 #include "GLContextEGL.h"
 #include "GLContextProvider.h"
 #include "mozilla/Unused.h"
-#include "mozilla/widget/AndroidCompositorWidget.h"
 #include "ScopedGLHelpers.h"
 #endif
 #include "GeckoProfiler.h"
 #include "TextRenderer.h"               // for TextRenderer
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "TreeTraversal.h"              // for ForEachNode
 
 #ifdef USE_SKIA
@@ -987,17 +987,19 @@ LayerManagerComposite::Render(const nsIn
 
   mProfilerScreenshotGrabber.MaybeGrabScreenshot(mCompositor);
 
   mCompositor->NormalDrawingDone();
 
 #if defined(MOZ_WIDGET_ANDROID)
   // Depending on the content shift the toolbar may be rendered on top of
   // some of the content so it must be rendered after the content.
-  RenderToolbar();
+  if (jni::IsFennec()) {
+    RenderToolbar();
+  }
   HandlePixelsTarget();
 #endif // defined(MOZ_WIDGET_ANDROID)
 
   // Debugging
   RenderDebugOverlay(actualBounds);
 
   {
     AUTO_PROFILER_LABEL("LayerManagerComposite::Render:EndFrame", GRAPHICS);
@@ -1008,34 +1010,16 @@ LayerManagerComposite::Render(const nsIn
   mCompositor->GetWidget()->PostRender(&widgetContext);
 
   mProfilerScreenshotGrabber.MaybeProcessQueue();
 
   RecordFrame();
 }
 
 #if defined(MOZ_WIDGET_ANDROID)
-class ScopedCompositorProjMatrix {
-public:
-  ScopedCompositorProjMatrix(CompositorOGL* aCompositor, const Matrix4x4& aProjMatrix):
-    mCompositor(aCompositor),
-    mOriginalProjMatrix(mCompositor->GetProjMatrix())
-  {
-    mCompositor->SetProjMatrix(aProjMatrix);
-  }
-
-  ~ScopedCompositorProjMatrix()
-  {
-    mCompositor->SetProjMatrix(mOriginalProjMatrix);
-  }
-private:
-  CompositorOGL* const mCompositor;
-  const Matrix4x4 mOriginalProjMatrix;
-};
-
 class ScopedCompostitorSurfaceSize {
 public:
   ScopedCompostitorSurfaceSize(CompositorOGL* aCompositor, const gfx::IntSize& aSize) :
     mCompositor(aCompositor),
     mOriginalSize(mCompositor->GetDestinationSurfaceSize())
   {
     mCompositor->SetDestinationSurfaceSize(aSize);
   }
@@ -1173,16 +1157,21 @@ LayerManagerComposite::RenderToPresentat
 
   mCompositor->EndFrame();
 }
 
 ScreenCoord
 LayerManagerComposite::GetContentShiftForToolbar()
 {
   ScreenCoord result(0.0f);
+  // If we're not in Fennec, we don't have a dynamic toolbar so there isn't a
+  // content offset.
+  if (!jni::IsFennec()) {
+    return result;
+  }
   // If GetTargetContext return is not null we are not drawing to the screen so there will not be any content offset.
   if (mCompositor->GetTargetContext() != nullptr) {
     return result;
   }
 
   if (CompositorBridgeParent* bridge = mCompositor->GetCompositorBridgeParent()) {
     AndroidDynamicToolbarAnimator* animator = bridge->GetAndroidDynamicToolbarAnimator();
     MOZ_RELEASE_ASSERT(animator);
--- a/gfx/layers/ipc/UiCompositorControllerParent.cpp
+++ b/gfx/layers/ipc/UiCompositorControllerParent.cpp
@@ -160,19 +160,18 @@ UiCompositorControllerParent::RecvReques
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 UiCompositorControllerParent::RecvEnableLayerUpdateNotifications(const bool& aEnable)
 {
 #if defined(MOZ_WIDGET_ANDROID)
-  if (mAnimator) {
-    mAnimator->EnableLayersUpdateNotifications(aEnable);
-  }
+  // Layers updates are need by Robocop test which enables them
+  mCompositorLayersUpdateEnabled = aEnable;
 #endif // defined(MOZ_WIDGET_ANDROID)
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 UiCompositorControllerParent::RecvToolbarPixelsToCompositor(Shmem&& aMem, const ScreenIntSize& aSize)
 {
@@ -232,18 +231,38 @@ UiCompositorControllerParent::ToolbarAni
 
 bool
 UiCompositorControllerParent::AllocPixelBuffer(const int32_t aSize, ipc::Shmem* aMem)
 {
   MOZ_ASSERT(aSize > 0);
   return AllocShmem(aSize, ipc::SharedMemory::TYPE_BASIC, aMem);
 }
 
+void
+UiCompositorControllerParent::NotifyLayersUpdated()
+{
+#ifdef MOZ_WIDGET_ANDROID
+  if (mCompositorLayersUpdateEnabled) {
+    ToolbarAnimatorMessageFromCompositor(LAYERS_UPDATED);
+  }
+#endif
+}
+
+void
+UiCompositorControllerParent::NotifyFirstPaint()
+{
+  ToolbarAnimatorMessageFromCompositor(FIRST_PAINT);
+}
+
+
 UiCompositorControllerParent::UiCompositorControllerParent(const LayersId& aRootLayerTreeId)
   : mRootLayerTreeId(aRootLayerTreeId)
+#ifdef MOZ_WIDGET_ANDROID
+  , mCompositorLayersUpdateEnabled(false)
+#endif
   , mMaxToolbarHeight(0)
 {
   MOZ_COUNT_CTOR(UiCompositorControllerParent);
 }
 
 UiCompositorControllerParent::~UiCompositorControllerParent()
 {
   MOZ_COUNT_DTOR(UiCompositorControllerParent);
@@ -279,17 +298,18 @@ UiCompositorControllerParent::Initialize
   AddRef();
   LayerTreeState* state = CompositorBridgeParent::GetIndirectShadowTree(mRootLayerTreeId);
   MOZ_ASSERT(state);
   MOZ_ASSERT(state->mParent);
   state->mUiControllerParent = this;
 #if defined(MOZ_WIDGET_ANDROID)
   AndroidDynamicToolbarAnimator* animator = state->mParent->GetAndroidDynamicToolbarAnimator();
   // It is possible the compositor has already started shutting down and
-  // the AndroidDynamicToolbarAnimator could be a nullptr.
+  // the AndroidDynamicToolbarAnimator could be a nullptr. Or this could be
+  // non-Fennec in which case the animator is null anyway.
   if (animator) {
     animator->Initialize(mRootLayerTreeId);
   }
 #endif
 }
 
 void
 UiCompositorControllerParent::Open(Endpoint<PUiCompositorControllerParent>&& aEndpoint)
--- a/gfx/layers/ipc/UiCompositorControllerParent.h
+++ b/gfx/layers/ipc/UiCompositorControllerParent.h
@@ -44,29 +44,34 @@ public:
 
   // Class specific functions
 #if defined(MOZ_WIDGET_ANDROID)
   void RegisterAndroidDynamicToolbarAnimator(AndroidDynamicToolbarAnimator* aAnimator);
 #endif // MOZ_WIDGET_ANDROID
   void ToolbarAnimatorMessageFromCompositor(int32_t aMessage);
   bool AllocPixelBuffer(const int32_t aSize, Shmem* aMem);
 
+  // Called when a layer has been updated so the UI thread may be notified if necessary.
+  void NotifyLayersUpdated();
+  void NotifyFirstPaint();
+
 private:
   explicit UiCompositorControllerParent(const LayersId& aRootLayerTreeId);
   ~UiCompositorControllerParent();
   void InitializeForSameProcess();
   void InitializeForOutOfProcess();
   void Initialize();
   void Open(Endpoint<PUiCompositorControllerParent>&& aEndpoint);
   void Shutdown();
 
   LayersId mRootLayerTreeId;
 
 #if defined(MOZ_WIDGET_ANDROID)
   RefPtr<AndroidDynamicToolbarAnimator> mAnimator;
+  bool mCompositorLayersUpdateEnabled;  // Flag set to true when the UI thread is expecting to be notified when a layer has been updated
 #endif // defined(MOZ_WIDGET_ANDROID)
 
   int32_t mMaxToolbarHeight;
 };
 
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -517,21 +517,17 @@ private:
   DECL_GFX_PREF(Once, "gfx.use-iosurface-textures",            UseIOSurfaceTextures, bool, false);
   DECL_GFX_PREF(Once, "gfx.use-mutex-on-present",              UseMutexOnPresent, bool, false);
   DECL_GFX_PREF(Once, "gfx.use-surfacetexture-textures",       UseSurfaceTextureTextures, bool, false);
   DECL_GFX_PREF(Once, "gfx.allow-texture-direct-mapping",      AllowTextureDirectMapping, bool, true);
   DECL_GFX_PREF(Live, "gfx.vsync.collect-scroll-transforms",   CollectScrollTransforms, bool, false);
   DECL_GFX_PREF(Once, "gfx.vsync.compositor.unobserve-count",  CompositorUnobserveCount, int32_t, 10);
 
   DECL_GFX_PREF(Once, "gfx.webrender.all",                     WebRenderAll, bool, false);
-#ifdef NIGHTLY_BUILD
   DECL_GFX_PREF(Once, "gfx.webrender.all.qualified",           WebRenderAllQualified, bool, true);
-#else
-  DECL_GFX_PREF(Once, "gfx.webrender.all.qualified",           WebRenderAllQualified, bool, false);
-#endif
   DECL_GFX_PREF(Live, "gfx.webrender.blob-images",             WebRenderBlobImages, bool, true);
   DECL_GFX_PREF(Live, "gfx.webrender.blob.invalidation",       WebRenderBlobInvalidation, bool, false);
   DECL_GFX_PREF(Live, "gfx.webrender.blob.paint-flashing",     WebRenderBlobPaintFlashing, bool, false);
   DECL_GFX_PREF(Live, "gfx.webrender.dl.dump-parent",          WebRenderDLDumpParent, bool, false);
   DECL_GFX_PREF(Live, "gfx.webrender.dl.dump-content",         WebRenderDLDumpContent, bool, false);
   DECL_GFX_PREF(Once, "gfx.webrender.enabled",                 WebRenderEnabledDoNotUseDirectly, bool, false);
   DECL_GFX_PREF(Once, "gfx.webrender.force-disabled",          WebRenderForceDisabled, bool, false);
   DECL_GFX_PREF(Live, "gfx.webrender.highlight-painted-layers",WebRenderHighlightPaintedLayers, bool, false);
@@ -702,17 +698,17 @@ private:
   DECL_GFX_PREF(Live, "layout.display-list.flatten-transform", LayoutFlattenTransform, bool, true);
 
   DECL_GFX_PREF(Once, "layout.frame_rate",                     LayoutFrameRate, int32_t, -1);
   DECL_GFX_PREF(Once, "layout.less-event-region-items",        LessEventRegionItems, bool, true);
   DECL_GFX_PREF(Live, "layout.min-active-layer-size",          LayoutMinActiveLayerSize, int, 64);
   DECL_GFX_PREF(Once, "layout.paint_rects_separately",         LayoutPaintRectsSeparately, bool, true);
 
   // This and code dependent on it should be removed once containerless scrolling looks stable.
-  DECL_GFX_PREF(Live, "layout.scroll.root-frame-containers",   LayoutUseContainersForRootFrames, bool, true);
+  DECL_OVERRIDE_PREF(Live, "layout.scroll.root-frame-containers",   LayoutUseContainersForRootFrames, !OverrideBase_WebRender());
   // This pref is to be set by test code only.
   DECL_GFX_PREF(Live, "layout.scrollbars.always-layerize-track", AlwaysLayerizeScrollbarTrackTestOnly, bool, false);
   DECL_GFX_PREF(Live, "layout.smaller-painted-layers",         LayoutSmallerPaintedLayers, bool, false);
 
   DECL_GFX_PREF(Once, "media.hardware-video-decoding.force-enabled",
                                                                HardwareVideoDecodingForceEnabled, bool, false);
 #ifdef XP_WIN
   DECL_GFX_PREF(Live, "media.wmf.dxva.d3d11.enabled", PDMWMFAllowD3D11, bool, true);
--- a/js/src/builtin/AtomicsObject.cpp
+++ b/js/src/builtin/AtomicsObject.cpp
@@ -201,17 +201,17 @@ js::atomics_compareExchange(JSContext* c
     }
     int32_t newCandidate;
     if (!ToInt32(cx, newv, &newCandidate)) {
         return false;
     }
 
     bool badType = false;
     int32_t result = CompareExchange(view->type(), oldCandidate, newCandidate,
-                                     view->viewDataShared(), offset, &badType);
+                                     view->dataPointerShared(), offset, &badType);
 
     if (badType) {
         return ReportBadArrayType(cx);
     }
 
     if (view->type() == Scalar::Uint32) {
         r.setNumber((double)(uint32_t)result);
     } else {
@@ -232,17 +232,17 @@ js::atomics_load(JSContext* cx, unsigned
     if (!GetSharedTypedArray(cx, objv, &view)) {
         return false;
     }
     uint32_t offset;
     if (!GetTypedArrayIndex(cx, idxv, view, &offset)) {
         return false;
     }
 
-    SharedMem<void*> viewData = view->viewDataShared();
+    SharedMem<void*> viewData = view->dataPointerShared();
     switch (view->type()) {
       case Scalar::Uint8: {
         uint8_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint8_t*>() + offset);
         r.setInt32(v);
         return true;
       }
       case Scalar::Int8: {
         int8_t v = jit::AtomicOperations::loadSeqCst(viewData.cast<uint8_t*>() + offset);
@@ -352,17 +352,17 @@ ExchangeOrStore(JSContext* cx, unsigned 
     }
     double integerValue;
     if (!ToInteger(cx, valv, &integerValue)) {
         return false;
     }
 
     bool badType = false;
     int32_t result = ExchangeOrStore<op>(view->type(), JS::ToInt32(integerValue),
-                                         view->viewDataShared(), offset, &badType);
+                                         view->dataPointerShared(), offset, &badType);
 
     if (badType) {
         return ReportBadArrayType(cx);
     }
 
     if (op == DoStore) {
         r.setNumber(integerValue);
     } else if (view->type() == Scalar::Uint32) {
@@ -398,17 +398,17 @@ AtomicsBinop(JSContext* cx, HandleValue 
     if (!GetTypedArrayIndex(cx, idxv, view, &offset)) {
         return false;
     }
     int32_t numberValue;
     if (!ToInt32(cx, valv, &numberValue)) {
         return false;
     }
 
-    SharedMem<void*> viewData = view->viewDataShared();
+    SharedMem<void*> viewData = view->dataPointerShared();
     switch (view->type()) {
       case Scalar::Int8: {
         int8_t v = (int8_t)numberValue;
         r.setInt32(T::operate(viewData.cast<int8_t*>() + offset, v));
         return true;
       }
       case Scalar::Uint8: {
         uint8_t v = (uint8_t)numberValue;
@@ -695,17 +695,17 @@ js::atomics_wait(JSContext* cx, unsigned
             }
         }
     }
 
     Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
     // The computation will not overflow because range checks have been
     // performed.
     uint32_t byteOffset = offset * sizeof(int32_t) +
-                          (view->viewDataShared().cast<uint8_t*>().unwrap(/* arithmetic */) -
+                          (view->dataPointerShared().cast<uint8_t*>().unwrap(/* arithmetic */) -
                            sab->dataPointerShared().unwrap(/* arithmetic */));
 
     switch (atomics_wait_impl(cx, sab->rawBufferObject(), byteOffset, value, timeout)) {
       case FutexThread::WaitResult::NotEqual:
         r.setString(cx->names().futexNotEqual);
         return true;
       case FutexThread::WaitResult::OK:
         r.setString(cx->names().futexOK);
@@ -789,17 +789,17 @@ js::atomics_notify(JSContext* cx, unsign
         }
         count = dcount > INT64_MAX ? -1 : int64_t(dcount);
     }
 
     Rooted<SharedArrayBufferObject*> sab(cx, view->bufferShared());
     // The computation will not overflow because range checks have been
     // performed.
     uint32_t byteOffset = offset * sizeof(int32_t) +
-                          (view->viewDataShared().cast<uint8_t*>().unwrap(/* arithmetic */) -
+                          (view->dataPointerShared().cast<uint8_t*>().unwrap(/* arithmetic */) -
                            sab->dataPointerShared().unwrap(/* arithmetic */));
 
     r.setNumber(double(atomics_notify_impl(sab->rawBufferObject(), byteOffset, count)));
 
     return true;
 }
 
 /* static */ bool
--- a/js/src/builtin/DataViewObject.cpp
+++ b/js/src/builtin/DataViewObject.cpp
@@ -66,25 +66,22 @@ DataViewObject::create(JSContext* cx, ui
     // The isSharedMemory property is invariant.  Self-hosting code that sets
     // BUFFER_SLOT or the private slot (if it does) must maintain it by always
     // setting those to reference shared memory.
     bool isSharedMemory = IsSharedArrayBuffer(arrayBuffer.get());
     if (isSharedMemory) {
         obj->setIsSharedMemory();
     }
 
-    obj->setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(byteOffset));
-    obj->setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(byteLength));
-    obj->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*arrayBuffer));
+    obj->setFixedSlot(BYTEOFFSET_SLOT, Int32Value(byteOffset));
+    obj->setFixedSlot(LENGTH_SLOT, Int32Value(byteLength));
+    obj->setFixedSlot(BUFFER_SLOT, ObjectValue(*arrayBuffer));
 
     SharedMem<uint8_t*> ptr = arrayBuffer->dataPointerEither();
-    // A pointer to raw shared memory is exposed through the private slot.  This
-    // is safe so long as getPrivate() is not used willy-nilly.  It is wrapped in
-    // other accessors in TypedArrayObject.h.
-    obj->initPrivate(ptr.unwrap(/*safe - see above*/) + byteOffset);
+    obj->initDataPointer(ptr + byteOffset);
 
     // Include a barrier if the data view's data pointer is in the nursery, as
     // is done for typed arrays.
     if (!IsInsideNursery(obj) && cx->nursery().isInside(ptr)) {
         // Shared buffer data should never be nursery-allocated, so we
         // need to fail here if isSharedMemory.  However, mmap() can
         // place a SharedArrayRawBuffer up against the bottom end of a
         // nursery chunk, and a zero-length buffer will erroneously be
@@ -93,17 +90,17 @@ DataViewObject::create(JSContext* cx, ui
             MOZ_ASSERT(arrayBuffer->byteLength() == 0 &&
                        (uintptr_t(ptr.unwrapValue()) & gc::ChunkMask) == 0);
         } else {
             cx->runtime()->gc.storeBuffer().putWholeCell(obj);
         }
     }
 
     // Verify that the private slot is at the expected place
-    MOZ_ASSERT(obj->numFixedSlots() == TypedArrayObject::DATA_SLOT);
+    MOZ_ASSERT(obj->numFixedSlots() == DATA_SLOT);
 
     if (arrayBuffer->is<ArrayBufferObject>()) {
         if (!arrayBuffer->as<ArrayBufferObject>().addView(cx, obj)) {
             return nullptr;
         }
     }
 
     return obj;
@@ -410,17 +407,17 @@ DataViewObject::read(JSContext* cx, Hand
     if (!ToIndex(cx, args.get(0), &getIndex)) {
         return false;
     }
 
     // Step 5.
     bool isLittleEndian = args.length() >= 2 && ToBoolean(args[1]);
 
     // Steps 6-7.
-    if (obj->arrayBufferEither().isDetached()) {
+    if (obj->hasDetachedBuffer()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
         return false;
     }
 
     // Steps 8-12.
     bool isSharedMemory;
     SharedMem<uint8_t*> data = DataViewObject::getDataPointer<NativeType>(cx, obj, getIndex,
                                                                           &isSharedMemory);
@@ -498,17 +495,17 @@ DataViewObject::write(JSContext* cx, Han
         value = JS::CanonicalizeNaN(value);
     }
 #endif
 
     // Step 6.
     bool isLittleEndian = args.length() >= 3 && ToBoolean(args[2]);
 
     // Steps 7-8.
-    if (obj->arrayBufferEither().isDetached()) {
+    if (obj->hasDetachedBuffer()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
         return false;
     }
 
     // Steps 9-13.
     bool isSharedMemory;
     SharedMem<uint8_t*> data = DataViewObject::getDataPointer<NativeType>(cx, obj, getIndex,
                                                                           &isSharedMemory);
@@ -890,17 +887,17 @@ DataViewObject::bufferGetter(JSContext* 
 }
 
 bool
 DataViewObject::byteLengthGetterImpl(JSContext* cx, const CallArgs& args)
 {
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
     // Step 6.
-    if (thisView->arrayBufferEither().isDetached()) {
+    if (thisView->hasDetachedBuffer()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
         return false;
     }
 
     // Step 7.
     args.rval().set(DataViewObject::byteLengthValue(thisView));
     return true;
 }
@@ -913,17 +910,17 @@ DataViewObject::byteLengthGetter(JSConte
 }
 
 bool
 DataViewObject::byteOffsetGetterImpl(JSContext* cx, const CallArgs& args)
 {
     Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>());
 
     // Step 6.
-    if (thisView->arrayBufferEither().isDetached()) {
+    if (thisView->hasDetachedBuffer()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
         return false;
     }
 
     // Step 7.
     args.rval().set(DataViewObject::byteOffsetValue(thisView));
     return true;
 }
@@ -962,17 +959,17 @@ const ClassSpec DataViewObject::classSpe
     nullptr,
     DataViewObject::methods,
     DataViewObject::properties,
 };
 
 const Class DataViewObject::class_ = {
     "DataView",
     JSCLASS_HAS_PRIVATE |
-    JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) |
+    JSCLASS_HAS_RESERVED_SLOTS(DataViewObject::RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_DataView),
     &DataViewObjectClassOps,
     &DataViewObject::classSpec_
 };
 
 const Class DataViewObject::protoClass_ = {
     js_Object_str,
     JSCLASS_HAS_CACHED_PROTO(JSProto_DataView),
@@ -1005,24 +1002,16 @@ const JSFunctionSpec DataViewObject::met
 const JSPropertySpec DataViewObject::properties[] = {
     JS_PSG("buffer", DataViewObject::bufferGetter, 0),
     JS_PSG("byteLength", DataViewObject::byteLengthGetter, 0),
     JS_PSG("byteOffset", DataViewObject::byteOffsetGetter, 0),
     JS_STRING_SYM_PS(toStringTag, "DataView", JSPROP_READONLY),
     JS_PS_END
 };
 
-void
-DataViewObject::notifyBufferDetached(void* newData)
-{
-    setFixedSlot(TypedArrayObject::LENGTH_SLOT, Int32Value(0));
-    setFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT, Int32Value(0));
-    setPrivate(newData);
-}
-
 JS_FRIEND_API(bool)
 JS_IsDataViewObject(JSObject* obj)
 {
     obj = CheckedUnwrap(obj);
     return obj ? obj->is<DataViewObject>() : false;
 }
 
 JS_FRIEND_API(uint32_t)
--- a/js/src/builtin/DataViewObject.h
+++ b/js/src/builtin/DataViewObject.h
@@ -7,28 +7,28 @@
 #ifndef vm_DataViewObject_h
 #define vm_DataViewObject_h
 
 #include "mozilla/Attributes.h"
 
 #include "gc/Barrier.h"
 #include "js/Class.h"
 #include "vm/ArrayBufferObject.h"
+#include "vm/ArrayBufferViewObject.h"
 #include "vm/JSObject.h"
 #include "vm/SharedArrayObject.h"
-#include "vm/TypedArrayObject.h"
 
 namespace js {
 
 // In the DataViewObject, the private slot contains a raw pointer into
 // the buffer.  The buffer may be shared memory and the raw pointer
 // should not be exposed without sharedness information accompanying
 // it.
 
-class DataViewObject : public NativeObject
+class DataViewObject : public ArrayBufferViewObject
 {
   private:
     static const ClassSpec classSpec_;
 
     static JSObject* CreatePrototype(JSContext* cx, JSProtoKey key);
 
     static bool is(HandleValue v) {
         return v.isObject() && v.toObject().hasClass(&class_);
@@ -57,61 +57,35 @@ class DataViewObject : public NativeObje
     create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength,
            Handle<ArrayBufferObjectMaybeShared*> arrayBuffer, HandleObject proto);
 
   public:
     static const Class class_;
     static const Class protoClass_;
 
     static Value byteOffsetValue(const DataViewObject* view) {
-        Value v = view->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT);
+        Value v = view->getFixedSlot(BYTEOFFSET_SLOT);
         MOZ_ASSERT(v.toInt32() >= 0);
         return v;
     }
 
     static Value byteLengthValue(const DataViewObject* view) {
-        Value v = view->getFixedSlot(TypedArrayObject::LENGTH_SLOT);
+        Value v = view->getFixedSlot(LENGTH_SLOT);
         MOZ_ASSERT(v.toInt32() >= 0);
         return v;
     }
 
-    static Value bufferValue(const DataViewObject* view) {
-        return view->getFixedSlot(TypedArrayObject::BUFFER_SLOT);
-    }
-
     uint32_t byteOffset() const {
         return byteOffsetValue(this).toInt32();
     }
 
     uint32_t byteLength() const {
         return byteLengthValue(this).toInt32();
     }
 
-    ArrayBufferObjectMaybeShared& arrayBufferEither() const {
-        return bufferValue(this).toObject().as<ArrayBufferObjectMaybeShared>();
-    }
-
-    SharedMem<void*> dataPointerEither() const {
-        void *p = getPrivate();
-        if (isSharedMemory()) {
-            return SharedMem<void*>::shared(p);
-        }
-        return SharedMem<void*>::unshared(p);
-    }
-
-    void* dataPointerUnshared() const {
-        MOZ_ASSERT(!isSharedMemory());
-        return getPrivate();
-    }
-
-    void* dataPointerShared() const {
-        MOZ_ASSERT(isSharedMemory());
-        return getPrivate();
-    }
-
     static bool construct(JSContext* cx, unsigned argc, Value* vp);
 
     static bool getInt8Impl(JSContext* cx, const CallArgs& args);
     static bool fun_getInt8(JSContext* cx, unsigned argc, Value* vp);
 
     static bool getUint8Impl(JSContext* cx, const CallArgs& args);
     static bool fun_getUint8(JSContext* cx, unsigned argc, Value* vp);
 
@@ -158,18 +132,16 @@ class DataViewObject : public NativeObje
     static bool fun_setFloat64(JSContext* cx, unsigned argc, Value* vp);
 
     template<typename NativeType>
     static bool read(JSContext* cx, Handle<DataViewObject*> obj, const CallArgs& args,
                      NativeType* val);
     template<typename NativeType>
     static bool write(JSContext* cx, Handle<DataViewObject*> obj, const CallArgs& args);
 
-    void notifyBufferDetached(void* newData);
-
   private:
     static const JSFunctionSpec methods[];
     static const JSPropertySpec properties[];
 };
 
 } // namespace js
 
 #endif /* vm_DataViewObject_h */
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -1063,18 +1063,19 @@ static MOZ_MUST_USE bool
 ReadableStream_cancel(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1: If ! IsReadableStream(this) is false, return a promise rejected
     //         with a TypeError exception.
     Rooted<ReadableStream*> stream(cx);
     stream = ToUnwrapped<ReadableStream>(cx, args.thisv(), "ReadableStream", "cancel");
-    if (!stream)
+    if (!stream) {
         return ReturnPromiseRejectedWithPendingError(cx, args);
+    }
 
     // Step 2: If ! IsReadableStreamLocked(this) is true, return a promise
     //         rejected with a TypeError exception.
     if (stream->locked()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_READABLESTREAM_LOCKED_METHOD, "cancel");
         return ReturnPromiseRejectedWithPendingError(cx, args);
     }
@@ -1095,18 +1096,19 @@ CreateReadableStreamDefaultReader(JSCont
 static bool
 ReadableStream_getReader(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
     Rooted<ReadableStream*> stream(cx);
     stream = ToUnwrapped<ReadableStream>(cx, args.thisv(), "ReadableStream", "getReader");
-    if (!stream)
+    if (!stream) {
         return false;
+    }
 
     RootedObject reader(cx);
 
     // Step 2: If mode is undefined, return
     //         ? AcquireReadableStreamDefaultReader(this).
     RootedValue modeVal(cx);
     HandleValue optionsVal = args.get(0);
     if (!optionsVal.isUndefined()) {
@@ -1178,18 +1180,19 @@ ReadableStreamTee(JSContext* cx, Handle<
 static bool
 ReadableStream_tee(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
     Rooted<ReadableStream*> stream(cx);
     stream = ToUnwrapped<ReadableStream>(cx, args.thisv(), "ReadableStream", "tee");
-    if (!stream)
+    if (!stream) {
         return false;
+    }
 
     // Step 2: Let branches be ? ReadableStreamTee(this, false).
     Rooted<ReadableStream*> branch1(cx);
     Rooted<ReadableStream*> branch2(cx);
     if (!ReadableStreamTee(cx, stream, false, &branch1, &branch2)) {
         return false;
     }
 
@@ -1358,21 +1361,23 @@ ReadableStreamTee_Pull(JSContext* cx, Ha
     //         branch2 be F.[[branch2]], teeState be F.[[teeState]], and
     //         cloneForBranch2 be F.[[cloneForBranch2]].
 
     // Step 2: Return the result of transforming
     //         ! ReadableStreamDefaultReaderRead(reader) by a fulfillment
     //         handler which takes the argument result and performs the
     //         following steps:
     Rooted<ReadableStream*> stream(cx, TeeState::stream(cx, teeState));
-    if (!stream)
+    if (!stream) {
         return nullptr;
+    }
     RootedObject readerObj(cx, ReaderFromStream(cx, stream));
-    if (!readerObj)
+    if (!readerObj) {
         return nullptr;
+    }
     Rooted<ReadableStreamDefaultReader*> reader(cx,
                                                 &readerObj->as<ReadableStreamDefaultReader>());
 
     RootedObject readPromise(cx, ReadableStreamDefaultReader::read(cx, reader));
     if (!readPromise) {
         return nullptr;
     }
 
@@ -1393,31 +1398,33 @@ ReadableStreamTee_Pull(JSContext* cx, Ha
  * created in the current cx compartment.
  */
 static MOZ_MUST_USE JSObject*
 ReadableStreamTee_Cancel(JSContext* cx, Handle<TeeState*> teeState,
                          Handle<ReadableStreamDefaultController*> branch, HandleValue reason_)
 {
     // Step 1: Let stream be F.[[stream]] and teeState be F.[[teeState]].
     Rooted<ReadableStream*> stream(cx, TeeState::stream(cx, teeState));
-    if (!stream)
+    if (!stream) {
         return nullptr;
+    }
 
     bool bothBranchesCanceled = false;
 
     // Step 2: Set teeState.[[canceled1]] to true.
     // Step 3: Set teeState.[[reason1]] to reason.
     {
         RootedValue reason(cx, reason_);
         if (reason.isGCThing() &&
             reason.toGCThing()->maybeCompartment() != teeState->compartment())
         {
             AutoRealm ar(cx, teeState);
-            if (!cx->compartment()->wrap(cx, &reason))
+            if (!cx->compartment()->wrap(cx, &reason)) {
                 return nullptr;
+            }
         }
         if (ControllerFlags(branch) & ControllerFlag_TeeBranch1) {
             teeState->setCanceled1(reason);
             bothBranchesCanceled = teeState->canceled2();
         } else {
             MOZ_ASSERT(ControllerFlags(branch) & ControllerFlag_TeeBranch2);
             teeState->setCanceled2(reason);
             bothBranchesCanceled = teeState->canceled1();
@@ -1434,18 +1441,19 @@ ReadableStreamTee_Cancel(JSContext* cx, 
             return nullptr;
         }
 
         compositeReason->setDenseInitializedLength(2);
 
         RootedValue reason1(cx, teeState->reason1());
         RootedValue reason2(cx, teeState->reason2());
         if (teeState->compartment() != cx->compartment()) {
-            if (!cx->compartment()->wrap(cx, &reason1) || !cx->compartment()->wrap(cx, &reason2))
+            if (!cx->compartment()->wrap(cx, &reason1) || !cx->compartment()->wrap(cx, &reason2)) {
                 return nullptr;
+            }
         }
         compositeReason->initDenseElement(0, reason1);
         compositeReason->initDenseElement(1, reason2);
         RootedValue compositeReasonVal(cx, ObjectValue(*compositeReason));
 
         Rooted<PromiseObject*> promise(cx, teeState->promise());
 
         // Step b: Let cancelResult be ! ReadableStreamCancel(stream, compositeReason).
@@ -1454,30 +1462,32 @@ ReadableStreamTee_Cancel(JSContext* cx, 
             AutoRealm ar(cx, promise);
             if (!cancelResult) {
                 if (!RejectWithPendingError(cx, promise)) {
                     return nullptr;
                 }
             } else {
                 // Step c: Resolve teeState.[[promise]] with cancelResult.
                 RootedValue resultVal(cx, ObjectValue(*cancelResult));
-                if (!cx->compartment()->wrap(cx, &resultVal))
+                if (!cx->compartment()->wrap(cx, &resultVal)) {
                     return nullptr;
+                }
                 if (!PromiseObject::resolve(cx, promise, resultVal)) {
                     return nullptr;
                 }
             }
         }
     }
 
     // Step 5: Return teeState.[[promise]].
     RootedObject promise(cx, teeState->promise());
     if (promise->compartment() != cx->compartment()) {
-        if (!cx->compartment()->wrap(cx, &promise))
+        if (!cx->compartment()->wrap(cx, &promise)) {
             return nullptr;
+        }
     }
     return promise;
 }
 
 static MOZ_MUST_USE bool
 ReadableStreamDefaultControllerErrorIfNeeded(JSContext* cx,
                                              Handle<ReadableStreamDefaultController*> controller,
                                              HandleValue e);
@@ -1628,18 +1638,19 @@ AppendToListAtSlot(JSContext* cx, Handle
  * Note: The returned Promise is created in the current cx compartment.
  */
 static MOZ_MUST_USE JSObject*
 ReadableStreamAddReadOrReadIntoRequest(JSContext* cx, Handle<ReadableStream*> stream)
 {
     // Step 1: Assert: ! IsReadableStreamBYOBReader(stream.[[reader]]) is true.
     // Skipped: handles both kinds of readers.
     Rooted<ReadableStreamReader*> reader(cx, ReaderFromStream(cx, stream));
-    if (!reader)
+    if (!reader) {
         return nullptr;
+    }
 
     // Step 2 of 3.4.2: Assert: stream.[[state]] is "readable".
     MOZ_ASSERT_IF(reader->is<ReadableStreamDefaultReader>(), stream->readable());
 
     // Step 3: Let promise be a new promise.
     RootedObject promise(cx, PromiseObject::createSkippingExecutor(cx));
     if (!promise) {
         return nullptr;
@@ -1693,18 +1704,19 @@ ReadableStream::cancel(JSContext* cx, Ha
     if (stream->closed()) {
         return PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
     }
 
     // Step 3: If stream.[[state]] is "errored", return a new promise rejected
     //         with stream.[[storedError]].
     if (stream->errored()) {
         RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
-        if (!cx->compartment()->wrap(cx, &storedError))
+        if (!cx->compartment()->wrap(cx, &storedError)) {
             return nullptr;
+        }
         return PromiseObject::unforgeableReject(cx, storedError);
     }
 
     // Step 4: Perform ! ReadableStreamClose(stream).
     if (!ReadableStreamCloseInternal(cx, stream)) {
         return nullptr;
     }
 
@@ -1778,18 +1790,19 @@ ReadableStreamCloseInternal(JSContext* c
                     return false;
                 }
 
                 resultObj = CreateIterResultObject(cx, UndefinedHandleValue, true);
                 if (!resultObj) {
                     return false;
                 }
                 resultVal = ObjectValue(*resultObj);
-                if (!ResolvePromise(cx, readRequest, resultVal))
+                if (!ResolvePromise(cx, readRequest, resultVal)) {
                     return false;
+                }
             }
 
             // Step b: Set reader.[[readRequests]] to an empty List.
             reader->setFixedSlot(ReaderSlot_Requests, UndefinedValue());
         }
     }
 
     // Step 6: Resolve reader.[[closedPromise]] with undefined.
@@ -1857,18 +1870,19 @@ ReadableStreamErrorInternal(JSContext* c
     for (uint32_t i = 0; i < len; i++) {
         // Step i: Reject readRequest.[[promise]] with e.
         val = readRequests->getDenseElement(i);
         readRequest = &val.toObject();
 
         // Responses have to be created in the compartment from which the
         // error was triggered, which might not be the same as the one the
         // request was created in, so we have to wrap requests here.
-        if (!cx->compartment()->wrap(cx, &readRequest))
+        if (!cx->compartment()->wrap(cx, &readRequest)) {
             return false;
+        }
 
         if (!RejectPromise(cx, readRequest, e)) {
             return false;
         }
     }
 
     // Step b: Set reader.[[readRequests]] to a new empty List.
     if (!SetNewList(cx, reader, ReaderSlot_Requests)) {
@@ -1895,18 +1909,19 @@ ReadableStreamErrorInternal(JSContext* c
         // Make sure we're in the stream's compartment.
         AutoRealm ar(cx, stream);
         ReadableStreamController* controller = ControllerFromStream(stream);
         void* source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
 
         // Ensure that the embedding doesn't have to deal with
         // mixed-compartment arguments to the callback.
         RootedValue error(cx, e);
-        if (!cx->compartment()->wrap(cx, &error))
+        if (!cx->compartment()->wrap(cx, &error)) {
             return false;
+        }
 
         cx->runtime()->readableStreamErroredCallback(cx, stream, source,
                                                      stream->embeddingFlags(), error);
     }
 
     return true;
 }
 
@@ -1920,36 +1935,39 @@ ReadableStreamErrorInternal(JSContext* c
  * of this function's operation is created in the current cx compartment.
  */
 static MOZ_MUST_USE bool
 ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx, Handle<ReadableStream*> stream,
                                            HandleValue chunk, bool done)
 {
     // Step 1: Let reader be stream.[[reader]].
     Rooted<ReadableStreamReader*> reader(cx, ReaderFromStream(cx, stream));
-    if (!reader)
+    if (!reader) {
         return false;
+    }
 
     // Step 2: Let readIntoRequest be the first element of
     //         reader.[[readIntoRequests]].
     // Step 3: Remove readIntoRequest from reader.[[readIntoRequests]], shifting
     //         all other elements downward (so that the second becomes the first,
     //         and so on).
     RootedValue val(cx, reader->getFixedSlot(ReaderSlot_Requests));
     RootedNativeObject readIntoRequests(cx, &val.toObject().as<NativeObject>());
     RootedObject readIntoRequest(cx, ShiftFromList<JSObject>(cx, readIntoRequests));
     MOZ_ASSERT(readIntoRequest);
-    if (!cx->compartment()->wrap(cx, &readIntoRequest))
+    if (!cx->compartment()->wrap(cx, &readIntoRequest)) {
         return false;
+    }
 
     // Step 4: Resolve readIntoRequest.[[promise]] with
     //         ! CreateIterResultObject(chunk, done).
     RootedValue wrappedChunk(cx, chunk);
-    if (!cx->compartment()->wrap(cx, &wrappedChunk))
+    if (!cx->compartment()->wrap(cx, &wrappedChunk)) {
         return false;
+    }
     RootedObject iterResult(cx, CreateIterResultObject(cx, wrappedChunk, done));
     if (!iterResult) {
         return false;
     }
     val = ObjectValue(*iterResult);
     return ResolvePromise(cx, readIntoRequest, val);
 }
 
@@ -1993,35 +2011,37 @@ ReadableStreamHasDefaultReader(JSContext
     if (stream->getFixedSlot(StreamSlot_Reader).isUndefined()) {
         *result = false;
         return true;
     }
 
     // Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false.
     // Step 4: Return true.
     JSObject* readerObj = ReaderFromStream(cx, stream);
-    if (!readerObj)
+    if (!readerObj) {
         return false;
+    }
 
     *result = readerObj->is<ReadableStreamDefaultReader>();
     return true;
 }
 #endif // DEBUG
 
 static MOZ_MUST_USE bool
 ReadableStreamGetReaderMode(JSContext* cx, ReadableStream* stream, ReaderMode* mode)
 {
     if (stream->getFixedSlot(StreamSlot_Reader).isUndefined()) {
         *mode = ReaderMode::None;
         return true;
     }
 
     JSObject* readerObj = ReaderFromStream(cx, stream);
-    if (!readerObj)
+    if (!readerObj) {
         return false;
+    }
 
     *mode = ReaderMode::Default;
 
     return true;
 }
 
 static MOZ_MUST_USE bool
 ReadableStreamReaderGenericInitialize(JSContext* cx,
@@ -2275,18 +2295,19 @@ ReadableStreamControllerCallPullIfNeeded
  */
 static MOZ_MUST_USE JSObject*
 ReadableStreamReaderGenericCancel(JSContext* cx, Handle<ReadableStreamReader*> reader,
                                   HandleValue reason)
 {
     // Step 1: Let stream be reader.[[ownerReadableStream]].
     // Step 2: Assert: stream is not undefined (implicit).
     Rooted<ReadableStream*> stream(cx, StreamFromReader(cx, reader));
-    if (!stream)
+    if (!stream) {
         return nullptr;
+    }
 
     // Step 3: Return ! ReadableStreamCancel(stream, reason).
     return ReadableStream::cancel(cx, stream, reason);
 }
 
 /**
  * Streams spec, 3.7.4. ReadableStreamReaderGenericInitialize ( reader, stream )
  *
@@ -2296,23 +2317,25 @@ ReadableStreamReaderGenericCancel(JSCont
 static MOZ_MUST_USE bool
 ReadableStreamReaderGenericInitialize(JSContext* cx, Handle<ReadableStreamReader*> reader,
                                       Handle<ReadableStream*> stream)
 {
     // Step 1: Set reader.[[ownerReadableStream]] to stream.
     // Step 2: Set stream.[[reader]] to reader.
     if (!IsObjectInContextCompartment(stream, cx)) {
         RootedObject wrappedStream(cx, stream);
-        if (!cx->compartment()->wrap(cx, &wrappedStream))
+        if (!cx->compartment()->wrap(cx, &wrappedStream)) {
             return false;
+        }
         reader->setFixedSlot(ReaderSlot_Stream, ObjectValue(*wrappedStream));
         AutoRealm ar(cx, stream);
         RootedObject wrappedReader(cx, reader);
-        if (!cx->compartment()->wrap(cx, &wrappedReader))
+        if (!cx->compartment()->wrap(cx, &wrappedReader)) {
             return false;
+        }
         stream->setFixedSlot(StreamSlot_Reader, ObjectValue(*wrappedReader));
     } else {
         reader->setFixedSlot(ReaderSlot_Stream, ObjectValue(*stream));
         stream->setFixedSlot(StreamSlot_Reader, ObjectValue(*reader));
     }
 
     // Step 3: If stream.[[state]] is "readable",
     RootedObject promise(cx);
@@ -2328,18 +2351,19 @@ ReadableStreamReaderGenericInitialize(JS
     } else {
         // Step b: Otherwise,
         // Step i: Assert: stream.[[state]] is "errored".
         MOZ_ASSERT(stream->errored());
 
         // Step ii: Set reader.[[closedPromise]] to a new promise rejected with
         //          stream.[[storedError]].
         RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
-        if (!cx->compartment()->wrap(cx, &storedError))
+        if (!cx->compartment()->wrap(cx, &storedError)) {
             return false;
+        }
         promise = PromiseObject::unforgeableReject(cx, storedError);
     }
 
     if (!promise) {
         return false;
     }
 
     reader->setFixedSlot(ReaderSlot_ClosedPromise, ObjectValue(*promise));
@@ -2352,18 +2376,19 @@ ReadableStreamReaderGenericInitialize(JS
  * Note: can operate on unwrapped ReadableStream reader instances from
  * another compartment.
  */
 static MOZ_MUST_USE bool
 ReadableStreamReaderGenericRelease(JSContext* cx, Handle<ReadableStreamReader*> reader)
 {
     // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined.
     Rooted<ReadableStream*> stream(cx, StreamFromReader(cx, reader));
-    if (!stream)
+    if (!stream) {
         return false;
+    }
 
     // Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader.
     MOZ_ASSERT(ReaderFromStream(cx, stream) == reader);
 
     // Create an exception to reject promises with below. We don't have a
     // clean way to do this, unfortunately.
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAMREADER_RELEASED);
     RootedValue exn(cx);
@@ -2384,18 +2409,19 @@ ReadableStreamReaderGenericRelease(JSCon
         Value val = reader->getFixedSlot(ReaderSlot_ClosedPromise);
         Rooted<PromiseObject*> closedPromise(cx, &val.toObject().as<PromiseObject>());
         if (closedPromise->compartment() != cx->compartment()) {
             ar.emplace(cx, closedPromise);
             if (!cx->compartment()->wrap(cx, &exn)) {
                 return false;
             }
         }
-        if (!PromiseObject::reject(cx, closedPromise, exn))
+        if (!PromiseObject::reject(cx, closedPromise, exn)) {
             return false;
+        }
     } else {
         // Step 4: Otherwise, set reader.[[closedPromise]] to a new promise rejected
         //         with a TypeError exception.
         RootedObject closedPromise(cx, PromiseObject::unforgeableReject(cx, exn));
         if (!closedPromise) {
             return false;
         }
         if (reader->compartment() != cx->compartment()) {
@@ -2426,18 +2452,19 @@ ReadableStreamControllerPullSteps(JSCont
  * another compartment.
  */
 /* static */ MOZ_MUST_USE JSObject*
 ReadableStreamDefaultReader::read(JSContext* cx, Handle<ReadableStreamDefaultReader*> reader)
 {
     // Step 1: Let stream be reader.[[ownerReadableStream]].
     // Step 2: Assert: stream is not undefined.
     Rooted<ReadableStream*> stream(cx, StreamFromReader(cx, reader));
-    if (!stream)
+    if (!stream) {
         return nullptr;
+    }
 
     // Step 3: Set stream.[[disturbed]] to true.
     SetStreamState(stream, StreamState(stream) | ReadableStream::Disturbed);
 
     // Step 4: If stream.[[state]] is "closed", return a new promise resolved with
     //         ! CreateIterResultObject(undefined, true).
     if (stream->closed()) {
         RootedObject iterResult(cx, CreateIterResultObject(cx, UndefinedHandleValue, true));
@@ -2447,18 +2474,19 @@ ReadableStreamDefaultReader::read(JSCont
         RootedValue iterResultVal(cx, ObjectValue(*iterResult));
         return PromiseObject::unforgeableResolve(cx, iterResultVal);
     }
 
     // Step 5: If stream.[[state]] is "errored", return a new promise rejected with
     //         stream.[[storedError]].
     if (stream->errored()) {
         RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
-        if (!cx->compartment()->wrap(cx, &storedError))
+        if (!cx->compartment()->wrap(cx, &storedError)) {
             return nullptr;
+        }
         return PromiseObject::unforgeableReject(cx, storedError);
     }
 
     // Step 6: Assert: stream.[[state]] is "readable".
     MOZ_ASSERT(stream->readable());
 
     // Step 7: Return ! stream.[[readableStreamController]].[[PullSteps]]().
     Rooted<ReadableStreamController*> controller(cx, ControllerFromStream(stream));
@@ -2890,18 +2918,19 @@ ReadableStreamControllerCancelSteps(JSCo
         RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
 
         if (pendingPullIntos->getDenseInitializedLength() != 0) {
             // Step a: Let firstDescriptor be the first element of
             //         this.[[pendingPullIntos]].
             // Step b: Set firstDescriptor.[[bytesFilled]] to 0.
             PullIntoDescriptor* descriptor;
             descriptor = ToUnwrapped<PullIntoDescriptor>(cx, PeekList<JSObject>(pendingPullIntos));
-            if (!descriptor)
+            if (!descriptor) {
                 return nullptr;
+            }
             descriptor->setBytesFilled(0);
         }
     }
 
     RootedValue underlyingSource(cx);
     underlyingSource = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
 
     // Step 1 of 3.8.5.1, step 2 of 3.10.5.1: Perform ! ResetQueue(this).
@@ -2928,36 +2957,39 @@ ReadableStreamControllerCancelSteps(JSCo
     if (ControllerFlags(controller) & ControllerFlag_ExternalSource) {
         bool needsWrapping = controller->compartment() != cx->compartment();
         RootedValue rval(cx);
         {
             RootedValue wrappedReason(cx, reason);
             mozilla::Maybe<AutoRealm> ar;
             if (needsWrapping) {
                 ar.emplace(cx, controller);
-                if (!cx->compartment()->wrap(cx, &wrappedReason))
+                if (!cx->compartment()->wrap(cx, &wrappedReason)) {
                     return nullptr;
+                }
             }
             void* source = underlyingSource.toPrivate();
             Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
             rval = cx->runtime()->readableStreamCancelCallback(cx, stream, source,
                                                                 stream->embeddingFlags(),
                                                                 wrappedReason);
         }
 
-        if (needsWrapping && !cx->compartment()->wrap(cx, &rval))
+        if (needsWrapping && !cx->compartment()->wrap(cx, &rval)) {
             return nullptr;
+        }
         return PromiseObject::unforgeableResolve(cx, rval);
     }
 
     // If the stream and its controller aren't in the cx compartment, we have
     // to ensure that the underlying source is correctly wrapped before
     // operating on it.
-    if (!cx->compartment()->wrap(cx, &underlyingSource))
+    if (!cx->compartment()->wrap(cx, &underlyingSource)) {
         return nullptr;
+    }
 
     return PromiseInvokeOrNoop(cx, underlyingSource, cx->names().cancel, reason);
 }
 
 inline static MOZ_MUST_USE bool
 DequeueValue(JSContext* cx, Handle<ReadableStreamController*> container, MutableHandleValue chunk);
 
 /**
@@ -3001,18 +3033,19 @@ ReadableStreamDefaultControllerPullSteps
         // Step c: Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
         else {
         if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) {
             return nullptr;
         }
         }
 
         // Step d: Return a promise resolved with ! CreateIterResultObject(chunk, false).
-        if (!cx->compartment()->wrap(cx, &chunk))
+        if (!cx->compartment()->wrap(cx, &chunk)) {
             return nullptr;
+        }
         RootedObject iterResultObj(cx, CreateIterResultObject(cx, chunk, false));
         if (!iterResultObj) {
             return nullptr;
         }
         RootedValue iterResult(cx, ObjectValue(*iterResultObj));
         return PromiseObject::unforgeableResolve(cx, iterResult);
     }
 
@@ -3036,18 +3069,19 @@ ReadableStreamDefaultControllerPullSteps
 static bool
 ControllerPullHandler(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedValue controllerVal(cx, args.callee().as<JSFunction>().getExtendedSlot(0));
     Rooted<ReadableStreamController*> controller(cx);
     controller = ToUnwrapped<ReadableStreamController>(cx, controllerVal);
-    if (!controller)
+    if (!controller) {
         return false;
+    }
 
     uint32_t flags = ControllerFlags(controller);
 
     // Step a: Set controller.[[pulling]] to false.
     // Step b.i: Set controller.[[pullAgain]] to false.
     RemoveControllerFlags(controller, ControllerFlag_Pulling | ControllerFlag_PullAgain);
 
     // Step b: If controller.[[pullAgain]] is true,
@@ -3068,18 +3102,19 @@ static bool
 ControllerPullFailedHandler(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     HandleValue e = args.get(0);
 
     RootedValue controllerVal(cx, args.callee().as<JSFunction>().getExtendedSlot(0));
     Rooted<ReadableStreamController*> controller(cx);
     controller = ToUnwrapped<ReadableStreamController>(cx, controllerVal);
-    if (!controller)
+    if (!controller) {
         return false;
+    }
 
     // Step a: If controller.[[controlledReadableStream]].[[state]] is "readable",
     //         perform ! ReadableByteStreamControllerError(controller, e).
     if (StreamFromController(controller)->readable()) {
         if (!ReadableStreamControllerError(cx, controller, e)) {
             return false;
         }
     }
@@ -3127,18 +3162,19 @@ ReadableStreamControllerCallPullIfNeeded
     MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_PullAgain));
 
     // Step 5: Set controller.[[pulling]] to true.
     AddControllerFlags(controller, ControllerFlag_Pulling);
 
     // Step 6: Let pullPromise be
     //         ! PromiseInvokeOrNoop(controller.[[underlyingByteSource]], "pull", controller).
     RootedObject wrappedController(cx, controller);
-    if (!cx->compartment()->wrap(cx, &wrappedController))
+    if (!cx->compartment()->wrap(cx, &wrappedController)) {
         return false;
+    }
     RootedValue controllerVal(cx, ObjectValue(*wrappedController));
     RootedValue underlyingSource(cx);
     underlyingSource = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
     RootedObject pullPromise(cx);
 
     if (IsMaybeWrapped<TeeState>(underlyingSource)) {
         Rooted<TeeState*> teeState(cx);
         teeState = &UncheckedUnwrap(&underlyingSource.toObject())->as<TeeState>();
@@ -3292,18 +3328,19 @@ ReadableStreamDefaultControllerEnqueue(J
         RootedValue chunkSize(cx, NumberValue(1));
         bool success = true;
 
         // Step b: If controller.[[strategySize]] is not undefined,
         RootedValue strategySize(cx);
         strategySize = controller->getFixedSlot(DefaultControllerSlot_StrategySize);
         if (!strategySize.isUndefined()) {
             // Step i: Set chunkSize to Call(stream.[[strategySize]], undefined, chunk).
-            if (!cx->compartment()->wrap(cx, &strategySize))
+            if (!cx->compartment()->wrap(cx, &strategySize)) {
                 return false;
+            }
             success = Call(cx, strategySize, UndefinedHandleValue, chunk, &chunkSize);
         }
 
         // Step c: Let enqueueResult be
         //         EnqueueValueWithSize(controller, chunk, chunkSize).
         if (success) {
             success = EnqueueValueWithSize(cx, controller, chunk, chunkSize);
         }
@@ -3709,18 +3746,19 @@ static MOZ_MUST_USE JSObject*
 ReadableByteStreamControllerPullSteps(JSContext* cx, Handle<ReadableStreamController*> controller)
 {
     // Step 1: Let stream be this.[[controlledReadableStream]].
     Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
 
     // Step 2: Assert: ! ReadableStreamHasDefaultReader(stream) is true.
 #ifdef DEBUG
     bool result;
-    if (!ReadableStreamHasDefaultReader(cx, stream, &result))
+    if (!ReadableStreamHasDefaultReader(cx, stream, &result)) {
         return nullptr;
+    }
     MOZ_ASSERT(result);
 #endif
 
     RootedValue val(cx);
     // Step 3: If this.[[queueTotalSize]] > 0,
     double queueTotalSize = QueueSize(controller);
     if (queueTotalSize > 0) {
         // Step 3.a: Assert: ! ReadableStreamGetNumReadRequests(_stream_) is 0.
@@ -3755,27 +3793,29 @@ ReadableByteStreamControllerPullSteps(JS
         } else {
             // Step 3.b: Let entry be the first element of this.[[queue]].
             // Step 3.c: Remove entry from this.[[queue]], shifting all other elements
             //           downward (so that the second becomes the first, and so on).
             val = controller->getFixedSlot(QueueContainerSlot_Queue);
             RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
             Rooted<ByteStreamChunk*> entry(cx);
             entry = ToUnwrapped<ByteStreamChunk>(cx, ShiftFromList<JSObject>(cx, queue));
-            if (!entry)
+            if (!entry) {
                 return nullptr;
+            }
 
             queueTotalSize = queueTotalSize - entry->byteLength();
 
             // Step 3.f: Let view be ! Construct(%Uint8Array%, « entry.[[buffer]],
             //                                   entry.[[byteOffset]], entry.[[byteLength]] »).
             // (reordered)
             RootedObject buffer(cx, entry->buffer());
-            if (!cx->compartment()->wrap(cx, &buffer))
+            if (!cx->compartment()->wrap(cx, &buffer)) {
                 return nullptr;
+            }
 
             uint32_t byteOffset = entry->byteOffset();
             view = JS_NewUint8ArrayWithBuffer(cx, buffer, byteOffset, entry->byteLength());
             if (!view) {
                 return nullptr;
             }
         }
 
@@ -3899,18 +3939,19 @@ ReadableByteStreamControllerInvalidateBY
  */
 static MOZ_MUST_USE bool
 ReadableByteStreamControllerClearPendingPullIntos(JSContext* cx,
                                                   Handle<ReadableStreamController*> controller)
 {
     MOZ_ASSERT(controller->is<ReadableByteStreamController>());
 
     // Step 1: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller).
-    if (!ReadableByteStreamControllerInvalidateBYOBRequest(cx, controller))
+    if (!ReadableByteStreamControllerInvalidateBYOBRequest(cx, controller)) {
         return false;
+    }
 
     // Step 2: Set controller.[[pendingPullIntos]] to a new empty List.
     return SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos);
 }
 
 /**
  * Streams spec, 3.12.5. ReadableByteStreamControllerClose ( controller )
  *
@@ -3943,18 +3984,19 @@ ReadableByteStreamControllerClose(JSCont
     RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
     RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
     if (pendingPullIntos->getDenseInitializedLength() != 0) {
         // Step a: Let firstPendingPullInto be the first element of
         //         controller.[[pendingPullIntos]].
         Rooted<PullIntoDescriptor*> firstPendingPullInto(cx);
         firstPendingPullInto = ToUnwrapped<PullIntoDescriptor>(cx,
                                                                PeekList<JSObject>(pendingPullIntos));
-        if (!firstPendingPullInto)
+        if (!firstPendingPullInto) {
             return false;
+        }
 
         // Step b: If firstPendingPullInto.[[bytesFilled]] > 0,
         if (firstPendingPullInto->bytesFilled() > 0) {
             // Step i: Let e be a new TypeError exception.
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                       JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL);
             RootedValue e(cx);
             // Not much we can do about uncatchable exceptions, just bail.
@@ -4319,18 +4361,19 @@ inline static MOZ_MUST_USE bool
 AppendToListAtSlot(JSContext* cx, HandleNativeObject container, uint32_t slot, HandleObject obj)
 {
     RootedValue val(cx, container->getFixedSlot(slot));
     RootedNativeObject list(cx, &val.toObject().as<NativeObject>());
 
     val = ObjectValue(*obj);
 
     AutoRealm ar(cx, list);
-    if (!cx->compartment()->wrap(cx, &val))
+    if (!cx->compartment()->wrap(cx, &val)) {
         return false;
+    }
     return AppendToList(cx, list, val);
 }
 
 
 /**
  * Streams spec, 6.4.1. InvokeOrNoop ( O, P, args )
  */
 inline static MOZ_MUST_USE bool
@@ -4421,28 +4464,30 @@ ValidateAndNormalizeQueuingStrategy(JSCo
     return true;
 }
 
 MOZ_MUST_USE bool
 js::ReadableStreamReaderCancel(JSContext* cx, HandleObject readerObj, HandleValue reason)
 {
     Rooted<ReadableStreamReader*> reader(cx, &readerObj->as<ReadableStreamReader>());
     ReadableStream* stream = StreamFromReader(cx, reader);
-    if (!stream)
+    if (!stream) {
         return false;
+    }
     return ReadableStreamReaderGenericCancel(cx, reader, reason);
 }
 
 MOZ_MUST_USE bool
 js::ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject readerObj)
 {
     Rooted<ReadableStreamReader*> reader(cx, &readerObj->as<ReadableStreamReader>());
     ReadableStream* stream = StreamFromReader(cx, reader);
-    if (!stream)
+    if (!stream) {
         return false;
+    }
     MOZ_ASSERT(ReadableStreamGetNumReadRequests(stream) == 0);
     return ReadableStreamReaderGenericRelease(cx, reader);
 }
 
 MOZ_MUST_USE bool
 ReadableStream::enqueue(JSContext* cx, Handle<ReadableStream*> stream, HandleValue chunk)
 {
     Rooted<ReadableStreamDefaultController*> controller(cx);
@@ -4554,18 +4599,19 @@ ReadableStream::updateDataAvailableFromS
     // Reordered because for externally-sourced streams it applies regardless
     // of reader type.
     if (ReadableStreamGetNumReadRequests(stream) == 0) {
         return true;
     }
 
     // Step 8: If ! ReadableStreamHasDefaultReader(stream) is true
     ReaderMode readerMode;
-    if (!ReadableStreamGetReaderMode(cx, stream, &readerMode))
+    if (!ReadableStreamGetReaderMode(cx, stream, &readerMode)) {
         return false;
+    }
 
     if (readerMode == ReaderMode::Default) {
         // Step b: Otherwise,
         // Step i: Assert: controller.[[queue]] is empty.
         MOZ_ASSERT(oldAvailableData == 0);
 
         // Step ii: Let transferredView be
         //          ! Construct(%Uint8Array%, transferredBuffer, byteOffset, byteLength).
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -769,17 +769,17 @@ WasmTextToBinary(JSContext* cx, unsigned
         return false;
     }
 
     RootedObject binary(cx, JS_NewUint8Array(cx, bytes.length()));
     if (!binary) {
         return false;
     }
 
-    memcpy(binary->as<TypedArrayObject>().viewDataUnshared(), bytes.begin(), bytes.length());
+    memcpy(binary->as<TypedArrayObject>().dataPointerUnshared(), bytes.begin(), bytes.length());
 
     if (!withOffsets) {
         args.rval().setObject(*binary);
         return true;
     }
 
     RootedObject obj(cx, JS_NewPlainObject(cx));
     if (!obj) {
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -9170,17 +9170,17 @@ IonBuilder::addTypedArrayLengthAndData(M
         if (objConst->type() == MIRType::Object) {
             tarr = &objConst->toObject();
         }
     } else if (TemporaryTypeSet* types = obj->resultTypeSet()) {
         tarr = types->maybeSingleton();
     }
 
     if (tarr) {
-        SharedMem<void*> data = tarr->as<TypedArrayObject>().viewDataEither();
+        SharedMem<void*> data = tarr->as<TypedArrayObject>().dataPointerEither();
         // Bug 979449 - Optimistically embed the elements and use TI to
         //              invalidate if we move them.
         bool isTenured = !tarr->runtimeFromMainThread()->gc.nursery().isInside(data);
         if (isTenured && tarr->isSingleton()) {
             // The 'data' pointer of TypedArrayObject can change in rare circumstances
             // (ArrayBufferObject::changeContents).
             TypeSet::ObjectKey* tarrKey = TypeSet::ObjectKey::get(tarr);
             if (!tarrKey->unknownProperties()) {
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -567,17 +567,17 @@ js::powi(double x, int32_t y)
         }
         m *= m;
     }
 }
 
 double
 js::ecmaPow(double x, double y)
 {
-    AutoUnsafeCallWithABI unsafe;
+    AutoUnsafeCallWithABI unsafe(UnsafeABIStrictness::AllowPendingExceptions);
 
     /*
      * Use powi if the exponent is an integer-valued double. We don't have to
      * check for NaN since a comparison with NaN is always false.
      */
     int32_t yi;
     if (NumberEqualsInt32(y, &yi)) {
         return powi(x, yi);
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -362,16 +362,17 @@ UNIFIED_SOURCES += [
     'util/CompleteFile.cpp',
     'util/NativeStack.cpp',
     'util/Printf.cpp',
     'util/StringBuffer.cpp',
     'util/Text.cpp',
     'util/Unicode.cpp',
     'vm/ArgumentsObject.cpp',
     'vm/ArrayBufferObject.cpp',
+    'vm/ArrayBufferViewObject.cpp',
     'vm/AsyncFunction.cpp',
     'vm/AsyncIteration.cpp',
     'vm/BytecodeUtil.cpp',
     'vm/Caches.cpp',
     'vm/CallNonGenericMethod.cpp',
     'vm/CharacterEncoding.cpp',
     'vm/CodeCoverage.cpp',
     'vm/Compartment.cpp',
--- a/js/src/shell/OSObject.cpp
+++ b/js/src/shell/OSObject.cpp
@@ -216,17 +216,17 @@ FileAsTypedArray(JSContext* cx, JS::Hand
                 // buffer.)
                 pathname = JS_EncodeStringToUTF8(cx, pathnameStr);
                 if (!pathname) {
                     return nullptr;
                 }
                 JS_ReportErrorUTF8(cx, "can't read %s: shared memory buffer", pathname.get());
                 return nullptr;
             }
-            char* buf = static_cast<char*>(ta.viewDataUnshared());
+            char* buf = static_cast<char*>(ta.dataPointerUnshared());
             size_t cc = fread(buf, 1, len, file);
             if (cc != len) {
                 if (ptrdiff_t(cc) < 0) {
                     /*
                      * Use Latin1 variant here because the encoding of the return
                      * value of strerror function can be non-UTF-8.
                      */
                     JS_ReportErrorLatin1(cx, "can't read %s: %s", pathname.get(), strerror(errno));
@@ -365,17 +365,17 @@ osfile_writeTypedArrayToFile(JSContext* 
         // See further comments in FileAsTypedArray, above.
         filename = JS_EncodeStringToUTF8(cx, str);
         if (!filename) {
             return false;
         }
         JS_ReportErrorUTF8(cx, "can't write %s: shared memory buffer", filename.get());
         return false;
     }
-    void* buf = obj->viewDataUnshared();
+    void* buf = obj->dataPointerUnshared();
     if (fwrite(buf, obj->bytesPerElement(), obj->length(), file) != obj->length() ||
         !autoClose.release())
     {
         filename = JS_EncodeStringToUTF8(cx, str);
         if (!filename) {
             return false;
         }
         JS_ReportErrorUTF8(cx, "can't write %s", filename.get());
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -504,33 +504,32 @@ ArrayBufferObject::detach(JSContext* cx,
             oomUnsafe.crash("ArrayBufferObject::detach");
         }
         MarkObjectGroupFlags(cx, cx->global(), OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER);
         cx->zone()->detachedTypedObjects = 1;
     }
 
     // Update all views of the buffer to account for the buffer having been
     // detached, and clear the buffer's data and list of views.
+    //
+    // Typed object buffers are not exposed and cannot be detached.
 
     auto& innerViews = ObjectRealm::get(buffer).innerViews.get();
     if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(buffer)) {
         for (size_t i = 0; i < views->length(); i++) {
-            NoteViewBufferWasDetached((*views)[i], newContents, cx);
+            JSObject* view = (*views)[i];
+            NoteViewBufferWasDetached(&view->as<ArrayBufferViewObject>(), newContents, cx);
         }
         innerViews.removeViews(buffer);
     }
-    if (buffer->firstView()) {
-        if (buffer->forInlineTypedObject()) {
-            // The buffer points to inline data in its first view, so to keep
-            // this pointer alive we don't clear out the first view.
-            MOZ_ASSERT(buffer->firstView()->is<InlineTransparentTypedObject>());
-        } else {
-            NoteViewBufferWasDetached(buffer->firstView(), newContents, cx);
-            buffer->setFirstView(nullptr);
-        }
+    if (JSObject* view = buffer->firstView()) {
+        MOZ_ASSERT(!buffer->forInlineTypedObject(),
+                   "Typed object buffers cannot be detached");
+        NoteViewBufferWasDetached(&view->as<ArrayBufferViewObject>(), newContents, cx);
+        buffer->setFirstView(nullptr);
     }
 
     if (newContents.data() != buffer->dataPointer()) {
         buffer->setNewData(cx->runtime()->defaultFreeOp(), newContents, OwnsData);
     }
 
     buffer->setByteLength(0);
     buffer->setIsDetached();
@@ -585,21 +584,23 @@ ArrayBufferObject::changeContents(JSCont
     // Change buffer contents.
     uint8_t* oldDataPointer = dataPointer();
     setNewData(cx->runtime()->defaultFreeOp(), newContents, ownsState);
 
     // Update all views.
     auto& innerViews = ObjectRealm::get(this).innerViews.get();
     if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) {
         for (size_t i = 0; i < views->length(); i++) {
-            changeViewContents(cx, (*views)[i], oldDataPointer, newContents);
+            JSObject* view = (*views)[i];
+            changeViewContents(cx, &view->as<ArrayBufferViewObject>(), oldDataPointer,
+                               newContents);
         }
     }
-    if (firstView()) {
-        changeViewContents(cx, firstView(), oldDataPointer, newContents);
+    if (JSObject* view = firstView()) {
+        changeViewContents(cx, &view->as<ArrayBufferViewObject>(), oldDataPointer, newContents);
     }
 }
 
 /*
  * [SMDOC] WASM Linear Memory structure
  *
  * Wasm Raw Buf Linear Memory Structure
  *
@@ -1476,54 +1477,53 @@ ArrayBufferObject::objectMoved(JSObject*
     // Fix up possible inline data pointer.
     if (src.hasInlineData()) {
         dst.setFixedSlot(DATA_SLOT, PrivateValue(dst.inlineDataPointer()));
     }
 
     return 0;
 }
 
-ArrayBufferViewObject*
+JSObject*
 ArrayBufferObject::firstView()
 {
     return getFixedSlot(FIRST_VIEW_SLOT).isObject()
-        ? static_cast<ArrayBufferViewObject*>(&getFixedSlot(FIRST_VIEW_SLOT).toObject())
+        ? &getFixedSlot(FIRST_VIEW_SLOT).toObject()
         : nullptr;
 }
 
 void
-ArrayBufferObject::setFirstView(ArrayBufferViewObject* view)
+ArrayBufferObject::setFirstView(JSObject* view)
 {
+    MOZ_ASSERT_IF(view,
+                  view->is<ArrayBufferViewObject>() || view->is<TypedObject>());
     setFixedSlot(FIRST_VIEW_SLOT, ObjectOrNullValue(view));
 }
 
 bool
-ArrayBufferObject::addView(JSContext* cx, JSObject* viewArg)
+ArrayBufferObject::addView(JSContext* cx, JSObject* view)
 {
-    // Note: we don't pass in an ArrayBufferViewObject as the argument due to
-    // tricky inheritance in the various view classes. View classes do not
-    // inherit from ArrayBufferViewObject so won't be upcast automatically.
-    MOZ_ASSERT(viewArg->is<ArrayBufferViewObject>() || viewArg->is<TypedObject>());
-    ArrayBufferViewObject* view = static_cast<ArrayBufferViewObject*>(viewArg);
+    MOZ_ASSERT(view->is<ArrayBufferViewObject>() || view->is<TypedObject>());
 
     if (!firstView()) {
         setFirstView(view);
         return true;
     }
+
     return ObjectRealm::get(this).innerViews.get().addView(cx, this, view);
 }
 
 /*
  * InnerViewTable
  */
 
 constexpr size_t VIEW_LIST_MAX_LENGTH = 500;
 
 bool
-InnerViewTable::addView(JSContext* cx, ArrayBufferObject* buffer, ArrayBufferViewObject* view)
+InnerViewTable::addView(JSContext* cx, ArrayBufferObject* buffer, JSObject* view)
 {
     // ArrayBufferObject entries are only added when there are multiple views.
     MOZ_ASSERT(buffer->firstView());
 
     Map::AddPtr p = map.lookupForAdd(buffer);
 
     MOZ_ASSERT(!gc::IsInsideNursery(buffer));
     bool addToNursery = nurseryKeysValid && gc::IsInsideNursery(view);
@@ -1653,181 +1653,23 @@ InnerViewTable::sizeOfExcludingThis(mozi
         vectorSize += e.front().value().sizeOfExcludingThis(mallocSizeOf);
     }
 
     return vectorSize
          + map.shallowSizeOfExcludingThis(mallocSizeOf)
          + nurseryKeys.sizeOfExcludingThis(mallocSizeOf);
 }
 
-/*
- * ArrayBufferViewObject
- */
-
-/*
- * This method is used to trace TypedArrayObjects and DataViewObjects. We need
- * a custom tracer to move the object's data pointer if its owner was moved and
- * stores its data inline.
- */
-/* static */ void
-ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg)
-{
-    NativeObject* obj = &objArg->as<NativeObject>();
-    HeapSlot& bufSlot = obj->getFixedSlotRef(TypedArrayObject::BUFFER_SLOT);
-    TraceEdge(trc, &bufSlot, "typedarray.buffer");
-
-    // Update obj's data pointer if it moved.
-    if (bufSlot.isObject()) {
-        if (IsArrayBuffer(&bufSlot.toObject())) {
-            ArrayBufferObject& buf = AsArrayBuffer(MaybeForwarded(&bufSlot.toObject()));
-            uint32_t offset = uint32_t(obj->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT).toInt32());
-            MOZ_ASSERT(offset <= INT32_MAX);
-
-            if (buf.forInlineTypedObject()) {
-                MOZ_ASSERT(buf.dataPointer() != nullptr);
-
-                // The data is inline with an InlineTypedObject associated with the
-                // buffer. Get a new address for the typed object if it moved.
-                JSObject* view = buf.firstView();
-
-                // Mark the object to move it into the tenured space.
-                TraceManuallyBarrieredEdge(trc, &view, "typed array nursery owner");
-                MOZ_ASSERT(view->is<InlineTypedObject>());
-                MOZ_ASSERT(view != obj);
-
-                size_t nfixed = obj->numFixedSlotsMaybeForwarded();
-                void* srcData = obj->getPrivate(nfixed);
-                void* dstData = view->as<InlineTypedObject>().inlineTypedMemForGC() + offset;
-                obj->setPrivateUnbarriered(nfixed, dstData);
-
-                // We can't use a direct forwarding pointer here, as there might
-                // not be enough bytes available, and other views might have data
-                // pointers whose forwarding pointers would overlap this one.
-                if (trc->isTenuringTracer()) {
-                    Nursery& nursery = trc->runtime()->gc.nursery();
-                    nursery.maybeSetForwardingPointer(trc, srcData, dstData, /* direct = */ false);
-                }
-            } else {
-                MOZ_ASSERT_IF(buf.dataPointer() == nullptr, offset == 0);
-
-                // The data may or may not be inline with the buffer. The buffer
-                // can only move during a compacting GC, in which case its
-                // objectMoved hook has already updated the buffer's data pointer.
-                size_t nfixed = obj->numFixedSlotsMaybeForwarded();
-                obj->setPrivateUnbarriered(nfixed, buf.dataPointer() + offset);
-            }
-        }
-    }
-}
-
-template <>
-bool
-JSObject::is<js::ArrayBufferViewObject>() const
-{
-    return is<DataViewObject>() || is<TypedArrayObject>();
-}
-
 template <>
 bool
 JSObject::is<js::ArrayBufferObjectMaybeShared>() const
 {
     return is<ArrayBufferObject>() || is<SharedArrayBufferObject>();
 }
 
-void
-ArrayBufferViewObject::notifyBufferDetached(JSContext* cx, void* newData)
-{
-    if (is<DataViewObject>()) {
-        if (as<DataViewObject>().isSharedMemory()) {
-            return;
-        }
-        as<DataViewObject>().notifyBufferDetached(newData);
-    } else if (is<TypedArrayObject>()) {
-        if (as<TypedArrayObject>().isSharedMemory()) {
-            return;
-        }
-        as<TypedArrayObject>().notifyBufferDetached(cx, newData);
-    } else {
-        as<OutlineTypedObject>().notifyBufferDetached(newData);
-    }
-}
-
-uint8_t*
-ArrayBufferViewObject::dataPointerUnshared(const JS::AutoRequireNoGC& nogc)
-{
-    if (is<DataViewObject>()) {
-        MOZ_ASSERT(!as<DataViewObject>().isSharedMemory());
-        return static_cast<uint8_t*>(as<DataViewObject>().dataPointerUnshared());
-    }
-    if (is<TypedArrayObject>()) {
-        MOZ_ASSERT(!as<TypedArrayObject>().isSharedMemory());
-        return static_cast<uint8_t*>(as<TypedArrayObject>().viewDataUnshared());
-    }
-    return as<TypedObject>().typedMem(nogc);
-}
-
-#ifdef DEBUG
-bool
-ArrayBufferViewObject::isSharedMemory()
-{
-    if (is<TypedArrayObject>()) {
-        return as<TypedArrayObject>().isSharedMemory();
-    }
-    return false;
-}
-#endif
-
-void
-ArrayBufferViewObject::setDataPointerUnshared(uint8_t* data)
-{
-    if (is<DataViewObject>()) {
-        MOZ_ASSERT(!as<DataViewObject>().isSharedMemory());
-        as<DataViewObject>().setPrivate(data);
-    } else if (is<TypedArrayObject>()) {
-        MOZ_ASSERT(!as<TypedArrayObject>().isSharedMemory());
-        as<TypedArrayObject>().setPrivate(data);
-    } else if (is<OutlineTypedObject>()) {
-        as<OutlineTypedObject>().setData(data);
-    } else {
-        MOZ_CRASH();
-    }
-}
-
-/* static */ ArrayBufferObjectMaybeShared*
-ArrayBufferViewObject::bufferObject(JSContext* cx, Handle<ArrayBufferViewObject*> thisObject)
-{
-    if (thisObject->is<TypedArrayObject>()) {
-        Rooted<TypedArrayObject*> typedArray(cx, &thisObject->as<TypedArrayObject>());
-        if (!TypedArrayObject::ensureHasBuffer(cx, typedArray)) {
-            return nullptr;
-        }
-        return thisObject->as<TypedArrayObject>().bufferEither();
-    }
-    MOZ_ASSERT(thisObject->is<DataViewObject>());
-    return &thisObject->as<DataViewObject>().arrayBufferEither();
-}
-
-/* JS Friend API */
-
-JS_FRIEND_API(bool)
-JS_IsArrayBufferViewObject(JSObject* obj)
-{
-    obj = CheckedUnwrap(obj);
-    return obj && obj->is<ArrayBufferViewObject>();
-}
-
-JS_FRIEND_API(JSObject*)
-js::UnwrapArrayBufferView(JSObject* obj)
-{
-    if (JSObject* unwrapped = CheckedUnwrap(obj)) {
-        return unwrapped->is<ArrayBufferViewObject>() ? unwrapped : nullptr;
-    }
-    return nullptr;
-}
-
 JS_FRIEND_API(uint32_t)
 JS_GetArrayBufferByteLength(JSObject* obj)
 {
     obj = CheckedUnwrap(obj);
     return obj ? AsArrayBuffer(obj).byteLength() : 0;
 }
 
 JS_FRIEND_API(uint8_t*)
@@ -2065,114 +1907,16 @@ JS_IsMappedArrayBufferObject(JSObject* o
     obj = CheckedUnwrap(obj);
     if (!obj) {
         return false;
     }
 
     return obj->is<ArrayBufferObject>() && obj->as<ArrayBufferObject>().isMapped();
 }
 
-JS_FRIEND_API(void*)
-JS_GetArrayBufferViewData(JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC&)
-{
-    obj = CheckedUnwrap(obj);
-    if (!obj) {
-        return nullptr;
-    }
-    if (obj->is<DataViewObject>()) {
-        DataViewObject& dv = obj->as<DataViewObject>();
-        *isSharedMemory = dv.isSharedMemory();
-        return dv.dataPointerEither().unwrap(/*safe - caller sees isSharedMemory flag*/);
-    }
-    TypedArrayObject& ta = obj->as<TypedArrayObject>();
-    *isSharedMemory = ta.isSharedMemory();
-    return ta.viewDataEither().unwrap(/*safe - caller sees isSharedMemory flag*/);
-}
-
-JS_FRIEND_API(JSObject*)
-JS_GetArrayBufferViewBuffer(JSContext* cx, HandleObject objArg, bool* isSharedMemory)
-{
-    AssertHeapIsIdle();
-    CHECK_THREAD(cx);
-    cx->check(objArg);
-
-    JSObject* obj = CheckedUnwrap(objArg);
-    if (!obj) {
-        return nullptr;
-    }
-    MOZ_ASSERT(obj->is<ArrayBufferViewObject>());
-
-    Rooted<ArrayBufferViewObject*> viewObject(cx, static_cast<ArrayBufferViewObject*>(obj));
-    ArrayBufferObjectMaybeShared* buffer = ArrayBufferViewObject::bufferObject(cx, viewObject);
-    *isSharedMemory = buffer->is<SharedArrayBufferObject>();
-    return buffer;
-}
-
-JS_FRIEND_API(uint32_t)
-JS_GetArrayBufferViewByteLength(JSObject* obj)
-{
-    obj = CheckedUnwrap(obj);
-    if (!obj) {
-        return 0;
-    }
-    return obj->is<DataViewObject>()
-           ? obj->as<DataViewObject>().byteLength()
-           : obj->as<TypedArrayObject>().byteLength();
-}
-
-JS_FRIEND_API(uint32_t)
-JS_GetArrayBufferViewByteOffset(JSObject* obj)
-{
-    obj = CheckedUnwrap(obj);
-    if (!obj) {
-        return 0;
-    }
-    return obj->is<DataViewObject>()
-           ? obj->as<DataViewObject>().byteOffset()
-           : obj->as<TypedArrayObject>().byteOffset();
-}
-
-JS_FRIEND_API(JSObject*)
-JS_GetObjectAsArrayBufferView(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data)
-{
-    if (!(obj = CheckedUnwrap(obj))) {
-        return nullptr;
-    }
-    if (!(obj->is<ArrayBufferViewObject>())) {
-        return nullptr;
-    }
-
-    js::GetArrayBufferViewLengthAndData(obj, length, isSharedMemory, data);
-    return obj;
-}
-
-JS_FRIEND_API(void)
-js::GetArrayBufferViewLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory,
-                                    uint8_t** data)
-{
-    MOZ_ASSERT(obj->is<ArrayBufferViewObject>());
-
-    *length = obj->is<DataViewObject>()
-              ? obj->as<DataViewObject>().byteLength()
-              : obj->as<TypedArrayObject>().byteLength();
-
-    if (obj->is<DataViewObject>()) {
-        DataViewObject& dv = obj->as<DataViewObject>();
-        *isSharedMemory = dv.isSharedMemory();
-        *data = static_cast<uint8_t*>(
-            dv.dataPointerEither().unwrap(/*safe - caller sees isShared flag*/));
-    }
-    else {
-        TypedArrayObject& ta = obj->as<TypedArrayObject>();
-        *isSharedMemory = ta.isSharedMemory();
-        *data = static_cast<uint8_t*>(
-            ta.viewDataEither().unwrap(/*safe - caller sees isShared flag*/));
-    }
-}
-
 JS_FRIEND_API(JSObject*)
 JS_GetObjectAsArrayBuffer(JSObject* obj, uint32_t* length, uint8_t** data)
 {
     if (!(obj = CheckedUnwrap(obj))) {
         return nullptr;
     }
     if (!IsArrayBuffer(obj)) {
         return nullptr;
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -47,29 +47,30 @@ bool ExtendBufferMapping(void* dataStart
 void UnmapBufferMemory(void* dataStart, size_t mappedSize);
 
 // Return the number of currently live mapped buffers.
 int32_t LiveMappedBufferCount();
 
 // The inheritance hierarchy for the various classes relating to typed arrays
 // is as follows.
 //
-// - NativeObject
-//   - ArrayBufferObjectMaybeShared
-//     - ArrayBufferObject
-//     - SharedArrayBufferObject
-//   - DataViewObject
-//   - TypedArrayObject (declared in vm/TypedArrayObject.h)
-//     - TypedArrayObjectTemplate
-//       - Int8ArrayObject
-//       - Uint8ArrayObject
-//       - ...
+//
 // - JSObject
-//   - ArrayBufferViewObject
 //   - TypedObject (declared in builtin/TypedObject.h)
+//   - NativeObject
+//     - ArrayBufferObjectMaybeShared
+//       - ArrayBufferObject
+//       - SharedArrayBufferObject
+//     - ArrayBufferViewObject
+//       - DataViewObject
+//       - TypedArrayObject (declared in vm/TypedArrayObject.h)
+//         - TypedArrayObjectTemplate
+//           - Int8ArrayObject
+//           - Uint8ArrayObject
+//           - ...
 //
 // Note that |TypedArrayObjectTemplate| is just an implementation
 // detail that makes implementing its various subclasses easier.
 //
 // ArrayBufferObject and SharedArrayBufferObject are unrelated data types:
 // the racy memory of the latter cannot substitute for the non-racy memory of
 // the former; the non-racy memory of the former cannot be used with the atomics;
 // the former can be detached and the latter not.  Hence they have been
@@ -320,32 +321,32 @@ class ArrayBufferObject : public ArrayBu
     static void addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf,
                                        JS::ClassInfo* info);
 
     // ArrayBufferObjects (strongly) store the first view added to them, while
     // later views are (weakly) stored in the compartment's InnerViewTable
     // below. Buffers usually only have one view, so this slot optimizes for
     // the common case. Avoiding entries in the InnerViewTable saves memory and
     // non-incrementalized sweep time.
-    ArrayBufferViewObject* firstView();
+    JSObject* firstView();
 
     bool addView(JSContext* cx, JSObject* view);
 
     void setNewData(FreeOp* fop, BufferContents newContents, OwnsState ownsState);
     void changeContents(JSContext* cx, BufferContents newContents, OwnsState ownsState);
 
     // Detach this buffer from its original memory.  (This necessarily makes
     // views of this buffer unusable for modifying that original memory.)
     static void
     detach(JSContext* cx, Handle<ArrayBufferObject*> buffer, BufferContents newContents);
 
   private:
     void changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
                             uint8_t* oldDataPointer, BufferContents newContents);
-    void setFirstView(ArrayBufferViewObject* view);
+    void setFirstView(JSObject* view);
 
     uint8_t* inlineDataPointer() const;
 
     struct FreeInfo {
         JS::BufferContentsFreeFunc freeFunc;
         void* freeUserData;
     };
     FreeInfo* freeInfo() const;
@@ -447,41 +448,16 @@ class ArrayBufferObject : public ArrayBu
 
 typedef Rooted<ArrayBufferObject*> RootedArrayBufferObject;
 typedef Handle<ArrayBufferObject*> HandleArrayBufferObject;
 typedef MutableHandle<ArrayBufferObject*> MutableHandleArrayBufferObject;
 
 bool CreateWasmBuffer(JSContext* cx, const wasm::Limits& memory,
                       MutableHandleArrayBufferObjectMaybeShared buffer);
 
-/*
- * ArrayBufferViewObject
- *
- * Common definitions shared by all array buffer views.
- */
-
-class ArrayBufferViewObject : public NativeObject
-{
-  public:
-    static ArrayBufferObjectMaybeShared* bufferObject(JSContext* cx, Handle<ArrayBufferViewObject*> obj);
-
-    void notifyBufferDetached(JSContext* cx, void* newData);
-
-#ifdef DEBUG
-    bool isSharedMemory();
-#endif
-
-    // By construction we only need unshared variants here.  See
-    // comments in ArrayBufferObject.cpp.
-    uint8_t* dataPointerUnshared(const JS::AutoRequireNoGC&);
-    void setDataPointerUnshared(uint8_t* data);
-
-    static void trace(JSTracer* trc, JSObject* obj);
-};
-
 bool
 ToClampedIndex(JSContext* cx, HandleValue v, uint32_t length, uint32_t* out);
 
 /*
  * Tests for ArrayBufferObject, like obj->is<ArrayBufferObject>().
  */
 bool IsArrayBuffer(HandleValue v);
 bool IsArrayBuffer(HandleObject obj);
@@ -581,17 +557,17 @@ template<> inline bool TypeIsUnsigned<ui
 template<> inline bool TypeIsUnsigned<uint16_t>() { return true; }
 template<> inline bool TypeIsUnsigned<uint32_t>() { return true; }
 
 // Per-compartment table that manages the relationship between array buffers
 // and the views that use their storage.
 class InnerViewTable
 {
   public:
-    typedef Vector<ArrayBufferViewObject*, 1, SystemAllocPolicy> ViewVector;
+    typedef Vector<JSObject*, 1, SystemAllocPolicy> ViewVector;
 
     friend class ArrayBufferObject;
 
   private:
     struct MapGCPolicy {
         static bool needsSweep(JSObject** key, ViewVector* value) {
             return InnerViewTable::sweepEntry(key, *value);
         }
@@ -623,17 +599,17 @@ class InnerViewTable
     Vector<JSObject*, 0, SystemAllocPolicy> nurseryKeys;
 
     // Whether nurseryKeys is a complete list.
     bool nurseryKeysValid;
 
     // Sweep an entry during GC, returning whether the entry should be removed.
     static bool sweepEntry(JSObject** pkey, ViewVector& views);
 
-    bool addView(JSContext* cx, ArrayBufferObject* obj, ArrayBufferViewObject* view);
+    bool addView(JSContext* cx, ArrayBufferObject* buffer, JSObject* view);
     ViewVector* maybeViewsUnbarriered(ArrayBufferObject* obj);
     void removeViews(ArrayBufferObject* obj);
 
   public:
     InnerViewTable()
       : nurseryKeysValid(true)
     {}
 
@@ -666,15 +642,11 @@ class MutableWrappedPtrOperations<InnerV
         return table().sizeOfExcludingThis(mallocSizeOf);
     }
 };
 
 } // namespace js
 
 template <>
 bool
-JSObject::is<js::ArrayBufferViewObject>() const;
-
-template <>
-bool
 JSObject::is<js::ArrayBufferObjectMaybeShared>() const;
 
 #endif // vm_ArrayBufferObject_h
copy from js/src/vm/ArrayBufferObject.cpp
copy to js/src/vm/ArrayBufferViewObject.cpp
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferViewObject.cpp
@@ -1,1813 +1,118 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "vm/ArrayBufferObject-inl.h"
-#include "vm/ArrayBufferObject.h"
-
-#include "mozilla/Alignment.h"
-#include "mozilla/CheckedInt.h"
-#include "mozilla/FloatingPoint.h"
-#include "mozilla/Maybe.h"
-#include "mozilla/PodOperations.h"
-#include "mozilla/TaggedAnonymousMemory.h"
+#include "vm/ArrayBufferViewObject.h"
 
-#include <string.h>
-#ifndef XP_WIN
-# include <sys/mman.h>
-#endif
-#ifdef MOZ_VALGRIND
-# include <valgrind/memcheck.h>
-#endif
-
-#include "jsapi.h"
-#include "jsfriendapi.h"
-#include "jsnum.h"
-#include "jstypes.h"
-#include "jsutil.h"
-
-#include "builtin/Array.h"
 #include "builtin/DataViewObject.h"
-#include "gc/Barrier.h"
-#include "gc/FreeOp.h"
-#include "gc/Memory.h"
-#include "js/Conversions.h"
-#include "js/MemoryMetrics.h"
-#include "js/Wrapper.h"
-#include "util/Windows.h"
-#include "vm/GlobalObject.h"
-#include "vm/Interpreter.h"
+#include "gc/Nursery.h"
 #include "vm/JSContext.h"
-#include "vm/JSObject.h"
-#include "vm/SharedArrayObject.h"
-#include "vm/WrapperObject.h"
-#include "wasm/WasmSignalHandlers.h"
-#include "wasm/WasmTypes.h"
+#include "vm/TypedArrayObject.h"
 
-#include "gc/Marking-inl.h"
 #include "gc/Nursery-inl.h"
-#include "vm/JSAtom-inl.h"
 #include "vm/NativeObject-inl.h"
-#include "vm/Shape-inl.h"
-
-using JS::ToInt32;
-
-using mozilla::Atomic;
-using mozilla::CheckedInt;
-using mozilla::Some;
-using mozilla::Maybe;
-using mozilla::Nothing;
-using mozilla::Unused;
 
 using namespace js;
 
 /*
- * Convert |v| to an array index for an array of length |length| per
- * the Typed Array Specification section 7.0, |subarray|. If successful,
- * the output value is in the range [0, length].
- */
-bool
-js::ToClampedIndex(JSContext* cx, HandleValue v, uint32_t length, uint32_t* out)
-{
-    int32_t result;
-    if (!ToInt32(cx, v, &result)) {
-        return false;
-    }
-    if (result < 0) {
-        result += length;
-        if (result < 0) {
-            result = 0;
-        }
-    } else if (uint32_t(result) > length) {
-        result = length;
-    }
-    *out = uint32_t(result);
-    return true;
-}
-
-// If there are too many 4GB buffers live we run up against system resource
-// exhaustion (address space or number of memory map descriptors), see
-// bug 1068684, bug 1073934 for details.  The limiting case seems to be
-// Windows Vista Home 64-bit, where the per-process address space is limited
-// to 8TB.  Thus we track the number of live objects, and set a limit of
-// 1000 live objects per process and we throw an OOM error if the per-process
-// limit is exceeded.
-//
-// Since the MaximumLiveMappedBuffers limit is not generally accounted for by
-// any existing GC-trigger heuristics, we need an extra heuristic for triggering
-// GCs when the caller is allocating memories rapidly without other garbage.
-// Thus, once the live buffer count crosses a certain threshold, we start
-// triggering GCs every N allocations. As we get close to the limit, perform
-// expensive non-incremental full GCs as a last-ditch effort to avoid
-// unnecessary failure. The *Sans use a ton of vmem for bookkeeping leaving a
-// lot less for the program so use a lower limit.
-
-#if defined(MOZ_TSAN) || defined(MOZ_ASAN)
-static const int32_t MaximumLiveMappedBuffers = 500;
-#else
-static const int32_t MaximumLiveMappedBuffers = 1000;
-#endif
-static const int32_t StartTriggeringAtLiveBufferCount = 100;
-static const int32_t StartSyncFullGCAtLiveBufferCount = MaximumLiveMappedBuffers - 100;
-static const int32_t AllocatedBuffersPerTrigger = 100;
-
-static Atomic<int32_t, mozilla::ReleaseAcquire> liveBufferCount(0);
-static Atomic<int32_t, mozilla::ReleaseAcquire> allocatedSinceLastTrigger(0);
-
-int32_t
-js::LiveMappedBufferCount()
-{
-    return liveBufferCount;
-}
-
-void*
-js::MapBufferMemory(size_t mappedSize, size_t initialCommittedSize)
-{
-    MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
-    MOZ_ASSERT(initialCommittedSize % gc::SystemPageSize() == 0);
-    MOZ_ASSERT(initialCommittedSize <= mappedSize);
-
-    // Test >= to guard against the case where multiple extant runtimes
-    // race to allocate.
-    if (++liveBufferCount >= MaximumLiveMappedBuffers) {
-        if (OnLargeAllocationFailure) {
-            OnLargeAllocationFailure();
-        }
-        if (liveBufferCount >= MaximumLiveMappedBuffers) {
-            liveBufferCount--;
-            return nullptr;
-        }
-    }
-
-#ifdef XP_WIN
-    void* data = VirtualAlloc(nullptr, mappedSize, MEM_RESERVE, PAGE_NOACCESS);
-    if (!data) {
-        liveBufferCount--;
-        return nullptr;
-    }
-
-    if (!VirtualAlloc(data, initialCommittedSize, MEM_COMMIT, PAGE_READWRITE)) {
-        VirtualFree(data, 0, MEM_RELEASE);
-        liveBufferCount--;
-        return nullptr;
-    }
-#else  // XP_WIN
-    void* data = MozTaggedAnonymousMmap(nullptr, mappedSize, PROT_NONE,
-                                        MAP_PRIVATE | MAP_ANON, -1, 0, "wasm-reserved");
-    if (data == MAP_FAILED) {
-        liveBufferCount--;
-        return nullptr;
-    }
-
-    // Note we will waste a page on zero-sized memories here
-    if (mprotect(data, initialCommittedSize, PROT_READ | PROT_WRITE)) {
-        munmap(data, mappedSize);
-        liveBufferCount--;
-        return nullptr;
-    }
-#endif  // !XP_WIN
-
-#if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
-    VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)data + initialCommittedSize,
-                                                   mappedSize - initialCommittedSize);
-#endif
-
-    return data;
-}
-
-bool
-js::CommitBufferMemory(void* dataEnd, uint32_t delta)
-{
-    MOZ_ASSERT(delta);
-    MOZ_ASSERT(delta % gc::SystemPageSize() == 0);
-
-#ifdef XP_WIN
-    if (!VirtualAlloc(dataEnd, delta, MEM_COMMIT, PAGE_READWRITE)) {
-        return false;
-    }
-#else  // XP_WIN
-    if (mprotect(dataEnd, delta, PROT_READ | PROT_WRITE)) {
-        return false;
-    }
-#endif  // !XP_WIN
-
-#if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
-    VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)dataEnd, delta);
-#endif
-
-    return true;
-}
-
-#ifndef WASM_HUGE_MEMORY
-bool
-js::ExtendBufferMapping(void* dataPointer, size_t mappedSize, size_t newMappedSize)
-{
-    MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
-    MOZ_ASSERT(newMappedSize % gc::SystemPageSize() == 0);
-    MOZ_ASSERT(newMappedSize >= mappedSize);
-
-#ifdef XP_WIN
-    void* mappedEnd = (char*)dataPointer + mappedSize;
-    uint32_t delta = newMappedSize - mappedSize;
-    if (!VirtualAlloc(mappedEnd, delta, MEM_RESERVE, PAGE_NOACCESS)) {
-        return false;
-    }
-    return true;
-#elif defined(XP_LINUX)
-    // Note this will not move memory (no MREMAP_MAYMOVE specified)
-    if (MAP_FAILED == mremap(dataPointer, mappedSize, newMappedSize, 0)) {
-        return false;
-    }
-    return true;
-#else
-    // No mechanism for remapping on MacOS and other Unices. Luckily
-    // shouldn't need it here as most of these are 64-bit.
-    return false;
-#endif
-}
-#endif
-
-void
-js::UnmapBufferMemory(void* base, size_t mappedSize)
-{
-    MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
-
-#ifdef XP_WIN
-    VirtualFree(base, 0, MEM_RELEASE);
-#else  // XP_WIN
-    munmap(base, mappedSize);
-#endif  // !XP_WIN
-
-#if defined(MOZ_VALGRIND) && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
-    VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)base, mappedSize);
-#endif
-
-    // Decrement the buffer counter at the end -- otherwise, a race condition
-    // could enable the creation of unlimited buffers.
-    liveBufferCount--;
-}
-
-/*
- * ArrayBufferObject
- *
- * This class holds the underlying raw buffer that the TypedArrayObject classes
- * access.  It can be created explicitly and passed to a TypedArrayObject, or
- * can be created implicitly by constructing a TypedArrayObject with a size.
- */
-
-/*
- * ArrayBufferObject (base)
- */
-
-static const ClassOps ArrayBufferObjectClassOps = {
-    nullptr,        /* addProperty */
-    nullptr,        /* delProperty */
-    nullptr,        /* enumerate */
-    nullptr,        /* newEnumerate */
-    nullptr,        /* resolve */
-    nullptr,        /* mayResolve */
-    ArrayBufferObject::finalize,
-    nullptr,        /* call        */
-    nullptr,        /* hasInstance */
-    nullptr,        /* construct   */
-    ArrayBufferObject::trace,
-};
-
-static const JSFunctionSpec arraybuffer_functions[] = {
-    JS_FN("isView", ArrayBufferObject::fun_isView, 1, 0),
-    JS_FS_END
-};
-
-static const JSPropertySpec arraybuffer_properties[] = {
-    JS_SELF_HOSTED_SYM_GET(species, "ArrayBufferSpecies", 0),
-    JS_PS_END
-};
-
-
-static const JSFunctionSpec arraybuffer_proto_functions[] = {
-    JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2, 0),
-    JS_FS_END
-};
-
-static const JSPropertySpec arraybuffer_proto_properties[] = {
-    JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0),
-    JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY),
-    JS_PS_END
-};
-
-static const ClassSpec ArrayBufferObjectClassSpec = {
-    GenericCreateConstructor<ArrayBufferObject::class_constructor, 1, gc::AllocKind::FUNCTION>,
-    GenericCreatePrototype<ArrayBufferObject>,
-    arraybuffer_functions,
-    arraybuffer_properties,
-    arraybuffer_proto_functions,
-    arraybuffer_proto_properties
-};
-
-static const ClassExtension ArrayBufferObjectClassExtension = {
-    nullptr,    /* weakmapKeyDelegateOp */
-    ArrayBufferObject::objectMoved
-};
-
-const Class ArrayBufferObject::class_ = {
-    "ArrayBuffer",
-    JSCLASS_DELAY_METADATA_BUILDER |
-    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) |
-    JSCLASS_BACKGROUND_FINALIZE,
-    &ArrayBufferObjectClassOps,
-    &ArrayBufferObjectClassSpec,
-    &ArrayBufferObjectClassExtension
-};
-
-const Class ArrayBufferObject::protoClass_ = {
-    "ArrayBufferPrototype",
-    JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer),
-    JS_NULL_CLASS_OPS,
-    &ArrayBufferObjectClassSpec
-};
-
-bool
-js::IsArrayBuffer(HandleValue v)
-{
-    return v.isObject() && v.toObject().is<ArrayBufferObject>();
-}
-
-bool
-js::IsArrayBuffer(HandleObject obj)
-{
-    return obj->is<ArrayBufferObject>();
-}
-
-bool
-js::IsArrayBuffer(JSObject* obj)
-{
-    return obj->is<ArrayBufferObject>();
-}
-
-ArrayBufferObject&
-js::AsArrayBuffer(HandleObject obj)
-{
-    MOZ_ASSERT(IsArrayBuffer(obj));
-    return obj->as<ArrayBufferObject>();
-}
-
-ArrayBufferObject&
-js::AsArrayBuffer(JSObject* obj)
-{
-    MOZ_ASSERT(IsArrayBuffer(obj));
-    return obj->as<ArrayBufferObject>();
-}
-
-bool
-js::IsArrayBufferMaybeShared(HandleValue v)
-{
-    return v.isObject() && v.toObject().is<ArrayBufferObjectMaybeShared>();
-}
-
-bool
-js::IsArrayBufferMaybeShared(HandleObject obj)
-{
-    return obj->is<ArrayBufferObjectMaybeShared>();
-}
-
-bool
-js::IsArrayBufferMaybeShared(JSObject* obj)
-{
-    return obj->is<ArrayBufferObjectMaybeShared>();
-}
-
-ArrayBufferObjectMaybeShared&
-js::AsArrayBufferMaybeShared(HandleObject obj)
-{
-    MOZ_ASSERT(IsArrayBufferMaybeShared(obj));
-    return obj->as<ArrayBufferObjectMaybeShared>();
-}
-
-ArrayBufferObjectMaybeShared&
-js::AsArrayBufferMaybeShared(JSObject* obj)
-{
-    MOZ_ASSERT(IsArrayBufferMaybeShared(obj));
-    return obj->as<ArrayBufferObjectMaybeShared>();
-}
-
-MOZ_ALWAYS_INLINE bool
-ArrayBufferObject::byteLengthGetterImpl(JSContext* cx, const CallArgs& args)
-{
-    MOZ_ASSERT(IsArrayBuffer(args.thisv()));
-    args.rval().setInt32(args.thisv().toObject().as<ArrayBufferObject>().byteLength());
-    return true;
-}
-
-bool
-ArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args);
-}
-
-/*
- * ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1
- */
-bool
-ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-    args.rval().setBoolean(args.get(0).isObject() &&
-                           JS_IsArrayBufferViewObject(&args.get(0).toObject()));
-    return true;
-}
-
-// ES2017 draft 24.1.2.1
-bool
-ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    // Step 1.
-    if (!ThrowIfNotConstructing(cx, args, "ArrayBuffer")) {
-        return false;
-    }
-
-    // Step 2.
-    uint64_t byteLength;
-    if (!ToIndex(cx, args.get(0), &byteLength)) {
-        return false;
-    }
-
-    // Step 3 (Inlined 24.1.1.1 AllocateArrayBuffer).
-    // 24.1.1.1, step 1 (Inlined 9.1.14 OrdinaryCreateFromConstructor).
-    RootedObject proto(cx);
-    if (!GetPrototypeFromBuiltinConstructor(cx, args, &proto)) {
-        return false;
-    }
-
-    // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
-    // Refuse to allocate too large buffers, currently limited to ~2 GiB.
-    if (byteLength > INT32_MAX) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
-        return false;
-    }
-
-    // 24.1.1.1, steps 1 and 4-6.
-    JSObject* bufobj = create(cx, uint32_t(byteLength), proto);
-    if (!bufobj) {
-        return false;
-    }
-    args.rval().setObject(*bufobj);
-    return true;
-}
-
-static ArrayBufferObject::BufferContents
-AllocateArrayBufferContents(JSContext* cx, uint32_t nbytes)
-{
-    uint8_t* p = cx->pod_callocCanGC<uint8_t>(nbytes,
-                                                      js::ArrayBufferContentsArena);
-    return ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(p);
-}
-
-static void
-NoteViewBufferWasDetached(ArrayBufferViewObject* view,
-                          ArrayBufferObject::BufferContents newContents,
-                          JSContext* cx)
-{
-    view->notifyBufferDetached(cx, newContents.data());
-
-    // Notify compiled jit code that the base pointer has moved.
-    MarkObjectStateChange(cx, view);
-}
-
-/* static */ void
-ArrayBufferObject::detach(JSContext* cx, Handle<ArrayBufferObject*> buffer,
-                          BufferContents newContents)
-{
-    cx->check(buffer);
-    MOZ_ASSERT(!buffer->isPreparedForAsmJS());
-
-    // When detaching buffers where we don't know all views, the new data must
-    // match the old data. All missing views are typed objects, which do not
-    // expect their data to ever change.
-    MOZ_ASSERT_IF(buffer->forInlineTypedObject(),
-                  newContents.data() == buffer->dataPointer());
-
-    // When detaching a buffer with typed object views, any jitcode accessing
-    // such views must be deoptimized so that detachment checks are performed.
-    // This is done by setting a zone-wide flag indicating that buffers with
-    // typed object views have been detached.
-    if (buffer->hasTypedObjectViews()) {
-        // Make sure the global object's group has been instantiated, so the
-        // flag change will be observed.
-        AutoEnterOOMUnsafeRegion oomUnsafe;
-        if (!JSObject::getGroup(cx, cx->global())) {
-            oomUnsafe.crash("ArrayBufferObject::detach");
-        }
-        MarkObjectGroupFlags(cx, cx->global(), OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER);
-        cx->zone()->detachedTypedObjects = 1;
-    }
-
-    // Update all views of the buffer to account for the buffer having been
-    // detached, and clear the buffer's data and list of views.
-
-    auto& innerViews = ObjectRealm::get(buffer).innerViews.get();
-    if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(buffer)) {
-        for (size_t i = 0; i < views->length(); i++) {
-            NoteViewBufferWasDetached((*views)[i], newContents, cx);
-        }
-        innerViews.removeViews(buffer);
-    }
-    if (buffer->firstView()) {
-        if (buffer->forInlineTypedObject()) {
-            // The buffer points to inline data in its first view, so to keep
-            // this pointer alive we don't clear out the first view.
-            MOZ_ASSERT(buffer->firstView()->is<InlineTransparentTypedObject>());
-        } else {
-            NoteViewBufferWasDetached(buffer->firstView(), newContents, cx);
-            buffer->setFirstView(nullptr);
-        }
-    }
-
-    if (newContents.data() != buffer->dataPointer()) {
-        buffer->setNewData(cx->runtime()->defaultFreeOp(), newContents, OwnsData);
-    }
-
-    buffer->setByteLength(0);
-    buffer->setIsDetached();
-}
-
-void
-ArrayBufferObject::setNewData(FreeOp* fop, BufferContents newContents, OwnsState ownsState)
-{
-    if (ownsData()) {
-        MOZ_ASSERT(newContents.data() != dataPointer());
-        releaseData(fop);
-    }
-
-    setDataPointer(newContents, ownsState);
-}
-
-// This is called *only* from changeContents(), below.
-// By construction, every view parameter will be mapping unshared memory (an ArrayBuffer).
-// Hence no reason to worry about shared memory here.
-
-void
-ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
-                                      uint8_t* oldDataPointer, BufferContents newContents)
-{
-    MOZ_ASSERT(!view->isSharedMemory());
-
-    // Watch out for NULL data pointers in views. This means that the view
-    // is not fully initialized (in which case it'll be initialized later
-    // with the correct pointer).
-    JS::AutoCheckCannotGC nogc;
-    uint8_t* viewDataPointer = view->dataPointerUnshared(nogc);
-    if (viewDataPointer) {
-        MOZ_ASSERT(newContents);
-        ptrdiff_t offset = viewDataPointer - oldDataPointer;
-        viewDataPointer = static_cast<uint8_t*>(newContents.data()) + offset;
-        view->setDataPointerUnshared(viewDataPointer);
-    }
-
-    // Notify compiled jit code that the base pointer has moved.
-    MarkObjectStateChange(cx, view);
-}
-
-// BufferContents is specific to ArrayBuffer, hence it will not represent shared memory.
-
-void
-ArrayBufferObject::changeContents(JSContext* cx, BufferContents newContents,
-                                  OwnsState ownsState)
-{
-    MOZ_RELEASE_ASSERT(!isWasm());
-    MOZ_ASSERT(!forInlineTypedObject());
-
-    // Change buffer contents.
-    uint8_t* oldDataPointer = dataPointer();
-    setNewData(cx->runtime()->defaultFreeOp(), newContents, ownsState);
-
-    // Update all views.
-    auto& innerViews = ObjectRealm::get(this).innerViews.get();
-    if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(this)) {
-        for (size_t i = 0; i < views->length(); i++) {
-            changeViewContents(cx, (*views)[i], oldDataPointer, newContents);
-        }
-    }
-    if (firstView()) {
-        changeViewContents(cx, firstView(), oldDataPointer, newContents);
-    }
-}
-
-/*
- * [SMDOC] WASM Linear Memory structure
- *
- * Wasm Raw Buf Linear Memory Structure
- *
- * The linear heap in Wasm is an mmaped array buffer. Several
- * constants manage its lifetime:
- *
- *  - length - the wasm-visible current length of the buffer. Accesses in the
- *    range [0, length] succeed. May only increase.
- *
- *  - boundsCheckLimit - the size against which we perform bounds checks. It is
- *    always a constant offset smaller than mappedSize. Currently that constant
- *    offset is 64k (wasm::GuardSize).
- *
- *  - maxSize - the optional declared limit on how much length can grow.
- *
- *  - mappedSize - the actual mmaped size. Access in the range
- *    [0, mappedSize] will either succeed, or be handled by the wasm signal
- *    handlers.
- *
- * The below diagram shows the layout of the wasm heap. The wasm-visible
- * portion of the heap starts at 0. There is one extra page prior to the
- * start of the wasm heap which contains the WasmArrayRawBuffer struct at
- * its end (i.e. right before the start of the WASM heap).
- *
- *  WasmArrayRawBuffer
- *      \    ArrayBufferObject::dataPointer()
- *       \  /
- *        \ |
- *  ______|_|____________________________________________________________
- * |______|_|______________|___________________|____________|____________|
- *          0          length              maxSize  boundsCheckLimit  mappedSize
- *
- * \_______________________/
- *          COMMITED
- *                          \____________________________________________/
- *                                           SLOP
- * \_____________________________________________________________________/
- *                         MAPPED
- *
- * Invariants:
- *  - length only increases
- *  - 0 <= length <= maxSize (if present) <= boundsCheckLimit <= mappedSize
- *  - on ARM boundsCheckLimit must be a valid ARM immediate.
- *  - if maxSize is not specified, boundsCheckLimit/mappedSize may grow. They are
- *    otherwise constant.
- *
- * NOTE: For asm.js on non-x64 we guarantee that
- *
- * length == maxSize == boundsCheckLimit == mappedSize
- *
- * That is, signal handlers will not be invoked, since they cannot emulate
- * asm.js accesses on non-x64 architectures.
- *
- * The region between length and mappedSize is the SLOP - an area where we use
- * signal handlers to catch things that slip by bounds checks. Logically it has
- * two parts:
- *
- *  - from length to boundsCheckLimit - this part of the SLOP serves to catch
- *  accesses to memory we have reserved but not yet grown into. This allows us
- *  to grow memory up to max (when present) without having to patch/update the
- *  bounds checks.
- *
- *  - from boundsCheckLimit to mappedSize - this part of the SLOP allows us to
- *  bounds check against base pointers and fold some constant offsets inside
- *  loads. This enables better Bounds Check Elimination.
- *
- */
-
-class js::WasmArrayRawBuffer
-{
-    Maybe<uint32_t> maxSize_;
-    size_t mappedSize_;         // Not including the header page
-
-  protected:
-    WasmArrayRawBuffer(uint8_t* buffer, const Maybe<uint32_t>& maxSize, size_t mappedSize)
-      : maxSize_(maxSize), mappedSize_(mappedSize)
-    {
-        MOZ_ASSERT(buffer == dataPointer());
-    }
-
-  public:
-    static WasmArrayRawBuffer* Allocate(uint32_t numBytes, const Maybe<uint32_t>& maxSize);
-    static void Release(void* mem);
-
-    uint8_t* dataPointer() {
-        uint8_t* ptr = reinterpret_cast<uint8_t*>(this);
-        return ptr + sizeof(WasmArrayRawBuffer);
-    }
-
-    uint8_t* basePointer() {
-        return dataPointer() - gc::SystemPageSize();
-    }
-
-    size_t mappedSize() const {
-        return mappedSize_;
-    }
-
-    Maybe<uint32_t> maxSize() const {
-        return maxSize_;
-    }
-
-#ifndef WASM_HUGE_MEMORY
-    uint32_t boundsCheckLimit() const {
-        MOZ_ASSERT(mappedSize_ <= UINT32_MAX);
-        MOZ_ASSERT(mappedSize_ >= wasm::GuardSize);
-        MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize_ - wasm::GuardSize));
-        return mappedSize_ - wasm::GuardSize;
-    }
-#endif
-
-    MOZ_MUST_USE bool growToSizeInPlace(uint32_t oldSize, uint32_t newSize) {
-        MOZ_ASSERT(newSize >= oldSize);
-        MOZ_ASSERT_IF(maxSize(), newSize <= maxSize().value());
-        MOZ_ASSERT(newSize <= mappedSize());
-
-        uint32_t delta = newSize - oldSize;
-        MOZ_ASSERT(delta % wasm::PageSize == 0);
-
-        uint8_t* dataEnd = dataPointer() + oldSize;
-        MOZ_ASSERT(uintptr_t(dataEnd) % gc::SystemPageSize() == 0);
-
-        if (delta && !CommitBufferMemory(dataEnd, delta)) {
-            return false;
-        }
-
-        return true;
-    }
-
-#ifndef WASM_HUGE_MEMORY
-    bool extendMappedSize(uint32_t maxSize) {
-        size_t newMappedSize = wasm::ComputeMappedSize(maxSize);
-        MOZ_ASSERT(mappedSize_ <= newMappedSize);
-        if (mappedSize_ == newMappedSize) {
-            return true;
-        }
-
-        if (!ExtendBufferMapping(dataPointer(), mappedSize_, newMappedSize)) {
-            return false;
-        }
-
-        mappedSize_ = newMappedSize;
-        return true;
-    }
-
-    // Try and grow the mapped region of memory. Does not change current size.
-    // Does not move memory if no space to grow.
-    void tryGrowMaxSizeInPlace(uint32_t deltaMaxSize) {
-        CheckedInt<uint32_t> newMaxSize = maxSize_.value();
-        newMaxSize += deltaMaxSize;
-        MOZ_ASSERT(newMaxSize.isValid());
-        MOZ_ASSERT(newMaxSize.value() % wasm::PageSize == 0);
-
-        if (!extendMappedSize(newMaxSize.value())) {
-            return;
-        }
-
-        maxSize_ = Some(newMaxSize.value());
-    }
-#endif // WASM_HUGE_MEMORY
-};
-
-/* static */ WasmArrayRawBuffer*
-WasmArrayRawBuffer::Allocate(uint32_t numBytes, const Maybe<uint32_t>& maxSize)
-{
-    MOZ_RELEASE_ASSERT(numBytes <= ArrayBufferObject::MaxBufferByteLength);
-
-    size_t mappedSize;
-#ifdef WASM_HUGE_MEMORY
-    mappedSize = wasm::HugeMappedSize;
-#else
-    mappedSize = wasm::ComputeMappedSize(maxSize.valueOr(numBytes));
-#endif
-
-    MOZ_RELEASE_ASSERT(mappedSize <= SIZE_MAX - gc::SystemPageSize());
-    MOZ_RELEASE_ASSERT(numBytes <= maxSize.valueOr(UINT32_MAX));
-    MOZ_ASSERT(numBytes % gc::SystemPageSize() == 0);
-    MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
-
-    uint64_t mappedSizeWithHeader = mappedSize + gc::SystemPageSize();
-    uint64_t numBytesWithHeader = numBytes + gc::SystemPageSize();
-
-    void* data = MapBufferMemory((size_t) mappedSizeWithHeader, (size_t) numBytesWithHeader);
-    if (!data) {
-        return nullptr;
-    }
-
-    uint8_t* base = reinterpret_cast<uint8_t*>(data) + gc::SystemPageSize();
-    uint8_t* header = base - sizeof(WasmArrayRawBuffer);
-
-    auto rawBuf = new (header) WasmArrayRawBuffer(base, maxSize, mappedSize);
-    return rawBuf;
-}
-
-/* static */ void
-WasmArrayRawBuffer::Release(void* mem)
-{
-    WasmArrayRawBuffer* header = (WasmArrayRawBuffer*)((uint8_t*)mem - sizeof(WasmArrayRawBuffer));
-
-    MOZ_RELEASE_ASSERT(header->mappedSize() <= SIZE_MAX - gc::SystemPageSize());
-    size_t mappedSizeWithHeader = header->mappedSize() + gc::SystemPageSize();
-
-    UnmapBufferMemory(header->basePointer(), mappedSizeWithHeader);
-}
-
-WasmArrayRawBuffer*
-ArrayBufferObject::BufferContents::wasmBuffer() const
-{
-    MOZ_RELEASE_ASSERT(kind_ == WASM);
-    return (WasmArrayRawBuffer*)(data_ - sizeof(WasmArrayRawBuffer));
-}
-
-template<typename ObjT, typename RawbufT>
-static bool
-CreateBuffer(JSContext* cx, uint32_t initialSize, const Maybe<uint32_t>& maxSize,
-             MutableHandleArrayBufferObjectMaybeShared maybeSharedObject)
-{
-#define ROUND_UP(v, a) ((v) % (a) == 0 ? (v) : v + a - ((v) % (a)))
-
-    RawbufT* buffer = RawbufT::Allocate(initialSize, maxSize);
-    if (!buffer) {
-#ifdef  WASM_HUGE_MEMORY
-        ReportOutOfMemory(cx);
-        return false;
-#else
-        // If we fail, and have a maxSize, try to reserve the biggest chunk in
-        // the range [initialSize, maxSize) using log backoff.
-        if (!maxSize) {
-            ReportOutOfMemory(cx);
-            return false;
-        }
-
-        uint32_t cur = maxSize.value() / 2;
-
-        for (; cur > initialSize; cur /= 2) {
-            buffer = RawbufT::Allocate(initialSize, mozilla::Some(ROUND_UP(cur, wasm::PageSize)));
-            if (buffer) {
-                break;
-            }
-        }
-
-        if (!buffer) {
-            ReportOutOfMemory(cx);
-            return false;
-        }
-
-        // Try to grow our chunk as much as possible.
-        for (size_t d = cur / 2; d >= wasm::PageSize; d /= 2) {
-            buffer->tryGrowMaxSizeInPlace(ROUND_UP(d, wasm::PageSize));
-        }
-#endif
-    }
-
-#undef ROUND_UP
-
-    // ObjT::createFromNewRawBuffer assumes ownership of |buffer| even in case
-    // of failure.
-    ObjT* object = ObjT::createFromNewRawBuffer(cx, buffer, initialSize);
-    if (!object) {
-        return false;
-    }
-
-    maybeSharedObject.set(object);
-
-    // See MaximumLiveMappedBuffers comment above.
-    if (liveBufferCount > StartSyncFullGCAtLiveBufferCount) {
-        JS::PrepareForFullGC(cx);
-        JS::NonIncrementalGC(cx, GC_NORMAL, JS::gcreason::TOO_MUCH_WASM_MEMORY);
-        allocatedSinceLastTrigger = 0;
-    } else if (liveBufferCount > StartTriggeringAtLiveBufferCount) {
-        allocatedSinceLastTrigger++;
-        if (allocatedSinceLastTrigger > AllocatedBuffersPerTrigger) {
-            Unused << cx->runtime()->gc.triggerGC(JS::gcreason::TOO_MUCH_WASM_MEMORY);
-            allocatedSinceLastTrigger = 0;
-        }
-    } else {
-        allocatedSinceLastTrigger = 0;
-    }
-
-    return true;
-}
-
-bool
-js::CreateWasmBuffer(JSContext* cx, const wasm::Limits& memory,
-                     MutableHandleArrayBufferObjectMaybeShared buffer)
-{
-    MOZ_ASSERT(memory.initial % wasm::PageSize == 0);
-    MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
-    MOZ_RELEASE_ASSERT((memory.initial / wasm::PageSize) <= wasm::MaxMemoryInitialPages);
-
-    // Prevent applications specifying a large max (like UINT32_MAX) from
-    // unintentially OOMing the browser on 32-bit: they just want "a lot of
-    // memory". Maintain the invariant that initialSize <= maxSize.
-
-    Maybe<uint32_t> maxSize = memory.maximum;
-    if (sizeof(void*) == 4 && maxSize) {
-        static const uint32_t OneGiB = 1 << 30;
-        uint32_t clamp = Max(OneGiB, memory.initial);
-        maxSize = Some(Min(clamp, *maxSize));
-    }
-
-#ifndef WASM_HUGE_MEMORY
-    if (sizeof(void*) == 8 && maxSize && maxSize.value() >= (UINT32_MAX - wasm::PageSize)) {
-        // On 64-bit platforms that don't define WASM_HUGE_MEMORY
-        // clamp maxSize to smaller value that satisfies the 32-bit invariants
-        // maxSize + wasm::PageSize < UINT32_MAX and maxSize % wasm::PageSize == 0
-        uint32_t clamp = (wasm::MaxMemoryMaximumPages - 2) * wasm::PageSize;
-        MOZ_ASSERT(clamp < UINT32_MAX);
-        MOZ_ASSERT(memory.initial <= clamp);
-        maxSize = Some(clamp);
-    }
-#endif
-
-    if (memory.shared == wasm::Shareable::True) {
-        if (!cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) {
-            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_SHMEM_LINK);
-            return false;
-        }
-        return CreateBuffer<SharedArrayBufferObject, SharedArrayRawBuffer>(cx, memory.initial,
-                                                                           maxSize, buffer);
-    }
-    return CreateBuffer<ArrayBufferObject, WasmArrayRawBuffer>(cx, memory.initial, maxSize,
-                                                               buffer);
-}
-
-// Note this function can return false with or without an exception pending. The
-// asm.js caller checks cx->isExceptionPending before propagating failure.
-// Returning false without throwing means that asm.js linking will fail which
-// will recompile as non-asm.js.
-/* static */ bool
-ArrayBufferObject::prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer)
-{
-    MOZ_ASSERT(buffer->byteLength() % wasm::PageSize == 0);
-    MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
-
-    if (buffer->forInlineTypedObject()) {
-        return false;
-    }
-
-    if (!buffer->isWasm() && buffer->isPreparedForAsmJS()) {
-        return true;
-    }
-
-    // Non-prepared-for-asm.js wasm buffers can be detached at any time.
-    if (buffer->isWasm()) {
-        return false;
-    }
-
-    if (!buffer->ownsData()) {
-        BufferContents contents = AllocateArrayBufferContents(cx, buffer->byteLength());
-        if (!contents) {
-            return false;
-        }
-        memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength());
-        buffer->changeContents(cx, contents, OwnsData);
-    }
-
-    buffer->setIsPreparedForAsmJS();
-    return true;
-}
-
-ArrayBufferObject::BufferContents
-ArrayBufferObject::createMappedContents(int fd, size_t offset, size_t length)
-{
-    void* data = gc::AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
-    return BufferContents::create<MAPPED>(data);
-}
-
-uint8_t*
-ArrayBufferObject::inlineDataPointer() const
-{
-    return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_)));
-}
-
-uint8_t*
-ArrayBufferObject::dataPointer() const
-{
-    return static_cast<uint8_t*>(getFixedSlot(DATA_SLOT).toPrivate());
-}
-
-SharedMem<uint8_t*>
-ArrayBufferObject::dataPointerShared() const
-{
-    return SharedMem<uint8_t*>::unshared(getFixedSlot(DATA_SLOT).toPrivate());
-}
-
-ArrayBufferObject::FreeInfo*
-ArrayBufferObject::freeInfo() const
-{
-    MOZ_ASSERT(isExternal());
-    return reinterpret_cast<FreeInfo*>(inlineDataPointer());
-}
-
-void
-ArrayBufferObject::releaseData(FreeOp* fop)
-{
-    MOZ_ASSERT(ownsData());
-
-    switch (bufferKind()) {
-      case PLAIN:
-        fop->free_(dataPointer());
-        break;
-      case MAPPED:
-        gc::DeallocateMappedContent(dataPointer(), byteLength());
-        break;
-      case WASM:
-        WasmArrayRawBuffer::Release(dataPointer());
-        break;
-      case EXTERNAL:
-        if (freeInfo()->freeFunc) {
-            // The analyzer can't know for sure whether the embedder-supplied
-            // free function will GC. We give the analyzer a hint here.
-            // (Doing a GC in the free function is considered a programmer
-            // error.)
-            JS::AutoSuppressGCAnalysis nogc;
-            freeInfo()->freeFunc(dataPointer(), freeInfo()->freeUserData);
-        }
-        break;
-    }
-}
-
-void
-ArrayBufferObject::setDataPointer(BufferContents contents, OwnsState ownsData)
-{
-    setFixedSlot(DATA_SLOT, PrivateValue(contents.data()));
-    setOwnsData(ownsData);
-    setFlags((flags() & ~KIND_MASK) | contents.kind());
-
-    if (isExternal()) {
-        auto info = freeInfo();
-        info->freeFunc = contents.freeFunc();
-        info->freeUserData = contents.freeUserData();
-    }
-}
-
-uint32_t
-ArrayBufferObject::byteLength() const
-{
-    return getFixedSlot(BYTE_LENGTH_SLOT).toInt32();
-}
-
-void
-ArrayBufferObject::setByteLength(uint32_t length)
-{
-    MOZ_ASSERT(length <= INT32_MAX);
-    setFixedSlot(BYTE_LENGTH_SLOT, Int32Value(length));
-}
-
-size_t
-ArrayBufferObject::wasmMappedSize() const
-{
-    if (isWasm()) {
-        return contents().wasmBuffer()->mappedSize();
-    }
-    return byteLength();
-}
-
-size_t
-js::WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf)
-{
-    if (buf->is<ArrayBufferObject>()) {
-        return buf->as<ArrayBufferObject>().wasmMappedSize();
-    }
-    return buf->as<SharedArrayBufferObject>().wasmMappedSize();
-}
-
-Maybe<uint32_t>
-ArrayBufferObject::wasmMaxSize() const
-{
-    if (isWasm()) {
-        return contents().wasmBuffer()->maxSize();
-    } else {
-        return Some<uint32_t>(byteLength());
-    }
-}
-
-Maybe<uint32_t>
-js::WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf)
-{
-    if (buf->is<ArrayBufferObject>()) {
-        return buf->as<ArrayBufferObject>().wasmMaxSize();
-    }
-    return buf->as<SharedArrayBufferObject>().wasmMaxSize();
-}
-
-/* static */ bool
-ArrayBufferObject::wasmGrowToSizeInPlace(uint32_t newSize,
-                                         HandleArrayBufferObject oldBuf,
-                                         MutableHandleArrayBufferObject newBuf,
-                                         JSContext* cx)
-{
-    // On failure, do not throw and ensure that the original buffer is
-    // unmodified and valid. After WasmArrayRawBuffer::growToSizeInPlace(), the
-    // wasm-visible length of the buffer has been increased so it must be the
-    // last fallible operation.
-
-    if (newSize > ArrayBufferObject::MaxBufferByteLength) {
-        return false;
-    }
-
-    newBuf.set(ArrayBufferObject::createEmpty(cx));
-    if (!newBuf) {
-        cx->clearPendingException();
-        return false;
-    }
-
-    if (!oldBuf->contents().wasmBuffer()->growToSizeInPlace(oldBuf->byteLength(), newSize)) {
-        return false;
-    }
-
-    bool hasStealableContents = true;
-    BufferContents contents = ArrayBufferObject::stealContents(cx, oldBuf, hasStealableContents);
-    MOZ_ASSERT(contents);
-    newBuf->initialize(newSize, contents, OwnsData);
-    return true;
-}
-
-#ifndef WASM_HUGE_MEMORY
-/* static */ bool
-ArrayBufferObject::wasmMovingGrowToSize(uint32_t newSize,
-                                        HandleArrayBufferObject oldBuf,
-                                        MutableHandleArrayBufferObject newBuf,
-                                        JSContext* cx)
-{
-    // On failure, do not throw and ensure that the original buffer is
-    // unmodified and valid.
-
-    if (newSize > ArrayBufferObject::MaxBufferByteLength) {
-        return false;
-    }
-
-    if (newSize <= oldBuf->wasmBoundsCheckLimit() ||
-        oldBuf->contents().wasmBuffer()->extendMappedSize(newSize))
-    {
-        return wasmGrowToSizeInPlace(newSize, oldBuf, newBuf, cx);
-    }
-
-    newBuf.set(ArrayBufferObject::createEmpty(cx));
-    if (!newBuf) {
-        cx->clearPendingException();
-        return false;
-    }
-
-    WasmArrayRawBuffer* newRawBuf = WasmArrayRawBuffer::Allocate(newSize, Nothing());
-    if (!newRawBuf) {
-        return false;
-    }
-    BufferContents contents = BufferContents::create<WASM>(newRawBuf->dataPointer());
-    newBuf->initialize(newSize, contents, OwnsData);
-
-    memcpy(newBuf->dataPointer(), oldBuf->dataPointer(), oldBuf->byteLength());
-    ArrayBufferObject::detach(cx, oldBuf, BufferContents::createPlain(nullptr));
-    return true;
-}
-
-uint32_t
-ArrayBufferObject::wasmBoundsCheckLimit() const
-{
-    if (isWasm()) {
-        return contents().wasmBuffer()->boundsCheckLimit();
-    }
-    return byteLength();
-}
-
-uint32_t
-ArrayBufferObjectMaybeShared::wasmBoundsCheckLimit() const
-{
-    if (is<ArrayBufferObject>()) {
-        return as<ArrayBufferObject>().wasmBoundsCheckLimit();
-    }
-    return as<SharedArrayBufferObject>().wasmBoundsCheckLimit();
-}
-#else
-uint32_t
-ArrayBufferObject::wasmBoundsCheckLimit() const
-{
-    return byteLength();
-}
-
-uint32_t
-ArrayBufferObjectMaybeShared::wasmBoundsCheckLimit() const
-{
-    return byteLength();
-}
-#endif
-
-uint32_t
-ArrayBufferObject::flags() const
-{
-    return uint32_t(getFixedSlot(FLAGS_SLOT).toInt32());
-}
-
-void
-ArrayBufferObject::setFlags(uint32_t flags)
-{
-    setFixedSlot(FLAGS_SLOT, Int32Value(flags));
-}
-
-ArrayBufferObject*
-ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents contents,
-                          OwnsState ownsState /* = OwnsData */,
-                          HandleObject proto /* = nullptr */,
-                          NewObjectKind newKind /* = GenericObject */)
-{
-    MOZ_ASSERT_IF(contents.kind() == MAPPED, contents);
-
-    // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
-    // Refuse to allocate too large buffers, currently limited to ~2 GiB.
-    if (nbytes > INT32_MAX) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
-        return nullptr;
-    }
-
-    // If we need to allocate data, try to use a larger object size class so
-    // that the array buffer's data can be allocated inline with the object.
-    // The extra space will be left unused by the object's fixed slots and
-    // available for the buffer's data, see NewObject().
-    size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_);
-
-    size_t nslots = reservedSlots;
-    bool allocated = false;
-    if (contents) {
-        if (ownsState == OwnsData) {
-            if (contents.kind() == EXTERNAL) {
-                // Store the FreeInfo in the inline data slots so that we
-                // don't use up slots for it in non-refcounted array buffers.
-                size_t freeInfoSlots = JS_HOWMANY(sizeof(FreeInfo), sizeof(Value));
-                MOZ_ASSERT(reservedSlots + freeInfoSlots <= NativeObject::MAX_FIXED_SLOTS,
-                           "FreeInfo must fit in inline slots");
-                nslots += freeInfoSlots;
-            } else {
-                // The ABO is taking ownership, so account the bytes against
-                // the zone.
-                size_t nAllocated = nbytes;
-                if (contents.kind() == MAPPED) {
-                    nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
-                }
-                cx->updateMallocCounter(nAllocated);
-            }
-        }
-    } else {
-        MOZ_ASSERT(ownsState == OwnsData);
-        size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
-        if (nbytes <= usableSlots * sizeof(Value)) {
-            int newSlots = JS_HOWMANY(nbytes, sizeof(Value));
-            MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
-            nslots = reservedSlots + newSlots;
-            contents = BufferContents::createPlain(nullptr);
-        } else {
-            contents = AllocateArrayBufferContents(cx, nbytes);
-            if (!contents) {
-                return nullptr;
-            }
-            allocated = true;
-        }
-    }
-
-    MOZ_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
-    gc::AllocKind allocKind = gc::GetGCObjectKind(nslots);
-
-    AutoSetNewObjectMetadata metadata(cx);
-    Rooted<ArrayBufferObject*> obj(cx,
-        NewObjectWithClassProto<ArrayBufferObject>(cx, proto, allocKind, newKind));
-    if (!obj) {
-        if (allocated) {
-            js_free(contents.data());
-        }
-        return nullptr;
-    }
-
-    MOZ_ASSERT(obj->getClass() == &class_);
-    MOZ_ASSERT(!gc::IsInsideNursery(obj));
-
-    if (!contents) {
-        void* data = obj->inlineDataPointer();
-        memset(data, 0, nbytes);
-        obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData);
-    } else {
-        obj->initialize(nbytes, contents, ownsState);
-    }
-
-    return obj;
-}
-
-ArrayBufferObject*
-ArrayBufferObject::create(JSContext* cx, uint32_t nbytes,
-                          HandleObject proto /* = nullptr */)
-{
-    return create(cx, nbytes, BufferContents::createPlain(nullptr),
-                  OwnsState::OwnsData, proto);
-}
-
-ArrayBufferObject*
-ArrayBufferObject::createEmpty(JSContext* cx)
-{
-    AutoSetNewObjectMetadata metadata(cx);
-    ArrayBufferObject* obj = NewBuiltinClassInstance<ArrayBufferObject>(cx);
-    if (!obj) {
-        return nullptr;
-    }
-
-    obj->setByteLength(0);
-    obj->setFlags(0);
-    obj->setFirstView(nullptr);
-    obj->setDataPointer(BufferContents::createPlain(nullptr), DoesntOwnData);
-
-    return obj;
-}
-
-ArrayBufferObject*
-ArrayBufferObject::createFromNewRawBuffer(JSContext* cx, WasmArrayRawBuffer* buffer,
-                                          uint32_t initialSize)
-{
-    AutoSetNewObjectMetadata metadata(cx);
-    ArrayBufferObject* obj = NewBuiltinClassInstance<ArrayBufferObject>(cx);
-    if (!obj) {
-        WasmArrayRawBuffer::Release(buffer->dataPointer());
-        return nullptr;
-    }
-
-    obj->setByteLength(initialSize);
-    obj->setFlags(0);
-    obj->setFirstView(nullptr);
-
-    auto contents = BufferContents::create<WASM>(buffer->dataPointer());
-    obj->setDataPointer(contents, OwnsData);
-
-    cx->updateMallocCounter(initialSize);
-
-    return obj;
-}
-
-/* static */ ArrayBufferObject::BufferContents
-ArrayBufferObject::externalizeContents(JSContext* cx, Handle<ArrayBufferObject*> buffer,
-                                       bool hasStealableContents)
-{
-    MOZ_ASSERT(buffer->isPlain(), "Only support doing this on plain ABOs");
-    MOZ_ASSERT(!buffer->isDetached(), "must have contents to externalize");
-    MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents());
-
-    BufferContents contents = buffer->contents();
-
-    if (hasStealableContents) {
-        buffer->setOwnsData(DoesntOwnData);
-        return contents;
-    }
-
-    // Create a new chunk of memory to return since we cannot steal the
-    // existing contents away from the buffer.
-    BufferContents newContents = AllocateArrayBufferContents(cx, buffer->byteLength());
-    if (!newContents) {
-        return BufferContents::createPlain(nullptr);
-    }
-    memcpy(newContents.data(), contents.data(), buffer->byteLength());
-    buffer->changeContents(cx, newContents, DoesntOwnData);
-
-    return newContents;
-}
-
-/* static */ ArrayBufferObject::BufferContents
-ArrayBufferObject::stealContents(JSContext* cx, Handle<ArrayBufferObject*> buffer,
-                                 bool hasStealableContents)
-{
-    // While wasm buffers cannot generally be transferred by content, the
-    // stealContents() is used internally by the impl of memory growth.
-    MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents() ||
-                                        (buffer->isWasm() && !buffer->isPreparedForAsmJS()));
-    cx->check(buffer);
-
-    BufferContents oldContents = buffer->contents();
-
-    if (hasStealableContents) {
-        // Return the old contents and reset the detached buffer's data
-        // pointer. This pointer should never be accessed.
-        auto newContents = BufferContents::createPlain(nullptr);
-        buffer->setOwnsData(DoesntOwnData); // Do not free the stolen data.
-        ArrayBufferObject::detach(cx, buffer, newContents);
-        buffer->setOwnsData(DoesntOwnData); // Do not free the nullptr.
-        return oldContents;
-    }
-
-    // Create a new chunk of memory to return since we cannot steal the
-    // existing contents away from the buffer.
-    BufferContents contentsCopy = AllocateArrayBufferContents(cx, buffer->byteLength());
-    if (!contentsCopy) {
-        return BufferContents::createPlain(nullptr);
-    }
-
-    if (buffer->byteLength() > 0) {
-        memcpy(contentsCopy.data(), oldContents.data(), buffer->byteLength());
-    }
-    ArrayBufferObject::detach(cx, buffer, oldContents);
-    return contentsCopy;
-}
-
-/* static */ void
-ArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf,
-                                          JS::ClassInfo* info)
-{
-    ArrayBufferObject& buffer = AsArrayBuffer(obj);
-
-    if (!buffer.ownsData()) {
-        return;
-    }
-
-    switch (buffer.bufferKind()) {
-      case PLAIN:
-        if (buffer.isPreparedForAsmJS()) {
-            info->objectsMallocHeapElementsAsmJS += mallocSizeOf(buffer.dataPointer());
-        } else {
-            info->objectsMallocHeapElementsNormal += mallocSizeOf(buffer.dataPointer());
-        }
-        break;
-      case MAPPED:
-        info->objectsNonHeapElementsNormal += buffer.byteLength();
-        break;
-      case WASM:
-        info->objectsNonHeapElementsWasm += buffer.byteLength();
-        MOZ_ASSERT(buffer.wasmMappedSize() >= buffer.byteLength());
-        info->wasmGuardPages += buffer.wasmMappedSize() - buffer.byteLength();
-        break;
-      case KIND_MASK:
-        MOZ_CRASH("bad bufferKind()");
-    }
-}
-
-/* static */ void
-ArrayBufferObject::finalize(FreeOp* fop, JSObject* obj)
-{
-    ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
-
-    if (buffer.ownsData()) {
-        buffer.releaseData(fop);
-    }
-}
-
-/* static */ void
-ArrayBufferObject::copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
-                            Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex,
-                            uint32_t count)
-{
-    MOZ_ASSERT(toBuffer->byteLength() >= count);
-    MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
-    MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
-    MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
-
-    memcpy(toBuffer->dataPointer() + toIndex, fromBuffer->dataPointer() + fromIndex, count);
-}
-
-/* static */ void
-ArrayBufferObject::trace(JSTracer* trc, JSObject* obj)
-{
-    // If this buffer is associated with an inline typed object,
-    // fix up the data pointer if the typed object was moved.
-    ArrayBufferObject& buf = obj->as<ArrayBufferObject>();
-
-    if (!buf.forInlineTypedObject()) {
-        return;
-    }
-
-    JSObject* view = MaybeForwarded(buf.firstView());
-    MOZ_ASSERT(view && view->is<InlineTransparentTypedObject>());
-
-    TraceManuallyBarrieredEdge(trc, &view, "array buffer inline typed object owner");
-    buf.setFixedSlot(DATA_SLOT,
-                     PrivateValue(view->as<InlineTransparentTypedObject>().inlineTypedMem()));
-}
-
-/* static */ size_t
-ArrayBufferObject::objectMoved(JSObject* obj, JSObject* old)
-{
-    ArrayBufferObject& dst = obj->as<ArrayBufferObject>();
-    const ArrayBufferObject& src = old->as<ArrayBufferObject>();
-
-    // Fix up possible inline data pointer.
-    if (src.hasInlineData()) {
-        dst.setFixedSlot(DATA_SLOT, PrivateValue(dst.inlineDataPointer()));
-    }
-
-    return 0;
-}
-
-ArrayBufferViewObject*
-ArrayBufferObject::firstView()
-{
-    return getFixedSlot(FIRST_VIEW_SLOT).isObject()
-        ? static_cast<ArrayBufferViewObject*>(&getFixedSlot(FIRST_VIEW_SLOT).toObject())
-        : nullptr;
-}
-
-void
-ArrayBufferObject::setFirstView(ArrayBufferViewObject* view)
-{
-    setFixedSlot(FIRST_VIEW_SLOT, ObjectOrNullValue(view));
-}
-
-bool
-ArrayBufferObject::addView(JSContext* cx, JSObject* viewArg)
-{
-    // Note: we don't pass in an ArrayBufferViewObject as the argument due to
-    // tricky inheritance in the various view classes. View classes do not
-    // inherit from ArrayBufferViewObject so won't be upcast automatically.
-    MOZ_ASSERT(viewArg->is<ArrayBufferViewObject>() || viewArg->is<TypedObject>());
-    ArrayBufferViewObject* view = static_cast<ArrayBufferViewObject*>(viewArg);
-
-    if (!firstView()) {
-        setFirstView(view);
-        return true;
-    }
-    return ObjectRealm::get(this).innerViews.get().addView(cx, this, view);
-}
-
-/*
- * InnerViewTable
- */
-
-constexpr size_t VIEW_LIST_MAX_LENGTH = 500;
-
-bool
-InnerViewTable::addView(JSContext* cx, ArrayBufferObject* buffer, ArrayBufferViewObject* view)
-{
-    // ArrayBufferObject entries are only added when there are multiple views.
-    MOZ_ASSERT(buffer->firstView());
-
-    Map::AddPtr p = map.lookupForAdd(buffer);
-
-    MOZ_ASSERT(!gc::IsInsideNursery(buffer));
-    bool addToNursery = nurseryKeysValid && gc::IsInsideNursery(view);
-
-    if (p) {
-        ViewVector& views = p->value();
-        MOZ_ASSERT(!views.empty());
-
-        if (addToNursery) {
-            // Only add the entry to |nurseryKeys| if it isn't already there.
-            if (views.length() >= VIEW_LIST_MAX_LENGTH) {
-                // To avoid quadratic blowup, skip the loop below if we end up
-                // adding enormous numbers of views for the same object.
-                nurseryKeysValid = false;
-            } else {
-                for (size_t i = 0; i < views.length(); i++) {
-                    if (gc::IsInsideNursery(views[i])) {
-                        addToNursery = false;
-                        break;
-                    }
-                }
-            }
-        }
-
-        if (!views.append(view)) {
-            ReportOutOfMemory(cx);
-            return false;
-        }
-    } else {
-        if (!map.add(p, buffer, ViewVector())) {
-            ReportOutOfMemory(cx);
-            return false;
-        }
-        // ViewVector has one inline element, so the first insertion is
-        // guaranteed to succeed.
-        MOZ_ALWAYS_TRUE(p->value().append(view));
-    }
-
-    if (addToNursery && !nurseryKeys.append(buffer)) {
-        nurseryKeysValid = false;
-    }
-
-    return true;
-}
-
-InnerViewTable::ViewVector*
-InnerViewTable::maybeViewsUnbarriered(ArrayBufferObject* buffer)
-{
-    Map::Ptr p = map.lookup(buffer);
-    if (p) {
-        return &p->value();
-    }
-    return nullptr;
-}
-
-void
-InnerViewTable::removeViews(ArrayBufferObject* buffer)
-{
-    Map::Ptr p = map.lookup(buffer);
-    MOZ_ASSERT(p);
-
-    map.remove(p);
-}
-
-/* static */ bool
-InnerViewTable::sweepEntry(JSObject** pkey, ViewVector& views)
-{
-    if (IsAboutToBeFinalizedUnbarriered(pkey)) {
-        return true;
-    }
-
-    MOZ_ASSERT(!views.empty());
-    size_t i = 0;
-    while (i < views.length()) {
-        if (IsAboutToBeFinalizedUnbarriered(&views[i])) {
-            // If the current element is garbage then remove it from the
-            // vector by moving the last one into its place.
-            views[i] = views.back();
-            views.popBack();
-        } else {
-            i++;
-        }
-    }
-
-    return views.empty();
-}
-
-void
-InnerViewTable::sweep()
-{
-    MOZ_ASSERT(nurseryKeys.empty());
-    map.sweep();
-}
-
-void
-InnerViewTable::sweepAfterMinorGC()
-{
-    MOZ_ASSERT(needsSweepAfterMinorGC());
-
-    if (nurseryKeysValid) {
-        for (size_t i = 0; i < nurseryKeys.length(); i++) {
-            JSObject* buffer = MaybeForwarded(nurseryKeys[i]);
-            Map::Ptr p = map.lookup(buffer);
-            if (!p) {
-                continue;
-            }
-
-            if (sweepEntry(&p->mutableKey(), p->value())) {
-                map.remove(buffer);
-            }
-        }
-        nurseryKeys.clear();
-    } else {
-        // Do the required sweeping by looking at every map entry.
-        nurseryKeys.clear();
-        sweep();
-
-        nurseryKeysValid = true;
-    }
-}
-
-size_t
-InnerViewTable::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
-{
-    size_t vectorSize = 0;
-    for (Map::Enum e(map); !e.empty(); e.popFront()) {
-        vectorSize += e.front().value().sizeOfExcludingThis(mallocSizeOf);
-    }
-
-    return vectorSize
-         + map.shallowSizeOfExcludingThis(mallocSizeOf)
-         + nurseryKeys.sizeOfExcludingThis(mallocSizeOf);
-}
-
-/*
- * ArrayBufferViewObject
- */
-
-/*
  * This method is used to trace TypedArrayObjects and DataViewObjects. We need
  * a custom tracer to move the object's data pointer if its owner was moved and
  * stores its data inline.
  */
 /* static */ void
 ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg)
 {
     NativeObject* obj = &objArg->as<NativeObject>();
-    HeapSlot& bufSlot = obj->getFixedSlotRef(TypedArrayObject::BUFFER_SLOT);
-    TraceEdge(trc, &bufSlot, "typedarray.buffer");
+    HeapSlot& bufSlot = obj->getFixedSlotRef(BUFFER_SLOT);
+    TraceEdge(trc, &bufSlot, "ArrayBufferViewObject.buffer");
 
     // Update obj's data pointer if it moved.
     if (bufSlot.isObject()) {
         if (IsArrayBuffer(&bufSlot.toObject())) {
             ArrayBufferObject& buf = AsArrayBuffer(MaybeForwarded(&bufSlot.toObject()));
-            uint32_t offset = uint32_t(obj->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT).toInt32());
+            uint32_t offset = uint32_t(obj->getFixedSlot(BYTEOFFSET_SLOT).toInt32());
             MOZ_ASSERT(offset <= INT32_MAX);
 
-            if (buf.forInlineTypedObject()) {
-                MOZ_ASSERT(buf.dataPointer() != nullptr);
-
-                // The data is inline with an InlineTypedObject associated with the
-                // buffer. Get a new address for the typed object if it moved.
-                JSObject* view = buf.firstView();
-
-                // Mark the object to move it into the tenured space.
-                TraceManuallyBarrieredEdge(trc, &view, "typed array nursery owner");
-                MOZ_ASSERT(view->is<InlineTypedObject>());
-                MOZ_ASSERT(view != obj);
-
-                size_t nfixed = obj->numFixedSlotsMaybeForwarded();
-                void* srcData = obj->getPrivate(nfixed);
-                void* dstData = view->as<InlineTypedObject>().inlineTypedMemForGC() + offset;
-                obj->setPrivateUnbarriered(nfixed, dstData);
+            // We don't expose the underlying ArrayBuffer for typed objects,
+            // and we don't allow constructing a TypedObject from an arbitrary
+            // ArrayBuffer, so we should never have a TypedArray/DataView with
+            // a buffer that has TypedObject views.
+            MOZ_RELEASE_ASSERT(!buf.forInlineTypedObject());
 
-                // We can't use a direct forwarding pointer here, as there might
-                // not be enough bytes available, and other views might have data
-                // pointers whose forwarding pointers would overlap this one.
-                if (trc->isTenuringTracer()) {
-                    Nursery& nursery = trc->runtime()->gc.nursery();
-                    nursery.maybeSetForwardingPointer(trc, srcData, dstData, /* direct = */ false);
-                }
-            } else {
-                MOZ_ASSERT_IF(buf.dataPointer() == nullptr, offset == 0);
+            MOZ_ASSERT_IF(buf.dataPointer() == nullptr, offset == 0);
 
-                // The data may or may not be inline with the buffer. The buffer
-                // can only move during a compacting GC, in which case its
-                // objectMoved hook has already updated the buffer's data pointer.
-                size_t nfixed = obj->numFixedSlotsMaybeForwarded();
-                obj->setPrivateUnbarriered(nfixed, buf.dataPointer() + offset);
-            }
+            // The data may or may not be inline with the buffer. The buffer
+            // can only move during a compacting GC, in which case its
+            // objectMoved hook has already updated the buffer's data pointer.
+            size_t nfixed = obj->numFixedSlotsMaybeForwarded();
+            obj->setPrivateUnbarriered(nfixed, buf.dataPointer() + offset);
         }
     }
 }
 
 template <>
 bool
 JSObject::is<js::ArrayBufferViewObject>() const
 {
     return is<DataViewObject>() || is<TypedArrayObject>();
 }
 
-template <>
-bool
-JSObject::is<js::ArrayBufferObjectMaybeShared>() const
-{
-    return is<ArrayBufferObject>() || is<SharedArrayBufferObject>();
-}
-
 void
 ArrayBufferViewObject::notifyBufferDetached(JSContext* cx, void* newData)
 {
-    if (is<DataViewObject>()) {
-        if (as<DataViewObject>().isSharedMemory()) {
-            return;
+    if (isSharedMemory()) {
+        return;
+    }
+
+    MOZ_ASSERT(!isSharedMemory());
+    setFixedSlot(LENGTH_SLOT, Int32Value(0));
+    setFixedSlot(BYTEOFFSET_SLOT, Int32Value(0));
+
+    // If the object is in the nursery, the buffer will be freed by the next
+    // nursery GC. Free the data slot pointer if the object has no inline data.
+    if (is<TypedArrayObject>()) {
+        TypedArrayObject& tarr = as<TypedArrayObject>();
+        Nursery& nursery = cx->nursery();
+        if (isTenured() && !hasBuffer() && !tarr.hasInlineElements() &&
+            !nursery.isInside(tarr.elements()))
+        {
+            js_free(tarr.elements());
         }
-        as<DataViewObject>().notifyBufferDetached(newData);
-    } else if (is<TypedArrayObject>()) {
-        if (as<TypedArrayObject>().isSharedMemory()) {
-            return;
-        }
-        as<TypedArrayObject>().notifyBufferDetached(cx, newData);
-    } else {
-        as<OutlineTypedObject>().notifyBufferDetached(newData);
     }
+
+    setPrivate(newData);
 }
 
 uint8_t*
 ArrayBufferViewObject::dataPointerUnshared(const JS::AutoRequireNoGC& nogc)
 {
-    if (is<DataViewObject>()) {
-        MOZ_ASSERT(!as<DataViewObject>().isSharedMemory());
-        return static_cast<uint8_t*>(as<DataViewObject>().dataPointerUnshared());
-    }
-    if (is<TypedArrayObject>()) {
-        MOZ_ASSERT(!as<TypedArrayObject>().isSharedMemory());
-        return static_cast<uint8_t*>(as<TypedArrayObject>().viewDataUnshared());
-    }
-    return as<TypedObject>().typedMem(nogc);
+    return static_cast<uint8_t*>(dataPointerUnshared());
 }
 
-#ifdef DEBUG
-bool
-ArrayBufferViewObject::isSharedMemory()
-{
-    if (is<TypedArrayObject>()) {
-        return as<TypedArrayObject>().isSharedMemory();
-    }
-    return false;
-}
-#endif
-
 void
 ArrayBufferViewObject::setDataPointerUnshared(uint8_t* data)
 {
-    if (is<DataViewObject>()) {
-        MOZ_ASSERT(!as<DataViewObject>().isSharedMemory());
-        as<DataViewObject>().setPrivate(data);
-    } else if (is<TypedArrayObject>()) {
-        MOZ_ASSERT(!as<TypedArrayObject>().isSharedMemory());
-        as<TypedArrayObject>().setPrivate(data);
-    } else if (is<OutlineTypedObject>()) {
-        as<OutlineTypedObject>().setData(data);
-    } else {
-        MOZ_CRASH();
-    }
+    MOZ_ASSERT(!isSharedMemory());
+    setPrivate(data);
 }
 
 /* static */ ArrayBufferObjectMaybeShared*
 ArrayBufferViewObject::bufferObject(JSContext* cx, Handle<ArrayBufferViewObject*> thisObject)
 {
     if (thisObject->is<TypedArrayObject>()) {
         Rooted<TypedArrayObject*> typedArray(cx, &thisObject->as<TypedArrayObject>());
         if (!TypedArrayObject::ensureHasBuffer(cx, typedArray)) {
             return nullptr;
         }
-        return thisObject->as<TypedArrayObject>().bufferEither();
     }
-    MOZ_ASSERT(thisObject->is<DataViewObject>());
-    return &thisObject->as<DataViewObject>().arrayBufferEither();
+    return thisObject->bufferEither();
 }
 
 /* JS Friend API */
 
 JS_FRIEND_API(bool)
 JS_IsArrayBufferViewObject(JSObject* obj)
 {
     obj = CheckedUnwrap(obj);
@@ -1818,294 +123,41 @@ JS_FRIEND_API(JSObject*)
 js::UnwrapArrayBufferView(JSObject* obj)
 {
     if (JSObject* unwrapped = CheckedUnwrap(obj)) {
         return unwrapped->is<ArrayBufferViewObject>() ? unwrapped : nullptr;
     }
     return nullptr;
 }
 
-JS_FRIEND_API(uint32_t)
-JS_GetArrayBufferByteLength(JSObject* obj)
-{
-    obj = CheckedUnwrap(obj);
-    return obj ? AsArrayBuffer(obj).byteLength() : 0;
-}
-
-JS_FRIEND_API(uint8_t*)
-JS_GetArrayBufferData(JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC&)
-{
-    obj = CheckedUnwrap(obj);
-    if (!obj) {
-        return nullptr;
-    }
-    if (!IsArrayBuffer(obj)) {
-        return nullptr;
-    }
-    *isSharedMemory = false;
-    return AsArrayBuffer(obj).dataPointer();
-}
-
-JS_FRIEND_API(bool)
-JS_DetachArrayBuffer(JSContext* cx, HandleObject obj)
-{
-    AssertHeapIsIdle();
-    CHECK_THREAD(cx);
-    cx->check(obj);
-
-    if (!obj->is<ArrayBufferObject>()) {
-        JS_ReportErrorASCII(cx, "ArrayBuffer object required");
-        return false;
-    }
-
-    Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
-
-    if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
-        return false;
-    }
-
-    ArrayBufferObject::BufferContents newContents =
-        buffer->hasStealableContents() ? ArrayBufferObject::BufferContents::createPlain(nullptr)
-                                       : buffer->contents();
-
-    ArrayBufferObject::detach(cx, buffer, newContents);
-
-    return true;
-}
-
-JS_FRIEND_API(bool)
-JS_IsDetachedArrayBufferObject(JSObject* obj)
-{
-    obj = CheckedUnwrap(obj);
-    if (!obj) {
-        return false;
-    }
-
-    return obj->is<ArrayBufferObject>() && obj->as<ArrayBufferObject>().isDetached();
-}
-
-JS_FRIEND_API(JSObject*)
-JS_NewArrayBuffer(JSContext* cx, uint32_t nbytes)
-{
-    AssertHeapIsIdle();
-    CHECK_THREAD(cx);
-    MOZ_ASSERT(nbytes <= INT32_MAX);
-    return ArrayBufferObject::create(cx, nbytes);
-}
-
-JS_PUBLIC_API(JSObject*)
-JS_NewArrayBufferWithContents(JSContext* cx, size_t nbytes, void* data)
-{
-    AssertHeapIsIdle();
-    CHECK_THREAD(cx);
-    MOZ_ASSERT_IF(!data, nbytes == 0);
-
-    ArrayBufferObject::BufferContents contents =
-        ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(data);
-    return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData,
-                                     /* proto = */ nullptr, TenuredObject);
-}
-
-JS_PUBLIC_API(JSObject*)
-JS_NewExternalArrayBuffer(JSContext* cx, size_t nbytes, void* data,
-                          JS::BufferContentsFreeFunc freeFunc, void* freeUserData)
-{
-    AssertHeapIsIdle();
-    CHECK_THREAD(cx);
-
-    MOZ_ASSERT(data);
-    MOZ_ASSERT(nbytes > 0);
-
-    ArrayBufferObject::BufferContents contents =
-        ArrayBufferObject::BufferContents::createExternal(data, freeFunc, freeUserData);
-    return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData,
-                                     /* proto = */ nullptr, TenuredObject);
-}
-
-JS_PUBLIC_API(JSObject*)
-JS_NewArrayBufferWithExternalContents(JSContext* cx, size_t nbytes, void* data)
-{
-    AssertHeapIsIdle();
-    CHECK_THREAD(cx);
-    MOZ_ASSERT_IF(!data, nbytes == 0);
-    ArrayBufferObject::BufferContents contents =
-        ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(data);
-    return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::DoesntOwnData,
-                                     /* proto = */ nullptr, TenuredObject);
-}
-
-JS_FRIEND_API(bool)
-JS_IsArrayBufferObject(JSObject* obj)
-{
-    obj = CheckedUnwrap(obj);
-    return obj && obj->is<ArrayBufferObject>();
-}
-
-JS_FRIEND_API(bool)
-JS_ArrayBufferHasData(JSObject* obj)
-{
-    return CheckedUnwrap(obj)->as<ArrayBufferObject>().hasData();
-}
-
-JS_FRIEND_API(JSObject*)
-js::UnwrapArrayBuffer(JSObject* obj)
-{
-    if (JSObject* unwrapped = CheckedUnwrap(obj)) {
-        return unwrapped->is<ArrayBufferObject>() ? unwrapped : nullptr;
-    }
-    return nullptr;
-}
-
-JS_FRIEND_API(JSObject*)
-js::UnwrapSharedArrayBuffer(JSObject* obj)
-{
-    if (JSObject* unwrapped = CheckedUnwrap(obj)) {
-        return unwrapped->is<SharedArrayBufferObject>() ? unwrapped : nullptr;
-    }
-    return nullptr;
-}
-
-JS_PUBLIC_API(void*)
-JS_ExternalizeArrayBufferContents(JSContext* cx, HandleObject obj)
-{
-    AssertHeapIsIdle();
-    CHECK_THREAD(cx);
-    cx->check(obj);
-
-    if (!obj->is<ArrayBufferObject>()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return nullptr;
-    }
-
-    Handle<ArrayBufferObject*> buffer = obj.as<ArrayBufferObject>();
-    if (!buffer->isPlain()) {
-        // This operation isn't supported on mapped or wsm ArrayBufferObjects.
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return nullptr;
-    }
-    if (buffer->isDetached()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
-        return nullptr;
-    }
-
-    // The caller assumes that a plain malloc'd buffer is returned.
-    // hasStealableContents is true for mapped buffers, so we must additionally
-    // require that the buffer is plain. In the future, we could consider
-    // returning something that handles releasing the memory.
-    bool hasStealableContents = buffer->hasStealableContents();
-
-    return ArrayBufferObject::externalizeContents(cx, buffer, hasStealableContents).data();
-}
-
-JS_PUBLIC_API(void*)
-JS_StealArrayBufferContents(JSContext* cx, HandleObject objArg)
-{
-    AssertHeapIsIdle();
-    CHECK_THREAD(cx);
-    cx->check(objArg);
-
-    JSObject* obj = CheckedUnwrap(objArg);
-    if (!obj) {
-        return nullptr;
-    }
-
-    if (!obj->is<ArrayBufferObject>()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
-        return nullptr;
-    }
-
-    Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
-    if (buffer->isDetached()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
-        return nullptr;
-    }
-
-    if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
-        return nullptr;
-    }
-
-    // The caller assumes that a plain malloc'd buffer is returned.
-    // hasStealableContents is true for mapped buffers, so we must additionally
-    // require that the buffer is plain. In the future, we could consider
-    // returning something that handles releasing the memory.
-    bool hasStealableContents = buffer->hasStealableContents() && buffer->isPlain();
-
-    AutoRealm ar(cx, buffer);
-    return ArrayBufferObject::stealContents(cx, buffer, hasStealableContents).data();
-}
-
-JS_PUBLIC_API(JSObject*)
-JS_NewMappedArrayBufferWithContents(JSContext* cx, size_t nbytes, void* data)
-{
-    AssertHeapIsIdle();
-    CHECK_THREAD(cx);
-
-    MOZ_ASSERT(data);
-    ArrayBufferObject::BufferContents contents =
-        ArrayBufferObject::BufferContents::create<ArrayBufferObject::MAPPED>(data);
-    return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData,
-                                     /* proto = */ nullptr, TenuredObject);
-}
-
-JS_PUBLIC_API(void*)
-JS_CreateMappedArrayBufferContents(int fd, size_t offset, size_t length)
-{
-    return ArrayBufferObject::createMappedContents(fd, offset, length).data();
-}
-
-JS_PUBLIC_API(void)
-JS_ReleaseMappedArrayBufferContents(void* contents, size_t length)
-{
-    gc::DeallocateMappedContent(contents, length);
-}
-
-JS_FRIEND_API(bool)
-JS_IsMappedArrayBufferObject(JSObject* obj)
-{
-    obj = CheckedUnwrap(obj);
-    if (!obj) {
-        return false;
-    }
-
-    return obj->is<ArrayBufferObject>() && obj->as<ArrayBufferObject>().isMapped();
-}
-
 JS_FRIEND_API(void*)
 JS_GetArrayBufferViewData(JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj) {
         return nullptr;
     }
-    if (obj->is<DataViewObject>()) {
-        DataViewObject& dv = obj->as<DataViewObject>();
-        *isSharedMemory = dv.isSharedMemory();
-        return dv.dataPointerEither().unwrap(/*safe - caller sees isSharedMemory flag*/);
-    }
-    TypedArrayObject& ta = obj->as<TypedArrayObject>();
-    *isSharedMemory = ta.isSharedMemory();
-    return ta.viewDataEither().unwrap(/*safe - caller sees isSharedMemory flag*/);
+
+    ArrayBufferViewObject& view = obj->as<ArrayBufferViewObject>();
+    *isSharedMemory = view.isSharedMemory();
+    return view.dataPointerEither().unwrap(/*safe - caller sees isSharedMemory flag*/);
 }
 
 JS_FRIEND_API(JSObject*)
 JS_GetArrayBufferViewBuffer(JSContext* cx, HandleObject objArg, bool* isSharedMemory)
 {
     AssertHeapIsIdle();
     CHECK_THREAD(cx);
     cx->check(objArg);
 
     JSObject* obj = CheckedUnwrap(objArg);
     if (!obj) {
         return nullptr;
     }
-    MOZ_ASSERT(obj->is<ArrayBufferViewObject>());
-
-    Rooted<ArrayBufferViewObject*> viewObject(cx, static_cast<ArrayBufferViewObject*>(obj));
+    Rooted<ArrayBufferViewObject*> viewObject(cx, &obj->as<ArrayBufferViewObject>());
     ArrayBufferObjectMaybeShared* buffer = ArrayBufferViewObject::bufferObject(cx, viewObject);
     *isSharedMemory = buffer->is<SharedArrayBufferObject>();
     return buffer;
 }
 
 JS_FRIEND_API(uint32_t)
 JS_GetArrayBufferViewByteLength(JSObject* obj)
 {
@@ -2149,46 +201,13 @@ js::GetArrayBufferViewLengthAndData(JSOb
                                     uint8_t** data)
 {
     MOZ_ASSERT(obj->is<ArrayBufferViewObject>());
 
     *length = obj->is<DataViewObject>()
               ? obj->as<DataViewObject>().byteLength()
               : obj->as<TypedArrayObject>().byteLength();
 
-    if (obj->is<DataViewObject>()) {
-        DataViewObject& dv = obj->as<DataViewObject>();
-        *isSharedMemory = dv.isSharedMemory();
-        *data = static_cast<uint8_t*>(
-            dv.dataPointerEither().unwrap(/*safe - caller sees isShared flag*/));
-    }
-    else {
-        TypedArrayObject& ta = obj->as<TypedArrayObject>();
-        *isSharedMemory = ta.isSharedMemory();
-        *data = static_cast<uint8_t*>(
-            ta.viewDataEither().unwrap(/*safe - caller sees isShared flag*/));
-    }
+    ArrayBufferViewObject& view = obj->as<ArrayBufferViewObject>();
+    *isSharedMemory = view.isSharedMemory();
+    *data = static_cast<uint8_t*>(
+            view.dataPointerEither().unwrap(/*safe - caller sees isShared flag*/));
 }
-
-JS_FRIEND_API(JSObject*)
-JS_GetObjectAsArrayBuffer(JSObject* obj, uint32_t* length, uint8_t** data)
-{
-    if (!(obj = CheckedUnwrap(obj))) {
-        return nullptr;
-    }
-    if (!IsArrayBuffer(obj)) {
-        return nullptr;
-    }
-
-    *length = AsArrayBuffer(obj).byteLength();
-    *data = AsArrayBuffer(obj).dataPointer();
-
-    return obj;
-}
-
-JS_FRIEND_API(void)
-js::GetArrayBufferLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data)
-{
-    MOZ_ASSERT(IsArrayBuffer(obj));
-    *length = AsArrayBuffer(obj).byteLength();
-    *data = AsArrayBuffer(obj).dataPointer();
-    *isSharedMemory = false;
-}
copy from js/src/vm/ArrayBufferObject.h
copy to js/src/vm/ArrayBufferViewObject.h
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferViewObject.h
@@ -1,680 +1,162 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef vm_ArrayBufferObject_h
-#define vm_ArrayBufferObject_h
-
-#include "mozilla/Maybe.h"
+#ifndef vm_ArrayBufferViewObject_h
+#define vm_ArrayBufferViewObject_h
 
 #include "builtin/TypedObjectConstants.h"
-#include "js/GCHashTable.h"
-#include "vm/JSObject.h"
-#include "vm/Runtime.h"
+#include "vm/ArrayBufferObject.h"
+#include "vm/NativeObject.h"
+#include "vm/SharedArrayObject.h"
 #include "vm/SharedMem.h"
-#include "wasm/WasmTypes.h"
 
 namespace js {
 
-class ArrayBufferViewObject;
-class WasmArrayRawBuffer;
-
-// Create a new mapping of size `mappedSize` with an initially committed prefix
-// of size `initialCommittedSize`.  Both arguments denote bytes and must be
-// multiples of the page size, with `initialCommittedSize` <= `mappedSize`.
-// Returns nullptr on failure.
-void* MapBufferMemory(size_t mappedSize, size_t initialCommittedSize);
-
-// Commit additional memory in an existing mapping.  `dataEnd` must be the
-// correct value for the end of the existing committed area, and `delta` must be
-// a byte amount to grow the mapping by, and must be a multiple of the page
-// size.  Returns false on failure.
-bool CommitBufferMemory(void* dataEnd, uint32_t delta);
-
-#ifndef WASM_HUGE_MEMORY
-// Extend an existing mapping by adding uncommited pages to it.  `dataStart`
-// must be the pointer to the start of the existing mapping, `mappedSize` the
-// size of the existing mapping, and `newMappedSize` the size of the extended
-// mapping (sizes in bytes), with `mappedSize` <= `newMappedSize`.  Both sizes
-// must be divisible by the page size.  Returns false on failure.
-bool ExtendBufferMapping(void* dataStart, size_t mappedSize, size_t newMappedSize);
-#endif
-
-// Remove an existing mapping.  `dataStart` must be the pointer to the start of
-// the mapping, and `mappedSize` the size of that mapping.
-void UnmapBufferMemory(void* dataStart, size_t mappedSize);
-
-// Return the number of currently live mapped buffers.
-int32_t LiveMappedBufferCount();
-
-// The inheritance hierarchy for the various classes relating to typed arrays
-// is as follows.
-//
-// - NativeObject
-//   - ArrayBufferObjectMaybeShared
-//     - ArrayBufferObject
-//     - SharedArrayBufferObject
-//   - DataViewObject
-//   - TypedArrayObject (declared in vm/TypedArrayObject.h)
-//     - TypedArrayObjectTemplate
-//       - Int8ArrayObject
-//       - Uint8ArrayObject
-//       - ...
-// - JSObject
-//   - ArrayBufferViewObject
-//   - TypedObject (declared in builtin/TypedObject.h)
-//
-// Note that |TypedArrayObjectTemplate| is just an implementation
-// detail that makes implementing its various subclasses easier.
-//
-// ArrayBufferObject and SharedArrayBufferObject are unrelated data types:
-// the racy memory of the latter cannot substitute for the non-racy memory of
-// the former; the non-racy memory of the former cannot be used with the atomics;
-// the former can be detached and the latter not.  Hence they have been
-// separated completely.
-//
-// Most APIs will only accept ArrayBufferObject.  ArrayBufferObjectMaybeShared
-// exists as a join point to allow APIs that can take or use either, notably AsmJS.
-//
-// In contrast with the separation of ArrayBufferObject and
-// SharedArrayBufferObject, the TypedArray types can map either.
-//
-// The possible data ownership and reference relationships with ArrayBuffers
-// and related classes are enumerated below. These are the possible locations
-// for typed data:
-//
-// (1) malloc'ed or mmap'ed data own