Merge autoland to mozilla-central a=merge
authorRazvan Maries <rmaries@mozilla.com>
Thu, 20 Jun 2019 12:45:34 +0300
changeset 479292 a440f0629814ea638bdbee6cf2f1a0425dd04c61
parent 479232 9767f3d2f79f7dd37136b1034ab1229ce5c4354b (current diff)
parent 479291 ef0e2b8429328d53cefee4ce64ab6b3cd2df937c (diff)
child 479339 74b3b3d1544c71eecda9de902a09573f6329cb0d
child 479692 20956a783e086e85f5cd8f394020bda54cc17368
push id36177
push userrmaries@mozilla.com
push dateThu, 20 Jun 2019 09:46:31 +0000
treeherdermozilla-central@a440f0629814 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone69.0a1
first release with
nightly linux32
a440f0629814 / 69.0a1 / 20190620094631 / files
nightly linux64
a440f0629814 / 69.0a1 / 20190620094631 / files
nightly mac
a440f0629814 / 69.0a1 / 20190620094631 / files
nightly win32
a440f0629814 / 69.0a1 / 20190620094631 / files
nightly win64
a440f0629814 / 69.0a1 / 20190620094631 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central a=merge
testing/web-platform/meta/css/css-backgrounds/background-image-first-line.html.ini
testing/web-platform/meta/webrtc/RTCDataChannel-id.html.ini
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1207,17 +1207,16 @@ pref("services.sync.prefs.sync.browser.n
 pref("services.sync.prefs.sync.browser.newtabpage.pinned", true);
 pref("services.sync.prefs.sync.browser.offline-apps.notify", true);
 pref("services.sync.prefs.sync.browser.safebrowsing.phishing.enabled", true);
 pref("services.sync.prefs.sync.browser.safebrowsing.malware.enabled", true);
 pref("services.sync.prefs.sync.browser.safebrowsing.downloads.enabled", true);
 pref("services.sync.prefs.sync.browser.safebrowsing.passwords.enabled", true);
 pref("services.sync.prefs.sync.browser.search.update", true);
 pref("services.sync.prefs.sync.browser.search.widget.inNavBar", true);
-pref("services.sync.prefs.sync.browser.sessionstore.restore_on_demand", true);
 pref("services.sync.prefs.sync.browser.startup.homepage", true);
 pref("services.sync.prefs.sync.browser.startup.page", true);
 pref("services.sync.prefs.sync.browser.tabs.loadInBackground", true);
 pref("services.sync.prefs.sync.browser.tabs.warnOnClose", true);
 pref("services.sync.prefs.sync.browser.tabs.warnOnOpen", true);
 pref("services.sync.prefs.sync.browser.taskbar.previews.enable", true);
 pref("services.sync.prefs.sync.browser.urlbar.matchBuckets", true);
 pref("services.sync.prefs.sync.browser.urlbar.maxRichResults", true);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1941,16 +1941,20 @@ var gBrowserInit = {
       // auto-resume downloads begin (such as after crashing or quitting with
       // active downloads) and speeds up the first-load of the download manager UI.
       // If the user manually opens the download manager before the timeout, the
       // downloads will start right away, and initializing again won't hurt.
       try {
         DownloadsCommon.initializeAllDataLinks();
         ChromeUtils.import("resource:///modules/DownloadsTaskbar.jsm", {})
           .DownloadsTaskbar.registerIndicator(window);
+        if (AppConstants.platform == "macosx") {
+          ChromeUtils.import("resource:///modules/DownloadsMacFinderProgress.jsm")
+            .DownloadsMacFinderProgress.register();
+        }
       } catch (ex) {
         Cu.reportError(ex);
       }
     }, {timeout: 10000});
 
     if (Win7Features) {
       scheduleIdleTask(() => Win7Features.onOpenWindow());
     }
--- a/browser/components/about/AboutProtectionsHandler.jsm
+++ b/browser/components/about/AboutProtectionsHandler.jsm
@@ -6,17 +6,17 @@
 
 var EXPORTED_SYMBOLS = ["AboutProtectionsHandler"];
 
 const {RemotePages} = ChromeUtils.import("resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm");
 
 var AboutProtectionsHandler = {
   _inited: false,
   _topics: [
-    "ArrivedOnPage",
+    "openContentBlockingPreferences",
   ],
 
   init() {
     this.pageListener = new RemotePages("about:protections");
     for (let topic of this._topics) {
       this.pageListener.addMessageListener(topic, this.receiveMessage);
     }
     this._inited = true;
@@ -28,16 +28,16 @@ var AboutProtectionsHandler = {
     }
     for (let topic of this._topics) {
       this.pageListener.removeMessageListener(topic, this.receiveMessage);
     }
     this.pageListener.destroy();
   },
 
   receiveMessage(aMessage) {
+    let win = aMessage.target.browser.ownerGlobal;
     switch (aMessage.name) {
-      case "ArrivedOnPage": {
-        // Successfully recieved a message
+      case "openContentBlockingPreferences":
+        win.openPreferences("privacy-trackingprotection", {origin: "about-protections"});
         break;
-      }
     }
   },
 };
new file mode 100644
--- /dev/null
+++ b/browser/components/downloads/DownloadsMacFinderProgress.jsm
@@ -0,0 +1,87 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+/* 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/. */
+
+/**
+ * Handles the download progress indicator of the macOS Finder.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = [
+  "DownloadsMacFinderProgress",
+];
+
+const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  Downloads: "resource://gre/modules/Downloads.jsm",
+});
+
+var DownloadsMacFinderProgress = {
+  /**
+   * Maps the path of the download, to the according progress indicator instance.
+   */
+  _finderProgresses: null,
+
+  /**
+   * This method is called after a new browser window on macOS is opened, it
+   * registers for receiving download events for the progressbar of the Finder.
+   */
+  register() {
+    // Ensure to register only once per process and not for every window.
+    if (!this._finderProgresses) {
+      this._finderProgresses = new Map();
+      Downloads.getList(Downloads.ALL).then(list => list.addView(this));
+    }
+  },
+
+  onDownloadAdded(download) {
+    if (download.stopped) {
+      return;
+    }
+
+    let finderProgress = Cc["@mozilla.org/widget/macfinderprogress;1"]
+      .createInstance(Ci.nsIMacFinderProgress);
+
+    let path = download.target.path;
+
+    finderProgress.init(path, () => {
+      download.cancel().catch(Cu.reportError);
+      download.removePartialData().catch(Cu.reportError);
+    });
+
+    if (download.hasProgress) {
+      finderProgress.updateProgress(download.currentBytes, download.totalBytes);
+    } else {
+      finderProgress.updateProgress(0, 0);
+    }
+    this._finderProgresses.set(path, finderProgress);
+  },
+
+  onDownloadChanged(download) {
+    let path = download.target.path;
+    let finderProgress = this._finderProgresses.get(path);
+    if (!finderProgress) {
+      // The download is not tracked, it may have been restarted,
+      // thus forward the call to onDownloadAdded to check if it should be tracked.
+      this.onDownloadAdded(download);
+    } else if (download.stopped) {
+      finderProgress.end();
+      this._finderProgresses.delete(path);
+    } else {
+      finderProgress.updateProgress(download.currentBytes, download.totalBytes);
+    }
+  },
+
+  onDownloadRemoved(download) {
+    let path = download.target.path;
+    let finderProgress = this._finderProgresses.get(path);
+    if (finderProgress) {
+      finderProgress.end();
+      this._finderProgresses.delete(path);
+    }
+  },
+};
--- a/browser/components/downloads/moz.build
+++ b/browser/components/downloads/moz.build
@@ -13,10 +13,15 @@ JAR_MANIFESTS += ['jar.mn']
 
 EXTRA_JS_MODULES += [
     'DownloadsCommon.jsm',
     'DownloadsSubview.jsm',
     'DownloadsTaskbar.jsm',
     'DownloadsViewUI.jsm',
 ]
 
+toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
+
+if toolkit == 'cocoa':
+    EXTRA_JS_MODULES += ['DownloadsMacFinderProgress.jsm']
+
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Downloads Panel')
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -102,17 +102,16 @@ Preferences.addAll([
   browser.taskbar.previews.enable
   - true if tabs are to be shown in the Windows 7 taskbar
   */
 
   { id: "browser.link.open_newwindow", type: "int" },
   { id: "browser.tabs.loadInBackground", type: "bool", inverted: true },
   { id: "browser.tabs.warnOnClose", type: "bool" },
   { id: "browser.tabs.warnOnOpen", type: "bool" },
-  { id: "browser.sessionstore.restore_on_demand", type: "bool" },
   { id: "browser.ctrlTab.recentlyUsedOrder", type: "bool" },
 
   // CFR
   {id: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons", type: "bool"},
   {id: "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features", type: "bool"},
 
   // Fonts
   { id: "font.language.group", type: "wstring" },
--- a/browser/components/protections/content/protections.css
+++ b/browser/components/protections/content/protections.css
@@ -1,12 +1,94 @@
 /* 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/. */
-.under-construction {
-    background-image: url("chrome://browser/content/illustrations/under-construction.svg");
-    background-repeat: no-repeat;
-    background-position: center;
-    min-height: 300px;
-    min-width: 300px;
-    /* Move the image down a bit - should be slightly higher than halfway down the page */
-    margin-top: -10%;
+ :root {
+   --card-box-shadow: 0 1px 4px 0 rgba(12,12,13,0.1), 0 1px 0 0 rgba(0,0,0,0.05);
+   --card-background:  #FFF;
+   --clickable-text-hover: hsla(0,0%,70%,.2);
+   --clickable-text-active: hsla(0,0%,70%,.3);
+   --card-divider: rgba(12,12,13,0.1) 1px solid;
+   --report-background: #FAFAFC;
+   --card-padding: 22px;
+ }
+
+body {
+  background-color: var(--report-background);
+  font: message-box;
+  margin-top: 82px;
+}
+
+#report-title {
+  font-size: 20px;
+  font-weight: 300;
+  margin-bottom: 22px;
+}
+
+#report-content {
+  width: 763px;
+  margin: 0 auto;
+}
+
+.etp-card .icon {
+  width: 60px;
+  height: 60px;
+  background: url("chrome://browser/skin/controlcenter/tracking-protection.svg") no-repeat center/cover;
+  grid-column: 1;
+  margin: 0 auto;
+}
+
+.report-card {
+  display: grid;
+  grid-template-columns: 100%;
+  grid-template-rows: 20% auto;
+  border-radius: 3px;
+  background-color: var(--card-background);
+  box-shadow: var(--card-box-shadow);
 }
+
+.report-card .card-header {
+  display: grid;
+  grid-template-columns: 1fr 7fr;
+  padding: var(--card-padding);
+  grid-gap: var(--card-padding);
+}
+
+.report-card .card-title {
+  font-size: 16px;
+  line-height: 22px;
+  margin: 0;
+  cursor: default;
+}
+
+.report-card .content {
+  margin-bottom: 24px;
+  margin-top: 5px;
+  font-size: 14px;
+  cursor: default;
+}
+
+#protection-details {
+  background: url("chrome://browser/skin/settings.svg") no-repeat 3px 3px;
+  padding: 4px 4px 4px 24px;
+  font-size: 12px;
+  display: inline;
+  cursor: default;
+}
+
+#protection-details:hover {
+  background-color: var(--clickable-text-hover);
+}
+
+#protection-details:hover:active {
+  background-color: var(--clickable-text-active);
+}
+
+#protection-details span {
+  font-weight: bold;
+}
+
+.card-body {
+  border-top: var(--card-divider);
+  grid-column: span 2;
+  grid-row: 2;
+  margin-bottom: 24px;
+}
--- a/browser/components/protections/content/protections.html
+++ b/browser/components/protections/content/protections.html
@@ -2,21 +2,39 @@
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!DOCTYPE html>
 <html>
   <head>
     <meta charset="utf-8">
     <meta http-equiv="Content-Security-Policy" content="default-src chrome: blob:">
-    <link rel="stylesheet" media="screen, projection" type="text/css"
-          href="chrome://global/skin/in-content/info-pages.css" title="infop">
     <link rel="stylesheet" type="text/css"
           href="chrome://browser/content/protections.css">
     <link rel="icon" href="chrome://global/skin/icons/warning.svg">
     <script type="text/javascript" src="chrome://browser/content/protections.js"></script>
     <title>Protection Report</title>
   </head>
 
   <body>
-    <div class="under-construction"><div/>
+    <div id="report-content">
+      <h2 id="report-title">Privacy Protections</h2>
+      <div class="report-card etp-card">
+        <div class="card-header">
+          <div class="icon"></div>
+          <div class="wrapper">
+            <h3 class="card-title">
+              Enhanced Tracking Protection
+            </h3>
+            <p class="content">
+              Trackers follow you around online to collect information about your browsing habits and interests. Firefox blocks many of these trackers and other malicious scripts.
+            </p>
+            <p id="protection-details">
+              Protection Level is set to <span>Standard</span>
+            </p>
+          </div>
+        </div>
+        <div class="card-body">
+        </div>
+      </div>
+    </div>
   </body>
 </html>
--- a/browser/components/protections/content/protections.js
+++ b/browser/components/protections/content/protections.js
@@ -1,9 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* eslint-env mozilla/frame-script */
 
 document.addEventListener("DOMContentLoaded", (e) => {
-  RPMSendAsyncMessage("ArrivedOnPage");
+  let protectionDetails = document.getElementById("protection-details");
+  protectionDetails.addEventListener("click", () => {
+    RPMSendAsyncMessage("openContentBlockingPreferences");
+  });
 });
--- a/browser/modules/RemotePrompt.jsm
+++ b/browser/modules/RemotePrompt.jsm
@@ -12,20 +12,24 @@ ChromeUtils.defineModuleGetter(this, "Pr
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 
 var RemotePrompt = {
   // Listeners are added in BrowserGlue.jsm
   receiveMessage(message) {
     switch (message.name) {
       case "Prompt:Open":
-        if (message.data.uri) {
-          this.openModalWindow(message.data, message.target);
+        const COMMON_DIALOG = "chrome://global/content/commonDialog.xul";
+        const SELECT_DIALOG = "chrome://global/content/selectDialog.xul";
+
+        if (message.data.tabPrompt) {
+          this.openTabPrompt(message.data, message.target);
         } else {
-          this.openTabPrompt(message.data, message.target);
+          let uri = (message.data.promptType == "select") ? SELECT_DIALOG : COMMON_DIALOG;
+          this.openModalWindow(uri, message.data, message.target);
         }
         break;
     }
   },
 
   openTabPrompt(args, browser) {
     let window = browser.ownerGlobal;
     let tabPrompt = window.gBrowser.getTabModalPromptBox(browser);
@@ -80,23 +84,23 @@ var RemotePrompt = {
       // there's other stuff in nsWindowWatcher::OpenWindowInternal
       // that we might need to do here as well.
     } catch (ex) {
       Cu.reportError(ex);
       onPromptClose(true);
     }
   },
 
-  openModalWindow(args, browser) {
+  openModalWindow(uri, args, browser) {
     let window = browser.ownerGlobal;
     try {
       PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser);
       let bag = PromptUtils.objectToPropBag(args);
 
-      Services.ww.openWindow(window, args.uri, "_blank",
+      Services.ww.openWindow(window, uri, "_blank",
                              "centerscreen,chrome,modal,titlebar", bag);
 
       PromptUtils.propBagToObject(bag, args);
     } finally {
       PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
       browser.messageManager.sendAsyncMessage("Prompt:Close", args);
     }
   },
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -1117,17 +1117,17 @@ panelview .toolbarbutton-1,
   -moz-context-properties: fill, fill-opacity;
   content: url(chrome://browser/skin/back-12.svg);
   fill: currentColor;
   fill-opacity: 0.6;
   float: right;
   transform: translateY(1px);
 }
 
-#main-window:not([customizing]) .subviewbutton-nav[disabled=true]::after {
+:root:not([customizing]) .subviewbutton-nav[disabled=true]::after {
   opacity: 0.4;
 }
 
 .PanelUI-subView .subviewbutton-nav:-moz-locale-dir(ltr)::after {
   transform: scaleX(-1) translateY(1px);
 }
 
 .subviewbutton[shortcut]::after,
--- a/devtools/client/debugger/src/actions/event-listeners.js
+++ b/devtools/client/debugger/src/actions/event-listeners.js
@@ -19,17 +19,17 @@ async function updateBreakpoints(dispatc
   dispatch({ type: "UPDATE_EVENT_LISTENERS", active: newEvents });
 
   const current = await asyncStore.eventListenerBreakpoints;
   asyncStore.eventListenerBreakpoints = {
     ...current,
     active: newEvents,
   };
 
-  client.setEventListenerBreakpoints(newEvents);
+  await client.setEventListenerBreakpoints(newEvents);
 }
 
 async function updateExpanded(dispatch, newExpanded: string[]) {
   dispatch({
     type: "UPDATE_EVENT_LISTENER_EXPANDED",
     expanded: newExpanded,
   });
 
@@ -41,30 +41,30 @@ async function updateExpanded(dispatch, 
 }
 
 export function addEventListenerBreakpoints(eventsToAdd: string[]) {
   return async ({ dispatch, client, getState }: ThunkArgs) => {
     const activeListenerBreakpoints = await getActiveEventListeners(getState());
 
     const newEvents = uniq([...eventsToAdd, ...activeListenerBreakpoints]);
 
-    updateBreakpoints(dispatch, client, newEvents);
+    await updateBreakpoints(dispatch, client, newEvents);
   };
 }
 
 export function removeEventListenerBreakpoints(eventsToRemove: string[]) {
   return async ({ dispatch, client, getState }: ThunkArgs) => {
     const activeListenerBreakpoints = await getActiveEventListeners(getState());
 
     const newEvents = remove(
       activeListenerBreakpoints,
       event => !eventsToRemove.includes(event)
     );
 
-    updateBreakpoints(dispatch, client, newEvents);
+    await updateBreakpoints(dispatch, client, newEvents);
   };
 }
 
 export function addEventListenerExpanded(category: string) {
   return async ({ dispatch, getState }: ThunkArgs) => {
     const expanded = await getEventListenerExpanded(getState());
 
     const newExpanded = uniq([...expanded, category]);
--- a/devtools/client/debugger/src/client/firefox.js
+++ b/devtools/client/debugger/src/client/firefox.js
@@ -43,17 +43,21 @@ export async function onConnect(connecti
   await threadClient.reconfigure({
     observeAsmJS: true,
     pauseWorkersUntilAttach: true,
     wasmBinarySource: supportsWasm,
     skipBreakpoints: prefs.skipPausing,
   });
 
   // Retrieve possible event listener breakpoints
-  actions.getEventListenerBreakpointTypes();
+  actions.getEventListenerBreakpointTypes().catch(e => console.error(e));
+
+  // Initialize the event breakpoints on the thread up front so that
+  // they are active once attached.
+  actions.addEventListenerBreakpoints([]).catch(e => console.error(e));
 
   // In Firefox, we need to initially request all of the sources. This
   // usually fires off individual `newSource` notifications as the
   // debugger finds them, but there may be existing sources already in
   // the debugger (if it's paused already, or if loading the page from
   // bfcache) so explicity fire `newSource` events for all returned
   // sources.
   const traits = tabTarget.traits;
--- a/devtools/client/debugger/src/client/firefox/commands.js
+++ b/devtools/client/debugger/src/client/firefox/commands.js
@@ -30,24 +30,28 @@ import type {
   TabTarget,
   DebuggerClient,
   Grip,
   ThreadClient,
   ObjectClient,
   SourcesPacket,
 } from "./types";
 
-import type { EventListenerCategoryList } from "../../actions/types";
+import type {
+  EventListenerCategoryList,
+  EventListenerActiveList,
+} from "../../actions/types";
 
 let workerClients: Object;
 let threadClient: ThreadClient;
 let tabTarget: TabTarget;
 let debuggerClient: DebuggerClient;
 let sourceActors: { [ActorId]: SourceId };
 let breakpoints: { [string]: Object };
+let eventBreakpoints: ?EventListenerActiveList;
 let supportsWasm: boolean;
 
 let shouldWaitForWorkers = false;
 
 type Dependencies = {
   threadClient: ThreadClient,
   tabTarget: TabTarget,
   debuggerClient: DebuggerClient,
@@ -358,26 +362,27 @@ async function setSkipPausing(shouldSkip
   await forEachWorkerThread(thread => thread.skipBreakpoints(shouldSkip));
 }
 
 function interrupt(thread: string): Promise<*> {
   return lookupThreadClient(thread).interrupt();
 }
 
 async function setEventListenerBreakpoints(ids: string[]) {
+  eventBreakpoints = ids;
+
   await threadClient.setActiveEventBreakpoints(ids);
   await forEachWorkerThread(thread => thread.setActiveEventBreakpoints(ids));
 }
 
 // eslint-disable-next-line
 async function getEventListenerBreakpointTypes(): Promise<
   EventListenerCategoryList
 > {
-  const { value } = await threadClient.getAvailableEventBreakpoints();
-  return value;
+  return threadClient.getAvailableEventBreakpoints();
 }
 
 function pauseGrip(thread: string, func: Function): ObjectClient {
   return lookupThreadClient(thread).pauseGrip(func);
 }
 
 function registerSourceActor(sourceActorId: string, sourceId: SourceId) {
   sourceActors[sourceActorId] = sourceId;
@@ -401,16 +406,17 @@ function getSourceForActor(actor: ActorI
   }
   return sourceActors[actor];
 }
 
 async function fetchWorkers(): Promise<Worker[]> {
   if (features.windowlessWorkers) {
     const options = {
       breakpoints,
+      eventBreakpoints,
       observeAsmJS: true,
     };
 
     const newWorkerClients = await updateWorkerClients({
       tabTarget,
       debuggerClient,
       threadClient,
       workerClients,
--- a/devtools/client/debugger/src/client/firefox/types.js
+++ b/devtools/client/debugger/src/client/firefox/types.js
@@ -363,19 +363,17 @@ export type ThreadClient = {
   reconfigure: ({ observeAsmJS: boolean }) => Promise<*>,
   getLastPausePacket: () => ?PausedPacket,
   _parent: TabClient,
   actor: ActorId,
   actorID: ActorId,
   request: (payload: Object) => Promise<*>,
   url: string,
   setActiveEventBreakpoints: (string[]) => void,
-  getAvailableEventBreakpoints: () => Promise<{|
-    value: EventListenerCategoryList,
-  |}>,
+  getAvailableEventBreakpoints: () => Promise<EventListenerCategoryList>,
   skipBreakpoints: boolean => Promise<{| skip: boolean |}>,
 };
 
 export type Panel = {|
   emit: (eventName: string) => void,
   openLink: (url: string) => void,
   openWorkerToolbox: (worker: Worker) => void,
   openElementInInspector: (grip: Object) => void,
--- a/devtools/client/debugger/src/components/SecondaryPanes/EventListeners.js
+++ b/devtools/client/debugger/src/components/SecondaryPanes/EventListeners.js
@@ -86,17 +86,24 @@ class EventListeners extends Component<P
           onClick={() => this.onCategoryToggle(category.name)}
         >
           <AccessibleImage className={classnames("arrow", { expanded })} />
         </button>
         <label className="event-listener-label">
           <input
             type="checkbox"
             value={category.name}
-            onChange={e => this.onCategoryClick(category, e.target.checked)}
+            onChange={e => {
+              this.onCategoryClick(
+                category,
+                // Clicking an indeterminate checkbox should always have the
+                // effect of disabling any selected items.
+                indeterminate ? false : e.target.checked
+              );
+            }}
             checked={checked}
             ref={el => el && (el.indeterminate = indeterminate)}
           />
           <span className="event-listener-category">{category.name}</span>
         </label>
       </div>
     );
   }
--- a/devtools/client/debugger/src/reducers/event-listeners.js
+++ b/devtools/client/debugger/src/reducers/event-listeners.js
@@ -41,24 +41,24 @@ function update(
       return { ...state, expanded: action.expanded };
 
     default:
       return state;
   }
 }
 
 export function getActiveEventListeners(state: State): EventListenerActiveList {
-  return state.eventListenerBreakpoints.active;
+  return state.eventListenerBreakpoints.active || [];
 }
 
 export function getEventListenerBreakpointTypes(
   state: State
 ): EventListenerCategoryList {
-  return state.eventListenerBreakpoints.categories;
+  return state.eventListenerBreakpoints.categories || [];
 }
 
 export function getEventListenerExpanded(
   state: State
 ): EventListenerExpandedList {
-  return state.eventListenerBreakpoints.expanded;
+  return state.eventListenerBreakpoints.expanded || [];
 }
 
 export default update;
--- a/devtools/client/debugger/src/utils/pause/why.js
+++ b/devtools/client/debugger/src/utils/pause/why.js
@@ -11,16 +11,17 @@ import type { Why } from "../../types";
 // "breakpointConditionThrown", "clientEvaluated"
 // "interrupted", "attached"
 const reasons = {
   debuggerStatement: "whyPaused.debuggerStatement",
   breakpoint: "whyPaused.breakpoint",
   exception: "whyPaused.exception",
   resumeLimit: "whyPaused.resumeLimit",
   breakpointConditionThrown: "whyPaused.breakpointConditionThrown",
+  eventBreakpoint: "whyPaused.eventBreakpoint",
 
   // V8
   DOM: "whyPaused.breakpoint",
   EventListener: "whyPaused.pauseOnDOMEvents",
   XHR: "whyPaused.xhr",
   promiseRejection: "whyPaused.promiseRejection",
   assert: "whyPaused.assert",
   debugCommand: "whyPaused.debugCommand",
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -588,16 +588,18 @@ support-files =
   examples/wasm-sourcemaps/fib.c
   examples/wasm-sourcemaps/fib.wasm
   examples/wasm-sourcemaps/fib.wasm.map
   examples/wasm-sourcemaps/utils.js
   examples/doc-html-breakpoints.html
   examples/html-breakpoints-slow.js
   examples/sjs_slow-load.sjs
   examples/fetch.js
+  examples/doc-event-breakpoints.html
+  examples/event-breakpoints.js
   examples/doc-xhr.html
   examples/doc-xhr-run-to-completion.html
   examples/doc-scroll-run-to-completion.html
   examples/pretty.js
   examples/sum/sum.js
   examples/sum/sum.min.js
   examples/sum/sum.min.js.map
   examples/big-sourcemap_files/bundle.js
@@ -801,14 +803,15 @@ skip-if = os == "win"
 skip-if = os == "win"
 [browser_dbg-wasm-sourcemaps.js]
 skip-if = true
 [browser_dbg-windowless-workers.js]
 [browser_dbg-windowless-workers-early-breakpoint.js]
 [browser_dbg-worker-scopes.js]
 skip-if = (os == 'linux' && debug) || ccov #Bug 1456013
 [browser_dbg-event-handler.js]
+[browser_dbg-event-breakpoints.js]
 [browser_dbg-eval-throw.js]
 [browser_dbg-sourceURL-breakpoint.js]
 [browser_dbg-old-breakpoint.js]
 [browser_dbg-idb-run-to-completion.js]
 [browser_dbg-scopes-xrays.js]
 [browser_dbg-message-run-to-completion.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-event-breakpoints.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
+
+function assertPauseLocation(dbg, line) {
+  const { location } = dbg.selectors.getVisibleSelectedFrame();
+
+  const source = findSource(dbg, "event-breakpoints.js");
+
+  is(location.sourceId, source.id, `correct sourceId`);
+  is(location.line, line, `correct line`);
+
+  assertPausedLocation(dbg);
+}
+
+add_task(async function() {
+  await pushPref("devtools.debugger.features.event-listeners-breakpoints", true);
+
+  const dbg = await initDebugger("doc-event-breakpoints.html", "event-breakpoints");
+  await selectSource(dbg, "event-breakpoints");
+  await waitForSelectedSource(dbg, "event-breakpoints");
+
+  await dbg.actions.addEventListenerBreakpoints([
+    "event.mouse.click",
+    "event.xhr.load",
+    "timer.timeout.set",
+    "timer.timeout.fire",
+  ]);
+
+  invokeInTab("clickHandler");
+  await waitForPaused(dbg);
+  assertPauseLocation(dbg, 12);
+  await resume(dbg);
+
+  invokeInTab("xhrHandler");
+  await waitForPaused(dbg);
+  assertPauseLocation(dbg, 20);
+  await resume(dbg);
+
+  invokeInTab("timerHandler");
+  await waitForPaused(dbg);
+  assertPauseLocation(dbg, 27);
+  await resume(dbg);
+
+  await waitForPaused(dbg);
+  assertPauseLocation(dbg, 29);
+  await resume(dbg);
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/examples/doc-event-breakpoints.html
@@ -0,0 +1,20 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Debugger event bp test page</title>
+  </head>
+
+  <body>
+    <button id="click-button">Run Click Handler</button>
+    <button id="xhr-button">Run XHR Handler</button>
+    <button id="timer-button">Run Timer Handler</button>
+    <div id="click-target" style="margin: 50px; background-color: green;">
+      Click Target
+    </div>
+    <script src="event-breakpoints.js"></script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/examples/event-breakpoints.js
@@ -0,0 +1,34 @@
+/* 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/>. */
+
+document.getElementById("click-button").onmousedown = clickHandler;
+function clickHandler() {
+  document.getElementById("click-target").click();
+}
+
+document.getElementById("click-target").onclick = clickTargetClicked;
+function clickTargetClicked() {
+  console.log("clicked");
+}
+
+document.getElementById("xhr-button").onmousedown = xhrHandler;
+function xhrHandler() {
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", "doc-event-breakpoints.html", true);
+  xhr.onload = function(){
+    console.log("xhr load");
+  };
+  xhr.send();
+}
+
+document.getElementById("timer-button").onmousedown = timerHandler;
+function timerHandler() {
+  setTimeout(
+    () => {
+      console.log("timer callback");
+    },
+    50
+  );
+  console.log("timer set");
+}
\ No newline at end of file
--- a/devtools/client/locales/en-US/debugger.properties
+++ b/devtools/client/locales/en-US/debugger.properties
@@ -978,16 +978,21 @@ experimental=This is an experimental fea
 # in a info block explaining how the debugger is currently paused due to a `debugger`
 # statement in the code
 whyPaused.debuggerStatement=Paused on debugger statement
 
 # LOCALIZATION NOTE (whyPaused.breakpoint): The text that is displayed
 # in a info block explaining how the debugger is currently paused on a breakpoint
 whyPaused.breakpoint=Paused on breakpoint
 
+# LOCALIZATION NOTE (whyPaused.eventBreakpoint): The text that is displayed
+# in a info block explaining how the debugger is currently paused on an event
+# breakpoint.
+whyPaused.eventBreakpoint=Paused on event breakpoint
+
 # LOCALIZATION NOTE (whyPaused.exception): The text that is displayed
 # in a info block explaining how the debugger is currently paused on an exception
 whyPaused.exception=Paused on exception
 
 # LOCALIZATION NOTE (whyPaused.resumeLimit): The text that is displayed
 # in a info block explaining how the debugger is currently paused while stepping
 # in or out of the stack
 whyPaused.resumeLimit=Paused while stepping
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -61,16 +61,20 @@ collapseDetailsPane=Hide request details
 # LOCALIZATION NOTE (headersEmptyText): This is the text displayed in the
 # headers tab of the network details pane when there are no headers available.
 headersEmptyText=No headers for this request
 
 # LOCALIZATION NOTE (headersFilterText): This is the text displayed in the
 # headers tab of the network details pane for the filtering input.
 headersFilterText=Filter headers
 
+# LOCALIZATION NOTE (webSocketsEmptyText): This is the text displayed in the
+# WebSockets tab of the network details pane when there are no frames available.
+webSocketsEmptyText=No WebSocket frames for this request
+
 # LOCALIZATION NOTE (cookiesEmptyText): This is the text displayed in the
 # cookies tab of the network details pane when there are no cookies available.
 cookiesEmptyText=No cookies for this request
 
 # LOCALIZATION NOTE (cookiesFilterText): This is the text displayed in the
 # cookies tab of the network details pane for the filtering input.
 cookiesFilterText=Filter cookies
 
@@ -632,16 +636,20 @@ netmonitor.toolbar.contentSize=Size
 # LOCALIZATION NOTE (netmonitor.toolbar.waterfall): This is the label displayed
 # in the network table toolbar, above the "waterfall" column.
 netmonitor.toolbar.waterfall=Timeline
 
 # LOCALIZATION NOTE (netmonitor.tab.headers): This is the label displayed
 # in the network details pane identifying the headers tab.
 netmonitor.tab.headers=Headers
 
+# LOCALIZATION NOTE (netmonitor.tab.webSockets): This is the label displayed
+# in the network details pane identifying the webSockets tab.
+netmonitor.tab.webSockets=WebSockets
+
 # LOCALIZATION NOTE (netmonitor.tab.cookies): This is the label displayed
 # in the network details pane identifying the cookies tab.
 netmonitor.tab.cookies=Cookies
 
 # LOCALIZATION NOTE (netmonitor.tab.cache): This is the label displayed
 # in the network details pane identifying the cache tab.
 netmonitor.tab.cache=Cache
 
--- a/devtools/client/netmonitor/src/actions/index.js
+++ b/devtools/client/netmonitor/src/actions/index.js
@@ -6,18 +6,20 @@
 
 const batching = require("./batching");
 const filters = require("./filters");
 const requests = require("./requests");
 const selection = require("./selection");
 const sort = require("./sort");
 const timingMarkers = require("./timing-markers");
 const ui = require("./ui");
+const webSockets = require("./web-sockets");
 
 Object.assign(exports,
   batching,
   filters,
   requests,
   selection,
   sort,
   timingMarkers,
-  ui
+  ui,
+  webSockets,
 );
--- a/devtools/client/netmonitor/src/actions/moz.build
+++ b/devtools/client/netmonitor/src/actions/moz.build
@@ -6,9 +6,10 @@ DevToolsModules(
     'batching.js',
     'filters.js',
     'index.js',
     'requests.js',
     'selection.js',
     'sort.js',
     'timing-markers.js',
     'ui.js',
+    'web-sockets.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/actions/web-sockets.js
@@ -0,0 +1,19 @@
+/* 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 { WS_ADD_FRAME } = require("../constants");
+
+function addFrame(httpChannelId, data) {
+  return {
+    type: WS_ADD_FRAME,
+    httpChannelId,
+    data,
+  };
+}
+
+module.exports = {
+  addFrame,
+};
--- a/devtools/client/netmonitor/src/components/TabboxPanel.js
+++ b/devtools/client/netmonitor/src/components/TabboxPanel.js
@@ -1,38 +1,41 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const Services = require("Services");
 const {
   Component,
   createFactory,
 } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { L10N } = require("../utils/l10n");
 const { PANELS } = require("../constants");
 
 // Components
 const Tabbar = createFactory(require("devtools/client/shared/components/tabs/TabBar"));
 const TabPanel = createFactory(require("devtools/client/shared/components/tabs/Tabs").TabPanel);
 const CookiesPanel = createFactory(require("./CookiesPanel"));
 const HeadersPanel = createFactory(require("./HeadersPanel"));
+const WebSocketsPanel = createFactory(require("./WebSocketsPanel"));
 const ParamsPanel = createFactory(require("./ParamsPanel"));
 const CachePanel = createFactory(require("./CachePanel"));
 const ResponsePanel = createFactory(require("./ResponsePanel"));
 const SecurityPanel = createFactory(require("./SecurityPanel"));
 const StackTracePanel = createFactory(require("./StackTracePanel"));
 const TimingsPanel = createFactory(require("./TimingsPanel"));
 
 const COLLAPSE_DETAILS_PANE = L10N.getStr("collapseDetailsPane");
 const CACHE_TITLE = L10N.getStr("netmonitor.tab.cache");
 const COOKIES_TITLE = L10N.getStr("netmonitor.tab.cookies");
 const HEADERS_TITLE = L10N.getStr("netmonitor.tab.headers");
+const WEBSOCKETS_TITLE = L10N.getStr("netmonitor.tab.webSockets");
 const PARAMS_TITLE = L10N.getStr("netmonitor.tab.params");
 const RESPONSE_TITLE = L10N.getStr("netmonitor.tab.response");
 const SECURITY_TITLE = L10N.getStr("netmonitor.tab.security");
 const STACK_TRACE_TITLE = L10N.getStr("netmonitor.tab.stackTrace");
 const TIMINGS_TITLE = L10N.getStr("netmonitor.tab.timings");
 
 /**
 * Tabbox panel component
@@ -82,16 +85,21 @@ class TabboxPanel extends Component {
       sourceMapService,
       toggleNetworkDetails,
     } = this.props;
 
     if (!request) {
       return null;
     }
 
+    const channelId = request.channelId;
+    const showWebSocketsPanel =
+      request.cause.type === "websocket" &&
+      Services.prefs.getBoolPref(
+        "devtools.netmonitor.features.webSockets");
     return (
       Tabbar({
         activeTabId,
         menuDocument: window.parent.document,
         onSelect: selectTab,
         renderOnlySelected: true,
         showAllTabsMenu: true,
         sidebarToggleButton: hideToggleButton ? null :
@@ -108,16 +116,24 @@ class TabboxPanel extends Component {
         },
           HeadersPanel({
             cloneSelectedRequest,
             connector,
             openLink,
             request,
           }),
         ),
+        showWebSocketsPanel && TabPanel({
+          id: PANELS.WEBSOCKETS,
+          title: WEBSOCKETS_TITLE,
+        },
+          WebSocketsPanel({
+            channelId,
+          }),
+        ),
         TabPanel({
           id: PANELS.COOKIES,
           title: COOKIES_TITLE,
         },
           CookiesPanel({
             connector,
             openLink,
             request,
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/WebSocketsPanel.js
@@ -0,0 +1,85 @@
+/* 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 { Component } = require("devtools/client/shared/vendor/react");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const {
+  connect,
+} = require("devtools/client/shared/redux/visibility-handler-connect");
+const { getFramesByChannelId } = require("../selectors/index");
+
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const { table, tbody, thead, tr, td, th, div } = dom;
+
+const { L10N } = require("../utils/l10n");
+const FRAMES_EMPTY_TEXT = L10N.getStr("webSocketsEmptyText");
+
+class WebSocketsPanel extends Component {
+  static get propTypes() {
+    return {
+      channelId: PropTypes.number,
+      frames: PropTypes.array,
+    };
+  }
+
+  constructor(props) {
+    super(props);
+  }
+
+  render() {
+    const { frames } = this.props;
+
+    if (!frames) {
+      return div({ className: "empty-notice" },
+        FRAMES_EMPTY_TEXT
+      );
+    }
+
+    const rows = [];
+    frames.forEach((frame, index) => {
+      rows.push(
+        tr(
+          { key: index,
+            className: "frames-row" },
+          td({ className: "frames-cell" }, frame.type),
+          td({ className: "frames-cell" }, frame.httpChannelId),
+          td({ className: "frames-cell" }, frame.payload),
+          td({ className: "frames-cell" }, frame.opCode),
+          td({ className: "frames-cell" }, frame.maskBit.toString()),
+          td({ className: "frames-cell" }, frame.finBit.toString()),
+          td({ className: "frames-cell" }, frame.timeStamp)
+        )
+      );
+    });
+
+    return table(
+      { className: "frames-list-table" },
+      thead(
+        { className: "frames-head" },
+        tr(
+          { className: "frames-row" },
+          th({ className: "frames-headerCell" }, "Type"),
+          th({ className: "frames-headerCell" }, "Channel ID"),
+          th({ className: "frames-headerCell" }, "Payload"),
+          th({ className: "frames-headerCell" }, "OpCode"),
+          th({ className: "frames-headerCell" }, "MaskBit"),
+          th({ className: "frames-headerCell" }, "FinBit"),
+          th({ className: "frames-headerCell" }, "Time")
+        )
+      ),
+      tbody(
+        {
+          className: "frames-list-tableBody",
+        },
+        rows
+      )
+    );
+  }
+}
+
+module.exports = connect((state, props) => ({
+  frames: getFramesByChannelId(state, props.channelId),
+}))(WebSocketsPanel);
--- a/devtools/client/netmonitor/src/components/moz.build
+++ b/devtools/client/netmonitor/src/components/moz.build
@@ -42,9 +42,10 @@ DevToolsModules(
     'SourceEditor.js',
     'StackTracePanel.js',
     'StatisticsPanel.js',
     'StatusBar.js',
     'StatusCode.js',
     'TabboxPanel.js',
     'TimingsPanel.js',
     'Toolbar.js',
+    'WebSocketsPanel.js',
 )
--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js
+++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js
@@ -74,16 +74,17 @@ class FirefoxDataProvider {
       isXHR,
       cause,
       startedDateTime,
       fromCache,
       fromServiceWorker,
       isThirdPartyTrackingResource,
       referrerPolicy,
       blockedReason,
+      channelId,
     } = data;
 
     // Insert blocked reason in the payload queue as well, as we'll need it later
     // when deciding if the request is complete.
     this.pushRequestToQueue(id, {
       blockedReason,
     });
 
@@ -101,16 +102,17 @@ class FirefoxDataProvider {
         // FF59+ supports fetching the traces lazily via requestData.
         stacktrace: cause.stacktrace,
 
         fromCache,
         fromServiceWorker,
         isThirdPartyTrackingResource,
         referrerPolicy,
         blockedReason,
+        channelId,
       }, true);
     }
 
     this.emit(EVENTS.REQUEST_ADDED, id);
   }
 
   /**
    * Update a network request if it already exists in application state.
@@ -338,29 +340,31 @@ class FirefoxDataProvider {
       request: {
         method,
         url,
       },
       startedDateTime,
       isThirdPartyTrackingResource,
       referrerPolicy,
       blockedReason,
+      channelId,
     } = networkInfo;
 
     await this.addRequest(actor, {
       cause,
       fromCache,
       fromServiceWorker,
       isXHR,
       method,
       startedDateTime,
       url,
       isThirdPartyTrackingResource,
       referrerPolicy,
       blockedReason,
+      channelId,
     });
 
     this.emit(EVENTS.NETWORK_EVENT, actor);
   }
 
   /**
    * The "networkEventUpdate" message type handler.
    *
@@ -421,63 +425,59 @@ class FirefoxDataProvider {
    * The "webSocketOpened" message type handler.
    *
    * @param {number} httpChannelId the channel ID
    * @param {string} effectiveURI the effective URI of the page
    * @param {string} protocols webSocket protocols
    * @param {string} extensions
    */
   async onWebSocketOpened(httpChannelId, effectiveURI, protocols, extensions) {
-    console.log("FirefoxDataProvider onWebSocketOpened: " +
-      " httpChannelId: " + httpChannelId +
-      " effectiveURI: " + effectiveURI +
-      " protocols: " + protocols +
-      " extensions: " + extensions);
   }
 
   /**
    * The "webSocketClosed" message type handler.
    *
    * @param {boolean} wasClean
    * @param {number} code
    * @param {string} reason
    */
   async onWebSocketClosed(wasClean, code, reason) {
-    console.log("FirefoxDataProvider onWebSocketClosed: " +
-      " wasClean: " + wasClean +
-      " code: " + code +
-      " reason: " + reason);
   }
 
   /**
    * The "frameSent" message type handler.
    *
    * @param {number} httpChannelId the channel ID
    * @param {object} data websocket frame information
    */
   async onFrameSent(httpChannelId, data) {
-    await this.getLongString(data.payload).then(payload => {
-      console.log("FirefoxDataProvider onFrameSent: " +
-      " httpChannelId: " + httpChannelId +
-      " onFrameSent: " + payload);
-    });
+    this.addFrame(httpChannelId, data);
   }
 
   /**
    * The "frameReceived" message type handler.
    *
    * @param {number} httpChannelId the channel ID
    * @param {object} data websocket frame information
    */
   async onFrameReceived(httpChannelId, data) {
-    await this.getLongString(data.payload).then(payload => {
-      console.log("FirefoxDataProvider onFrameReceived: " +
-      " httpChannelId: " + httpChannelId +
-      " onFrameSent: " + payload);
-    });
+    this.addFrame(httpChannelId, data);
+  }
+
+  /**
+   * Add a new WebSocket frame to application state.
+   *
+   * @param {number} httpChannelId the channel ID
+   * @param {object} data websocket frame information
+   */
+  async addFrame(httpChannelId, data) {
+    if (this.actionsEnabled && this.actions.addFrame) {
+      await this.actions.addFrame(httpChannelId, data);
+    }
+    // TODO: Emit an event for test here
   }
 
   /**
    * Notify actions when messages from onNetworkEventUpdate are done, networkEventUpdate
    * messages contain initial network info for each updateType and then we can invoke
    * requestData to fetch its corresponded data lazily.
    * Once all updateTypes of networkEventUpdate message are arrived, we flush merged
    * request payload from pending queue and then update component.
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -29,16 +29,17 @@ const actionTypes = {
   SET_REQUEST_FILTER_TEXT: "SET_REQUEST_FILTER_TEXT",
   SORT_BY: "SORT_BY",
   TOGGLE_COLUMN: "TOGGLE_COLUMN",
   TOGGLE_RECORDING: "TOGGLE_RECORDING",
   TOGGLE_REQUEST_FILTER_TYPE: "TOGGLE_REQUEST_FILTER_TYPE",
   UPDATE_REQUEST: "UPDATE_REQUEST",
   WATERFALL_RESIZE: "WATERFALL_RESIZE",
   SET_COLUMNS_WIDTH: "SET_COLUMNS_WIDTH",
+  WS_ADD_FRAME: "WS_ADD_FRAME",
 };
 
 // Descriptions for what this frontend is currently doing.
 const ACTIVITY_TYPE = {
   // Standing by and handling requests normally.
   NONE: 0,
 
   // Forcing the target to reload with cache enabled or disabled.
@@ -149,21 +150,23 @@ const UPDATE_PROPS = [
   "responseContentAvailable",
   "responseCache",
   "responseCacheAvailable",
   "formDataSections",
   "stacktrace",
   "isThirdPartyTrackingResource",
   "referrerPolicy",
   "blockedReason",
+  "channelId",
 ];
 
 const PANELS = {
   COOKIES: "cookies",
   HEADERS: "headers",
+  WEBSOCKETS: "webSockets",
   PARAMS: "params",
   RESPONSE: "response",
   CACHE: "cache",
   SECURITY: "security",
   STACK_TRACE: "stack-trace",
   TIMINGS: "timings",
 };
 
--- a/devtools/client/netmonitor/src/create-store.js
+++ b/devtools/client/netmonitor/src/create-store.js
@@ -22,16 +22,17 @@ const eventTelemetry = require("./middle
 
 // Reducers
 const rootReducer = require("./reducers/index");
 const { FilterTypes, Filters } = require("./reducers/filters");
 const { Requests } = require("./reducers/requests");
 const { Sort } = require("./reducers/sort");
 const { TimingMarkers } = require("./reducers/timing-markers");
 const { UI, Columns, ColumnsData } = require("./reducers/ui");
+const { WebSockets } = require("./reducers/web-sockets");
 
 /**
  * Configure state and middleware for the Network monitor tool.
  */
 function configureStore(connector, telemetry) {
   // Prepare initial state.
   const initialState = {
     filters: new Filters({
@@ -39,16 +40,17 @@ function configureStore(connector, telem
     }),
     requests: new Requests(),
     sort: new Sort(),
     timingMarkers: new TimingMarkers(),
     ui: UI({
       columns: getColumnState(),
       columnsData: getColumnsData(),
     }),
+    webSockets: new WebSockets(),
   };
 
   // Prepare middleware.
   const middleware = applyMiddleware(
     thunk,
     prefs,
     batching,
     recording(connector),
--- a/devtools/client/netmonitor/src/reducers/index.js
+++ b/devtools/client/netmonitor/src/reducers/index.js
@@ -6,20 +6,22 @@
 
 const { combineReducers } = require("devtools/client/shared/vendor/redux");
 const batchingReducer = require("./batching");
 const { requestsReducer } = require("./requests");
 const { sortReducer } = require("./sort");
 const { filters } = require("./filters");
 const { timingMarkers } = require("./timing-markers");
 const { ui } = require("./ui");
+const { webSocketsReducer } = require("./web-sockets");
 const networkThrottling = require("devtools/client/shared/components/throttling/reducer");
 
 module.exports = batchingReducer(
   combineReducers({
     requests: requestsReducer,
     sort: sortReducer,
+    webSockets: webSocketsReducer,
     filters,
     timingMarkers,
     ui,
     networkThrottling,
   })
 );
--- a/devtools/client/netmonitor/src/reducers/moz.build
+++ b/devtools/client/netmonitor/src/reducers/moz.build
@@ -5,9 +5,10 @@
 DevToolsModules(
     'batching.js',
     'filters.js',
     'index.js',
     'requests.js',
     'sort.js',
     'timing-markers.js',
     'ui.js',
+    'web-sockets.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/reducers/web-sockets.js
@@ -0,0 +1,64 @@
+/* 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 {
+  WS_ADD_FRAME,
+} = require("../constants");
+
+/**
+ * This structure stores list of all WebSocket frames received
+ * from the backend.
+ */
+function WebSockets() {
+  return {
+    // Map with all requests (key = channelId, value = array of frame objects)
+    frames: new Map(),
+  };
+}
+
+/**
+ * This reducer is responsible for maintaining list of
+ * WebSocket frames within the Network panel.
+ */
+function webSocketsReducer(state = WebSockets(), action) {
+  switch (action.type) {
+    // Appending new frame into the map.
+    case WS_ADD_FRAME: {
+      const nextState = { ...state };
+
+      const newFrame = {
+        httpChannelId: action.httpChannelId,
+        ...action.data,
+      };
+
+      nextState.frames = mapSet(state.frames, newFrame.httpChannelId, newFrame);
+
+      return nextState;
+    }
+
+    default:
+      return state;
+  }
+}
+
+/**
+ * Append new item into existing map and return new map.
+ */
+function mapSet(map, key, value) {
+  const newMap = new Map(map);
+  if (newMap.has(key)) {
+    const framesArray = [...newMap.get(key)];
+    framesArray.push(value);
+    newMap.set(key, framesArray);
+    return newMap;
+  }
+  return newMap.set(key, [value]);
+}
+
+module.exports = {
+  WebSockets,
+  webSocketsReducer,
+};
--- a/devtools/client/netmonitor/src/selectors/index.js
+++ b/devtools/client/netmonitor/src/selectors/index.js
@@ -2,14 +2,16 @@
  * 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 requests = require("./requests");
 const timingMarkers = require("./timing-markers");
 const ui = require("./ui");
+const webSockets = require("./web-sockets");
 
 Object.assign(exports,
   requests,
   timingMarkers,
-  ui
+  ui,
+  webSockets,
 );
--- a/devtools/client/netmonitor/src/selectors/moz.build
+++ b/devtools/client/netmonitor/src/selectors/moz.build
@@ -2,9 +2,10 @@
 # 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(
     'index.js',
     'requests.js',
     'timing-markers.js',
     'ui.js',
+    'web-sockets.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/selectors/web-sockets.js
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function getFramesByChannelId(state, channelId) {
+  return state.webSockets.frames.get(channelId);
+}
+
+module.exports = {
+  getFramesByChannelId,
+};
--- a/devtools/client/themes/dark-theme.css
+++ b/devtools/client/themes/dark-theme.css
@@ -66,17 +66,17 @@ body {
 .cm-s-mozilla .cm-executed-line {
   background-color: #133c26;
 }
 
 .cm-s-mozilla .cm-number,
 .variable-or-property .token-number,
 .variable-or-property[return] > .title > .name,
 .variable-or-property[scope] > .title > .name {
-  color: #6B89FF;
+  color: #709AFF;
 }
 
 .CodeMirror-Tern-completion-number:before {
   background-color: #5c9966;
 }
 
 .theme-fg-color1,
 .cm-s-mozilla .cm-attribute,
@@ -119,17 +119,17 @@ body {
 .theme-fg-color2 {
   color: var(--theme-highlight-purple);
 }
 
 .cm-s-mozilla .cm-string,
 .cm-s-mozilla .cm-string-2,
 .variable-or-property .token-string,
 .CodeMirror-Tern-farg {
-  color: #6B89FF;
+  color: #709AFF;
 }
 
 .CodeMirror-Tern-completion-string:before,
 .CodeMirror-Tern-completion-fn:before {
   background-color: #b26b47;
 }
 
 .cm-s-mozilla .cm-atom,
--- a/devtools/client/webconsole/test/fixtures/stub-generators/head.js
+++ b/devtools/client/webconsole/test/fixtures/stub-generators/head.js
@@ -70,16 +70,20 @@ function getCleanedPacket(key, packet) {
     if (res.startedDateTime) {
       res.startedDateTime = existingPacket.startedDateTime;
     }
 
     if (res.actor) {
       res.actor = existingPacket.actor;
     }
 
+    if (res.channelId) {
+      res.channelId = existingPacket.channelId;
+    }
+
     if (res.message) {
       // Clean timeStamp on the message prop.
       res.message.timeStamp = existingPacket.message.timeStamp;
       if (res.message.timer) {
         // Clean timer properties on the message.
         // Those properties are found on console.time, timeLog and timeEnd calls,
         // and those time can vary, which is why we need to clean them.
         if ("duration" in res.message.timer) {
--- a/devtools/client/webconsole/test/fixtures/stubs/networkEvent.js
+++ b/devtools/client/webconsole/test/fixtures/stubs/networkEvent.js
@@ -260,16 +260,17 @@ stubPackets.set(`GET request`, {
     "stacktraceAvailable": true
   },
   "response": {},
   "timings": {},
   "updates": [],
   "private": false,
   "isThirdPartyTrackingResource": false,
   "referrerPolicy": "no-referrer-when-downgrade",
+  "channelId": 22673132355586,
   "from": "server1.conn0.child1/consoleActor2"
 });
 
 stubPackets.set(`GET request update`, {
   "networkInfo": {
     "_type": "NetworkEvent",
     "actor": "server1.conn0.child1/netEvent30",
     "request": {
@@ -310,16 +311,17 @@ stubPackets.set(`XHR GET request`, {
     "stacktraceAvailable": true
   },
   "response": {},
   "timings": {},
   "updates": [],
   "private": false,
   "isThirdPartyTrackingResource": false,
   "referrerPolicy": "no-referrer-when-downgrade",
+  "channelId": 22673132355587,
   "from": "server1.conn1.child1/consoleActor2"
 });
 
 stubPackets.set(`XHR GET request update`, {
   "networkInfo": {
     "_type": "NetworkEvent",
     "actor": "server1.conn0.child1/netEvent31",
     "request": {
@@ -360,16 +362,17 @@ stubPackets.set(`XHR POST request`, {
     "stacktraceAvailable": true
   },
   "response": {},
   "timings": {},
   "updates": [],
   "private": false,
   "isThirdPartyTrackingResource": false,
   "referrerPolicy": "no-referrer-when-downgrade",
+  "channelId": 22673132355588,
   "from": "server1.conn2.child1/consoleActor2"
 });
 
 stubPackets.set(`XHR POST request update`, {
   "networkInfo": {
     "_type": "NetworkEvent",
     "actor": "server1.conn0.child1/netEvent32",
     "request": {
--- a/devtools/server/actors/network-event.js
+++ b/devtools/server/actors/network-event.js
@@ -63,16 +63,17 @@ const NetworkEventActor = protocol.Actor
       isXHR: this._isXHR,
       cause: this._cause,
       fromCache: this._fromCache,
       fromServiceWorker: this._fromServiceWorker,
       private: this._private,
       isThirdPartyTrackingResource: this._isThirdPartyTrackingResource,
       referrerPolicy: this._referrerPolicy,
       blockedReason: this._blockedReason,
+      channelId: this._channelId,
     };
   },
 
   /**
    * Releases this actor from the pool.
    */
   destroy(conn) {
     if (!this.netMonitorActor) {
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -1,26 +1,29 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const DebuggerNotificationObserver = require("DebuggerNotificationObserver");
 const Services = require("Services");
 const { Cr, Ci } = require("chrome");
 const { ActorPool } = require("devtools/server/actors/common");
 const { createValueGrip } = require("devtools/server/actors/object/utils");
 const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, dumpn } = DevToolsUtils;
 const { threadSpec } = require("devtools/shared/specs/thread");
 const {
   getAvailableEventBreakpoints,
+  eventBreakpointForNotification,
+  makeEventBreakpointMessage,
 } = require("devtools/server/actors/utils/event-breakpoints");
 
 loader.lazyRequireGetter(this, "EnvironmentActor", "devtools/server/actors/environment", true);
 loader.lazyRequireGetter(this, "BreakpointActorMap", "devtools/server/actors/utils/breakpoint-actor-map", true);
 loader.lazyRequireGetter(this, "PauseScopedObjectActor", "devtools/server/actors/pause-scoped", true);
 loader.lazyRequireGetter(this, "EventLoopStack", "devtools/server/actors/utils/event-loop", true);
 loader.lazyRequireGetter(this, "FrameActor", "devtools/server/actors/frame", true);
 loader.lazyRequireGetter(this, "throttle", "devtools/shared/throttle", true);
@@ -56,17 +59,18 @@ const ThreadActor = ActorClassWithSpec(t
     this._parent = parent;
     this._dbg = null;
     this._gripDepth = 0;
     this._threadLifetimePool = null;
     this._parentClosed = false;
     this._scripts = null;
     this._xhrBreakpoints = [];
     this._observingNetwork = false;
-    this._eventBreakpoints = [];
+    this._activeEventBreakpoints = new Set();
+    this._activeEventPause = null;
 
     this._priorPause = null;
 
     this._options = {
       autoBlackBox: false,
     };
 
     this.breakpointActorMap = new BreakpointActorMap(this);
@@ -89,16 +93,20 @@ const ThreadActor = ActorClassWithSpec(t
 
     this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
     this.createCompletionGrip = this.createCompletionGrip.bind(this);
     this.onDebuggerStatement = this.onDebuggerStatement.bind(this);
     this.onNewScript = this.onNewScript.bind(this);
     this.objectGrip = this.objectGrip.bind(this);
     this.pauseObjectGrip = this.pauseObjectGrip.bind(this);
     this._onOpeningRequest = this._onOpeningRequest.bind(this);
+    this._onNewDebuggee = this._onNewDebuggee.bind(this);
+    this._eventBreakpointListener = this._eventBreakpointListener.bind(this);
+
+    this._debuggerNotificationObserver = new DebuggerNotificationObserver();
 
     if (Services.obs) {
       // Set a wrappedJSObject property so |this| can be sent via the observer svc
       // for the xpcshell harness.
       this.wrappedJSObject = this;
       Services.obs.notifyObservers(this, "devtools-thread-instantiated");
     }
   },
@@ -107,16 +115,17 @@ const ThreadActor = ActorClassWithSpec(t
   _gripDepth: null,
 
   get dbg() {
     if (!this._dbg) {
       this._dbg = this._parent.makeDebugger();
       this._dbg.uncaughtExceptionHook = this.uncaughtExceptionHook;
       this._dbg.onDebuggerStatement = this.onDebuggerStatement;
       this._dbg.onNewScript = this.onNewScript;
+      this._dbg.onNewDebuggee = this._onNewDebuggee;
       if (this._dbg.replaying) {
         this._dbg.replayingOnForcedPause = this.replayingOnForcedPause.bind(this);
         const sendProgress = throttle((recording, executionPoint) => {
           if (this.attached) {
             this.conn.send({ type: "progress", from: this.actorID,
                              recording, executionPoint });
           }
         }, 100);
@@ -218,16 +227,26 @@ const ThreadActor = ActorClassWithSpec(t
     dumpn("in ThreadActor.prototype.destroy");
     if (this._state == "paused") {
       this.doResume();
     }
 
     this._xhrBreakpoints = [];
     this._updateNetworkObserver();
 
+    this._activeEventBreakpoints = new Set();
+    this._debuggerNotificationObserver.removeListener(
+      this._eventBreakpointListener);
+
+    for (const global of this.dbg.getDebuggees()) {
+      try {
+        this._debuggerNotificationObserver.disconnect(global);
+      } catch (e) { }
+    }
+
     this.sources.off("newSource", this.onNewSourceEvent);
     this.clearDebuggees();
     this.conn.removeActorPool(this._threadLifetimePool);
     this._threadLifetimePool = null;
 
     if (!this._dbg) {
       return;
     }
@@ -280,16 +299,19 @@ const ThreadActor = ActorClassWithSpec(t
       hooks: this._parent,
       connection: this.conn,
       thread: this,
     });
 
     if (options.breakpoints) {
       this._setBreakpointsOnAttach(options.breakpoints);
     }
+    if (options.eventBreakpoints) {
+      this.setActiveEventBreakpoints(options.eventBreakpoints);
+    }
 
     this.dbg.addDebuggees();
     this.dbg.enabled = true;
 
     if ("observeAsmJS" in this._options) {
       this.dbg.allowUnobservedAsmJS = !this._options.observeAsmJS;
     }
 
@@ -395,20 +417,34 @@ const ThreadActor = ActorClassWithSpec(t
     }
     return this._updateNetworkObserver();
   },
 
   getAvailableEventBreakpoints: function() {
     return getAvailableEventBreakpoints();
   },
   getActiveEventBreakpoints: function() {
-    return this._eventBreakpoints;
+    return Array.from(this._activeEventBreakpoints);
   },
   setActiveEventBreakpoints: function(ids) {
-    this._eventBreakpoints = ids;
+    this._activeEventBreakpoints = new Set(ids);
+
+    if (this._activeEventBreakpoints.size === 0) {
+      this._debuggerNotificationObserver.removeListener(
+        this._eventBreakpointListener);
+    } else {
+      this._debuggerNotificationObserver.addListener(
+        this._eventBreakpointListener);
+    }
+  },
+
+  _onNewDebuggee(global) {
+    try {
+      this._debuggerNotificationObserver.connect(global);
+    } catch (e) { }
   },
 
   _updateNetworkObserver() {
     // Workers don't have access to `Services` and even if they did, network
     // requests are all dispatched to the main thread, so there would be
     // nothing here to listen for. We'll need to revisit implementing
     // XHR breakpoints for workers.
     if (isWorker) {
@@ -508,16 +544,91 @@ const ThreadActor = ActorClassWithSpec(t
     Object.assign(this._options, options);
 
     // Update the global source store
     this.sources.setOptions(options);
 
     return {};
   },
 
+  _eventBreakpointListener(notification) {
+    if (this._state === "paused" || this._state === "detached") {
+      return;
+    }
+
+    const eventBreakpoint =
+      eventBreakpointForNotification(this.dbg, notification);
+
+    if (!this._activeEventBreakpoints.has(eventBreakpoint)) {
+      return;
+    }
+
+    if (notification.phase === "pre" && !this._activeEventPause) {
+      this._activeEventPause = this._captureDebuggerHooks();
+
+      this.dbg.onEnterFrame =
+        this._makeEventBreakpointEnterFrame(eventBreakpoint);
+    } else if (notification.phase === "post" && this._activeEventPause) {
+      this._restoreDebuggerHooks(this._activeEventPause);
+      this._activeEventPause = null;
+    } else if (!notification.phase && !this._activeEventPause) {
+      const frame = this.dbg.getNewestFrame();
+      if (frame) {
+        const { sourceActor } = this.sources.getFrameLocation(frame);
+        const url = sourceActor.url;
+        if (this.sources.isBlackBoxed(url)) {
+          return;
+        }
+
+        this._pauseAndRespondEventBreakpoint(frame, eventBreakpoint);
+      }
+    }
+  },
+
+  _makeEventBreakpointEnterFrame(eventBreakpoint) {
+    return frame => {
+      const { sourceActor } = this.sources.getFrameLocation(frame);
+      const url = sourceActor.url;
+      if (this.sources.isBlackBoxed(url)) {
+        return undefined;
+      }
+
+      this._restoreDebuggerHooks(this._activeEventPause);
+      this._activeEventPause = null;
+
+      return this._pauseAndRespondEventBreakpoint(frame, eventBreakpoint);
+    };
+  },
+
+  _pauseAndRespondEventBreakpoint(frame, eventBreakpoint) {
+    if (this.skipBreakpoints) {
+      return undefined;
+    }
+
+    return this._pauseAndRespond(frame, {
+      type: "eventBreakpoint",
+      breakpoint: eventBreakpoint,
+      message: makeEventBreakpointMessage(eventBreakpoint),
+    });
+  },
+
+  _captureDebuggerHooks() {
+    return {
+      onEnterFrame: this.dbg.onEnterFrame,
+      onStep: this.dbg.onStep,
+      onPop: this.dbg.onPop,
+    };
+  },
+
+  _restoreDebuggerHooks(hooks) {
+    this.dbg.onEnterFrame = hooks.onEnterFrame;
+    this.dbg.onStep = hooks.onStep;
+    this.dbg.onPop = hooks.onPop;
+  },
+
   /**
    * Pause the debuggee, by entering a nested event loop, and return a 'paused'
    * packet to the client.
    *
    * @param Debugger.Frame frame
    *        The newest debuggee frame in the stack.
    * @param object reason
    *        An object with a 'type' property containing the reason for the pause.
@@ -570,50 +681,51 @@ const ThreadActor = ActorClassWithSpec(t
     }
 
     // If the parent actor has been closed, terminate the debuggee script
     // instead of continuing. Executing JS after the content window is gone is
     // a bad idea.
     return this._parentClosed ? null : undefined;
   },
 
-  _makeOnEnterFrame: function({ thread, pauseAndRespond }) {
+  _makeOnEnterFrame: function({ pauseAndRespond }) {
     return frame => {
       const { sourceActor } = this.sources.getFrameLocation(frame);
 
       const url = sourceActor.url;
       if (this.sources.isBlackBoxed(url)) {
         return undefined;
       }
 
       // If the initial frame offset is a step target, we are done.
       if (frame.script.getOffsetMetadata(frame.offset).isStepStart) {
         return pauseAndRespond(frame);
       }
 
       // Continue forward until we get to a valid step target.
-      const { onStep, onPop } = thread._makeSteppingHooks(
+      const { onStep, onPop } = this._makeSteppingHooks(
         null, "next", false, null
       );
 
-      if (thread.dbg.replaying) {
+      if (this.dbg.replaying) {
         const offsets =
-          thread._findReplayingStepOffsets(null, frame,
+          this._findReplayingStepOffsets(null, frame,
                                            /* rewinding = */ false);
         frame.setReplayingOnStep(onStep, offsets);
       } else {
         frame.onStep = onStep;
       }
 
       frame.onPop = onPop;
       return undefined;
     };
   },
 
-  _makeOnPop: function({ thread, pauseAndRespond, startLocation, steppingType }) {
+  _makeOnPop: function({ pauseAndRespond, startLocation, steppingType }) {
+    const thread = this;
     const result = function(completion) {
       // onPop is called with 'this' set to the current frame.
       const location = thread.sources.getFrameLocation(this);
 
       const { sourceActor } = location;
       const url = sourceActor.url;
 
       if (thread.sources.isBlackBoxed(url)) {
@@ -723,18 +835,19 @@ const ThreadActor = ActorClassWithSpec(t
 
     if (pausePoint) {
       return pausePoint.step;
     }
 
     return script.getOffsetMetadata(offset).isStepStart;
   },
 
-  _makeOnStep: function({ thread, pauseAndRespond, startFrame,
+  _makeOnStep: function({ pauseAndRespond, startFrame,
                           startLocation, steppingType, completion, rewinding }) {
+    const thread = this;
     return function() {
       // onStep is called with 'this' set to the current frame.
 
       const location = thread.sources.getFrameLocation(this);
 
       // Always continue execution if either:
       //
       // 1. We are in a source mapped region, but inside a null mapping
@@ -823,17 +936,16 @@ const ThreadActor = ActorClassWithSpec(t
     // binding in each _makeOnX method, just do it once here and pass it
     // in to each function.
     const steppingHookState = {
       pauseAndRespond: (frame, onPacket = k=>k) => this._pauseAndRespond(
         frame,
         { type: "resumeLimit" },
         onPacket
       ),
-      thread: this,
       startFrame: this.youngestFrame,
       startLocation: startLocation,
       steppingType: steppingType,
       rewinding: rewinding,
       completion,
     };
 
     return {
--- a/devtools/server/actors/utils/event-breakpoints.js
+++ b/devtools/server/actors/utils/event-breakpoints.js
@@ -1,209 +1,417 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+function generalEvent(groupID, eventType) {
+  return {
+    id: `event.${groupID}.${eventType}`,
+    type: "event",
+    name: eventType,
+    message: `DOM '${eventType}' event`,
+    eventType,
+    filter: "general",
+  };
+}
+function nodeEvent(groupID, eventType) {
+  return {
+    ...generalEvent(groupID, eventType),
+    filter: "node",
+  };
+}
+function mediaNodeEvent(groupID, eventType) {
+  return {
+    ...generalEvent(groupID, eventType),
+    filter: "media",
+  };
+}
+function globalEvent(groupID, eventType) {
+  return {
+    ...generalEvent(groupID, eventType),
+    message: `Global '${eventType}' event`,
+    filter: "global",
+  };
+}
+function xhrEvent(groupID, eventType) {
+  return {
+    ...generalEvent(groupID, eventType),
+    message: `XHR '${eventType}' event`,
+    filter: "xhr",
+  };
+}
+function workerEvent(eventType) {
+  return {
+    ...generalEvent("worker", eventType),
+    message: `Worker '${eventType}' event`,
+    filter: "worker",
+  };
+}
+
+function timerEvent(type, operation, name, notificationType) {
+  return {
+    id: `timer.${type}.${operation}`,
+    type: "simple",
+    name,
+    message: name,
+    notificationType,
+  };
+}
+
+function animationEvent(operation, name, notificationType) {
+  return {
+    id: `animationframe.${operation}`,
+    type: "simple",
+    name,
+    message: name,
+    notificationType,
+  };
+}
+
+const AVAILABLE_BREAKPOINTS = [
+  {
+    name: "Animation",
+    items: [
+      animationEvent(
+        "request",
+        "Request Animation Frame",
+        "requestAnimationFrame"
+      ),
+      animationEvent(
+        "cancel",
+        "Cancel Animation Frame",
+        "cancelAnimationFrame"
+      ),
+      animationEvent(
+        "fire",
+        "Animation Frame fired",
+        "requestAnimationFrameCallback"
+      ),
+    ],
+  },
+  {
+    name: "Clipboard",
+    items: [
+      generalEvent("clipboard", "copy"),
+      generalEvent("clipboard", "cut"),
+      generalEvent("clipboard", "paste"),
+      generalEvent("clipboard", "beforecopy"),
+      generalEvent("clipboard", "beforecut"),
+      generalEvent("clipboard", "beforepaste"),
+    ],
+  },
+  {
+    name: "Control",
+    items: [
+      generalEvent("control", "resize"),
+      generalEvent("control", "scroll"),
+      generalEvent("control", "zoom"),
+      generalEvent("control", "focus"),
+      generalEvent("control", "blur"),
+      generalEvent("control", "select"),
+      generalEvent("control", "change"),
+      generalEvent("control", "submit"),
+      generalEvent("control", "reset"),
+    ],
+  },
+  {
+    name: "DOM Mutation",
+    items: [
+      // Deprecated DOM events.
+      nodeEvent("dom-mutation", "DOMActivate"),
+      nodeEvent("dom-mutation", "DOMFocusIn"),
+      nodeEvent("dom-mutation", "DOMFocusOut"),
+
+      // Standard DOM mutation events.
+      nodeEvent("dom-mutation", "DOMAttrModified"),
+      nodeEvent("dom-mutation", "DOMCharacterDataModified"),
+      nodeEvent("dom-mutation", "DOMNodeInserted"),
+      nodeEvent("dom-mutation", "DOMNodeInsertedIntoDocument"),
+      nodeEvent("dom-mutation", "DOMNodeRemoved"),
+      nodeEvent("dom-mutation", "DOMNodeRemovedIntoDocument"),
+      nodeEvent("dom-mutation", "DOMSubtreeModified"),
+
+      // DOM load events.
+      nodeEvent("dom-mutation", "DOMContentLoaded"),
+    ],
+  },
+  {
+    name: "Device",
+    items: [
+      globalEvent("device", "deviceorientation"),
+      globalEvent("device", "devicemotion"),
+    ],
+  },
+  {
+    name: "Drag and Drop",
+    items: [
+      generalEvent("drag-and-drop", "drag"),
+      generalEvent("drag-and-drop", "dragstart"),
+      generalEvent("drag-and-drop", "dragend"),
+      generalEvent("drag-and-drop", "dragenter"),
+      generalEvent("drag-and-drop", "dragover"),
+      generalEvent("drag-and-drop", "dragleave"),
+      generalEvent("drag-and-drop", "drop"),
+    ],
+  },
+  {
+    name: "Keyboard",
+    items: [
+      generalEvent("keyboard", "keydown"),
+      generalEvent("keyboard", "keyup"),
+      generalEvent("keyboard", "keypress"),
+      generalEvent("keyboard", "input"),
+    ],
+  },
+  {
+    name: "Load",
+    items: [
+      globalEvent("load", "load"),
+      globalEvent("load", "beforeunload"),
+      globalEvent("load", "unload"),
+      globalEvent("load", "abort"),
+      globalEvent("load", "error"),
+      globalEvent("load", "hashchange"),
+      globalEvent("load", "popstate"),
+    ],
+  },
+  {
+    name: "Media",
+    items: [
+      mediaNodeEvent("media", "play"),
+      mediaNodeEvent("media", "pause"),
+      mediaNodeEvent("media", "playing"),
+      mediaNodeEvent("media", "canplay"),
+      mediaNodeEvent("media", "canplaythrough"),
+      mediaNodeEvent("media", "seeking"),
+      mediaNodeEvent("media", "seeked"),
+      mediaNodeEvent("media", "timeupdate"),
+      mediaNodeEvent("media", "ended"),
+      mediaNodeEvent("media", "ratechange"),
+      mediaNodeEvent("media", "durationchange"),
+      mediaNodeEvent("media", "volumechange"),
+      mediaNodeEvent("media", "loadstart"),
+      mediaNodeEvent("media", "progress"),
+      mediaNodeEvent("media", "suspend"),
+      mediaNodeEvent("media", "abort"),
+      mediaNodeEvent("media", "error"),
+      mediaNodeEvent("media", "emptied"),
+      mediaNodeEvent("media", "stalled"),
+      mediaNodeEvent("media", "loadedmetadata"),
+      mediaNodeEvent("media", "loadeddata"),
+      mediaNodeEvent("media", "waiting"),
+    ],
+  },
+  {
+    name: "Mouse",
+    items: [
+      generalEvent("mouse", "auxclick"),
+      generalEvent("mouse", "click"),
+      generalEvent("mouse", "dblclick"),
+      generalEvent("mouse", "mousedown"),
+      generalEvent("mouse", "mouseup"),
+      generalEvent("mouse", "mouseover"),
+      generalEvent("mouse", "mousemove"),
+      generalEvent("mouse", "mouseout"),
+      generalEvent("mouse", "mouseenter"),
+      generalEvent("mouse", "mouseleave"),
+      generalEvent("mouse", "mousewheel"),
+      generalEvent("mouse", "wheel"),
+      generalEvent("mouse", "contextmenu"),
+    ],
+  },
+  {
+    name: "Pointer",
+    items: [
+      generalEvent("pointer", "pointerover"),
+      generalEvent("pointer", "pointerout"),
+      generalEvent("pointer", "pointerenter"),
+      generalEvent("pointer", "pointerleave"),
+      generalEvent("pointer", "pointerdown"),
+      generalEvent("pointer", "pointerup"),
+      generalEvent("pointer", "pointermove"),
+      generalEvent("pointer", "pointercancel"),
+      generalEvent("pointer", "gotpointercapture"),
+      generalEvent("pointer", "lostpointercapture"),
+    ],
+  },
+  {
+    name: "Timer",
+    items: [
+      timerEvent("timeout", "set", "setTimeout", "setTimeout"),
+      timerEvent("timeout", "clear", "clearTimeout", "clearTimeout"),
+      timerEvent("timeout", "fire", "setTimeout fired", "setTimeoutCallback"),
+      timerEvent("interval", "set", "setInterval", "setInterval"),
+      timerEvent("interval", "clear", "clearInterval", "clearInterval"),
+      timerEvent(
+        "interval",
+        "fire",
+        "setInterval fired",
+        "setIntervalCallback"
+      ),
+    ],
+  },
+  {
+    name: "Touch",
+    items: [
+      generalEvent("touch", "touchstart"),
+      generalEvent("touch", "touchmove"),
+      generalEvent("touch", "touchend"),
+      generalEvent("touch", "touchcancel"),
+    ],
+  },
+  {
+    name: "Worker",
+    items: [
+      workerEvent("message"),
+      workerEvent("messageerror"),
+    ],
+  },
+  {
+    name: "XHR",
+    items: [
+      xhrEvent("xhr", "readystatechange"),
+      xhrEvent("xhr", "load"),
+      xhrEvent("xhr", "loadstart"),
+      xhrEvent("xhr", "loadend"),
+      xhrEvent("xhr", "abort"),
+      xhrEvent("xhr", "error"),
+      xhrEvent("xhr", "progress"),
+      xhrEvent("xhr", "timeout"),
+    ],
+  },
+];
+
+const FLAT_EVENTS = [];
+for (const category of AVAILABLE_BREAKPOINTS) {
+  for (const event of category.items) {
+    FLAT_EVENTS.push(event);
+  }
+}
+const EVENTS_BY_ID = {};
+for (const event of FLAT_EVENTS) {
+  if (EVENTS_BY_ID[event.id]) {
+    throw new Error("Duplicate event ID detected: " + event.id);
+  }
+  EVENTS_BY_ID[event.id] = event;
+}
+
+const SIMPLE_EVENTS = {};
+const DOM_EVENTS = {};
+for (const eventBP of FLAT_EVENTS) {
+  if (eventBP.type === "simple") {
+    const { notificationType } = eventBP;
+    if (SIMPLE_EVENTS[notificationType]) {
+      throw new Error("Duplicate simple event");
+    }
+    SIMPLE_EVENTS[notificationType] = eventBP.id;
+  } else if (eventBP.type === "event") {
+    const { eventType, filter } = eventBP;
+
+    let targetTypes;
+    if (filter === "global") {
+      targetTypes = ["global"];
+    } else if (filter === "xhr") {
+      targetTypes = ["xhr"];
+    } else if (filter === "worker") {
+      targetTypes = ["worker"];
+    } else if (filter === "general") {
+      targetTypes = ["global", "node"];
+    } else if (filter === "node" || filter === "media") {
+      targetTypes = ["node"];
+    } else {
+      throw new Error("Unexpected filter type");
+    }
+
+    for (const targetType of targetTypes) {
+      let byEventType = DOM_EVENTS[targetType];
+      if (!byEventType) {
+        byEventType = {};
+        DOM_EVENTS[targetType] = byEventType;
+      }
+
+      if (byEventType[eventType]) {
+        throw new Error("Duplicate dom event: " + eventType);
+      }
+      byEventType[eventType] = eventBP.id;
+    }
+  } else {
+    throw new Error("Unknown type: " + eventBP.type);
+  }
+}
+
+exports.eventBreakpointForNotification = eventBreakpointForNotification;
+function eventBreakpointForNotification(dbg, notification) {
+  const notificationType = notification.type;
+
+  if (notification.type === "domEvent") {
+    const domEventNotification = DOM_EVENTS[notification.targetType];
+    if (!domEventNotification) {
+      return null;
+    }
+
+    // The 'event' value is a cross-compartment wrapper for the DOM Event object.
+    // While we could use that directly in the main thread as an Xray wrapper,
+    // when debugging workers we can't, because it is an opaque wrapper.
+    // To make things work, we have to always interact with the Event object via
+    // the Debugger.Object interface.
+    const evt = dbg
+      .makeGlobalObjectReference(notification.global)
+      .makeDebuggeeValue(notification.event);
+
+    const eventType = evt.getProperty("type").return;
+    const id = domEventNotification[eventType];
+    if (!id) {
+      return null;
+    }
+    const eventBreakpoint = EVENTS_BY_ID[id];
+
+    if (eventBreakpoint.filter === "media") {
+      const currentTarget = evt.getProperty("currentTarget").return;
+      if (!currentTarget) {
+        return null;
+      }
+
+      const nodeType = currentTarget.getProperty("nodeType").return;
+      const namespaceURI = currentTarget.getProperty("namespaceURI").return;
+      if (
+        nodeType !== 1 /* ELEMENT_NODE */ ||
+        namespaceURI !== "http://www.w3.org/1999/xhtml"
+      ) {
+        return null;
+      }
+
+      const nodeName =
+        currentTarget.getProperty("nodeName").return.toLowerCase();
+      if (nodeName !== "audio" && nodeName !== "video") {
+        return null;
+      }
+    }
+
+    return id;
+  }
+
+  return SIMPLE_EVENTS[notificationType] || null;
+}
+
+exports.makeEventBreakpointMessage = makeEventBreakpointMessage;
+function makeEventBreakpointMessage(id) {
+  return EVENTS_BY_ID[id].message;
+}
+
 exports.getAvailableEventBreakpoints = getAvailableEventBreakpoints;
 function getAvailableEventBreakpoints() {
   const available = [];
   for (const { name, items } of AVAILABLE_BREAKPOINTS) {
     available.push({
       name,
       events: items.map(item => ({
         id: item.id,
-        name: item.eventType,
+        name: item.name,
       })),
     });
   }
   return available;
 }
-
-function event(groupID, name, filter = "content") {
-  return {
-    id: `${groupID}.event.${name}`,
-    type: "event",
-    eventType: name,
-    filter,
-  };
-}
-
-const AVAILABLE_BREAKPOINTS = [
-  {
-    name: "Clipboard",
-    items: [
-      event("clipboard", "copy"),
-      event("clipboard", "cut"),
-      event("clipboard", "paste"),
-      event("clipboard", "beforecopy"),
-      event("clipboard", "beforecut"),
-      event("clipboard", "beforepaste"),
-    ],
-  },
-  {
-    name: "Control",
-    items: [
-      event("control", "resize"),
-      event("control", "scroll"),
-      event("control", "zoom"),
-      event("control", "focus"),
-      event("control", "blur"),
-      event("control", "select"),
-      event("control", "change"),
-      event("control", "submit"),
-      event("control", "reset"),
-    ],
-  },
-  {
-    name: "DOM Mutation",
-    items: [
-      // Deprecated DOM events.
-      event("dom-mutation", "DOMActivate"),
-      event("dom-mutation", "DOMFocusIn"),
-      event("dom-mutation", "DOMFocusOut"),
-
-      // Standard DOM mutation events.
-      event("dom-mutation", "DOMAttrModified"),
-      event("dom-mutation", "DOMCharacterDataModified"),
-      event("dom-mutation", "DOMNodeInserted"),
-      event("dom-mutation", "DOMNodeInsertedIntoDocument"),
-      event("dom-mutation", "DOMNodeRemoved"),
-      event("dom-mutation", "DOMNodeRemovedIntoDocument"),
-      event("dom-mutation", "DOMSubtreeModified"),
-
-      // DOM load events.
-      event("dom-mutation", "DOMContentLoaded"),
-    ],
-  },
-  {
-    name: "Device",
-    items: [
-      event("device", "deviceorientation"),
-      event("device", "devicemotion"),
-    ],
-  },
-  {
-    name: "Drag and Drop",
-    items: [
-      event("drag-and-drop", "drag"),
-      event("drag-and-drop", "dragstart"),
-      event("drag-and-drop", "dragend"),
-      event("drag-and-drop", "dragenter"),
-      event("drag-and-drop", "dragover"),
-      event("drag-and-drop", "dragleave"),
-      event("drag-and-drop", "drop"),
-    ],
-  },
-  {
-    name: "Keyboard",
-    items: [
-      event("keyboard", "keydown"),
-      event("keyboard", "keyup"),
-      event("keyboard", "keypress"),
-      event("keyboard", "input"),
-    ],
-  },
-  {
-    name: "Load",
-    items: [
-      event("load", "load", "global"),
-      event("load", "beforeunload", "global"),
-      event("load", "unload", "global"),
-      event("load", "abort", "global"),
-      event("load", "error", "global"),
-      event("load", "hashchange", "global"),
-      event("load", "popstate", "global"),
-    ],
-  },
-  {
-    name: "Media",
-    items: [
-      event("media", "play", "media"),
-      event("media", "pause", "media"),
-      event("media", "playing", "media"),
-      event("media", "canplay", "media"),
-      event("media", "canplaythrough", "media"),
-      event("media", "seeking", "media"),
-      event("media", "seeked", "media"),
-      event("media", "timeupdate", "media"),
-      event("media", "ended", "media"),
-      event("media", "ratechange", "media"),
-      event("media", "durationchange", "media"),
-      event("media", "volumechange", "media"),
-      event("media", "loadstart", "media"),
-      event("media", "progress", "media"),
-      event("media", "suspend", "media"),
-      event("media", "abort", "media"),
-      event("media", "error", "media"),
-      event("media", "emptied", "media"),
-      event("media", "stalled", "media"),
-      event("media", "loadedmetadata", "media"),
-      event("media", "loadeddata", "media"),
-      event("media", "waiting", "media"),
-    ],
-  },
-  {
-    name: "Mouse",
-    items: [
-      event("mouse", "auxclick"),
-      event("mouse", "click"),
-      event("mouse", "dblclick"),
-      event("mouse", "mousedown"),
-      event("mouse", "mouseup"),
-      event("mouse", "mouseover"),
-      event("mouse", "mousemove"),
-      event("mouse", "mouseout"),
-      event("mouse", "mouseenter"),
-      event("mouse", "mouseleave"),
-      event("mouse", "mousewheel"),
-      event("mouse", "wheel"),
-      event("mouse", "contextmenu"),
-    ],
-  },
-  {
-    name: "Pointer",
-    items: [
-      event("pointer", "pointerover"),
-      event("pointer", "pointerout"),
-      event("pointer", "pointerenter"),
-      event("pointer", "pointerleave"),
-      event("pointer", "pointerdown"),
-      event("pointer", "pointerup"),
-      event("pointer", "pointermove"),
-      event("pointer", "pointercancel"),
-      event("pointer", "gotpointercapture"),
-      event("pointer", "lostpointercapture"),
-    ],
-  },
-  {
-    name: "Touch",
-    items: [
-      event("touch", "touchstart"),
-      event("touch", "touchmove"),
-      event("touch", "touchend"),
-      event("touch", "touchcancel"),
-    ],
-  },
-  {
-    name: "Worker",
-    items: [
-      event("worker", "message", "global"),
-      event("worker", "messageerror", "global"),
-    ],
-  },
-  {
-    name: "XHR",
-    items: [
-      event("xhr", "readystatechange", "xhr"),
-      event("xhr", "load", "xhr"),
-      event("xhr", "loadstart", "xhr"),
-      event("xhr", "loadend", "xhr"),
-      event("xhr", "abort", "xhr"),
-      event("xhr", "error", "xhr"),
-      event("xhr", "progress", "xhr"),
-      event("xhr", "timeout", "xhr"),
-    ],
-  },
-];
--- a/devtools/server/actors/utils/make-debugger.js
+++ b/devtools/server/actors/utils/make-debugger.js
@@ -59,25 +59,33 @@ const { reportException } = require("dev
  */
 module.exports = function makeDebugger({ findDebuggees, shouldAddNewGlobalAsDebuggee }) {
   const dbg = isReplaying ? new ReplayDebugger() : new Debugger();
   EventEmitter.decorate(dbg);
 
   dbg.allowUnobservedAsmJS = true;
   dbg.uncaughtExceptionHook = reportDebuggerHookException;
 
+  function onNewDebuggee(global) {
+    if (dbg.onNewDebuggee) {
+      dbg.onNewDebuggee(global);
+    }
+  }
+
   dbg.onNewGlobalObject = function(global) {
     if (shouldAddNewGlobalAsDebuggee(global)) {
       safeAddDebuggee(this, global);
+      onNewDebuggee(global);
     }
   };
 
   dbg.addDebuggees = function() {
     for (const global of findDebuggees(this)) {
       safeAddDebuggee(this, global);
+      onNewDebuggee(global);
     }
   };
 
   return dbg;
 };
 
 const reportDebuggerHookException = e => reportException("Debugger Hook", e);
 
--- a/devtools/shared/builtin-modules.js
+++ b/devtools/shared/builtin-modules.js
@@ -18,16 +18,17 @@ const promise = require("resource://gre/
 const jsmScope = require("resource://devtools/shared/Loader.jsm");
 const { Services } = require("resource://gre/modules/Services.jsm");
 
 const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
 
 // Steal various globals only available in JSM scope (and not Sandbox one)
 const {
   console,
+  DebuggerNotificationObserver,
   DOMPoint,
   DOMQuad,
   DOMRect,
   HeapSnapshot,
   NamedNodeMap,
   NodeFilter,
   StructuredCloneHolder,
   TelemetryStopwatch,
@@ -224,16 +225,17 @@ function lazyRequireGetter(obj, property
     configurable: true,
     enumerable: true,
   });
 }
 
 // List of pseudo modules exposed to all devtools modules.
 exports.modules = {
   ChromeUtils,
+  DebuggerNotificationObserver,
   HeapSnapshot,
   promise,
   // Expose "chrome" Promise, which aren't related to any document
   // and so are never frozen, even if the browser loader module which
   // pull it is destroyed. See bug 1402779.
   Promise,
   Services: Object.create(Services),
   TelemetryStopwatch,
--- a/devtools/shared/client/constants.js
+++ b/devtools/shared/client/constants.js
@@ -39,16 +39,17 @@ const UnsolicitedNotifications = {
  * response to a client request.
  */
 const UnsolicitedPauses = {
   resumeLimit: "resumeLimit",
   debuggerStatement: "debuggerStatement",
   breakpoint: "breakpoint",
   DOMEvent: "DOMEvent",
   watchpoint: "watchpoint",
+  eventBreakpoint: "eventBreakpoint",
   exception: "exception",
   replayForcedPause: "replayForcedPause",
 };
 
 module.exports = {
   ThreadStateTypes,
   UnsolicitedNotifications,
   UnsolicitedPauses,
--- a/devtools/shared/fronts/webconsole.js
+++ b/devtools/shared/fronts/webconsole.js
@@ -92,16 +92,17 @@ class WebConsoleFront extends FrontClass
       // track the list of network event updates
       updates: [],
       private: actor.private,
       fromCache: actor.fromCache,
       fromServiceWorker: actor.fromServiceWorker,
       isThirdPartyTrackingResource: actor.isThirdPartyTrackingResource,
       referrerPolicy: actor.referrerPolicy,
       blockedReason: actor.blockedReason,
+      channelId: actor.channelId,
     };
     this._networkRequests.set(actor.actor, networkInfo);
 
     this.emit("networkEvent", networkInfo);
   }
 
   /**
    * The "networkEventUpdate" message type handler. We redirect any message to
--- a/devtools/shared/worker/loader.js
+++ b/devtools/shared/worker/loader.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-/* global worker */
+/* global worker, DebuggerNotificationObserver */
 
 // A CommonJS module loader that is designed to run inside a worker debugger.
 // We can't simply use the SDK module loader, because it relies heavily on
 // Components, which isn't available in workers.
 //
 // In principle, the standard instance of the worker loader should provide the
 // same built-in modules as its devtools counterpart, so that both loaders are
 // interchangable on the main thread, making them easier to test.
@@ -571,16 +571,17 @@ this.worker = new WorkerDebuggerLoader({
   },
   loadSubScript: loadSubScript,
   modules: {
     "Debugger": Debugger,
     "Services": Object.create(null),
     "chrome": chrome,
     "xpcInspector": xpcInspector,
     "ChromeUtils": ChromeUtils,
+    "DebuggerNotificationObserver": DebuggerNotificationObserver,
   },
   paths: {
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "devtools": "resource://devtools",
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "promise": "resource://gre/modules/Promise-backend.js",
     // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
     "xpcshell-test": "resource://test",
--- a/dom/base/DOMMatrix.cpp
+++ b/dom/base/DOMMatrix.cpp
@@ -37,36 +37,42 @@ JSObject* DOMMatrixReadOnly::WrapObject(
                                         JS::Handle<JSObject*> aGivenProto) {
   return DOMMatrixReadOnly_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::Constructor(
     const GlobalObject& aGlobal,
     const Optional<StringOrUnrestrictedDoubleSequence>& aArg,
     ErrorResult& aRv) {
-  RefPtr<DOMMatrixReadOnly> rval =
-      new DOMMatrixReadOnly(aGlobal.GetAsSupports());
   if (!aArg.WasPassed()) {
+    RefPtr<DOMMatrixReadOnly> rval =
+        new DOMMatrixReadOnly(aGlobal.GetAsSupports());
     return rval.forget();
   }
 
   const auto& arg = aArg.Value();
   if (arg.IsString()) {
     nsCOMPtr<nsPIDOMWindowInner> win =
         do_QueryInterface(aGlobal.GetAsSupports());
     if (!win) {
       aRv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
       return nullptr;
     }
+    RefPtr<DOMMatrixReadOnly> rval =
+        new DOMMatrixReadOnly(aGlobal.GetAsSupports());
     rval->SetMatrixValue(arg.GetAsString(), aRv);
-  } else {
-    const auto& sequence = arg.GetAsUnrestrictedDoubleSequence();
-    SetDataInMatrix(rval, sequence.Elements(), sequence.Length(), aRv);
+    return rval.forget();
   }
 
+  const auto& sequence = arg.GetAsUnrestrictedDoubleSequence();
+  const int length = sequence.Length();
+  const bool is2D = length == 6;
+  RefPtr<DOMMatrixReadOnly> rval =
+      new DOMMatrixReadOnly(aGlobal.GetAsSupports(), is2D);
+  SetDataInMatrix(rval, sequence.Elements(), length, aRv);
   return rval.forget();
 }
 
 already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::ReadStructuredClone(
     nsISupports* aParent, JSStructuredCloneReader* aReader) {
   uint8_t is2D;
 
   if (!JS_ReadBytes(aReader, &is2D, 1)) {
@@ -504,39 +510,46 @@ static void SetDataInMatrix(DOMMatrixRea
     lengthStr.AppendInt(aLength);
     aRv.ThrowTypeError<MSG_MATRIX_INIT_LENGTH_WRONG>(lengthStr);
   }
 }
 
 already_AddRefed<DOMMatrix> DOMMatrix::Constructor(const GlobalObject& aGlobal,
                                                    const Float32Array& aArray32,
                                                    ErrorResult& aRv) {
-  RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports());
   aArray32.ComputeLengthAndData();
-  SetDataInMatrix(obj, aArray32.Data(), aArray32.Length(), aRv);
+
+  const int length = aArray32.Length();
+  const bool is2D = length == 6;
+  RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D);
+  SetDataInMatrix(obj, aArray32.Data(), length, aRv);
 
   return obj.forget();
 }
 
 already_AddRefed<DOMMatrix> DOMMatrix::Constructor(const GlobalObject& aGlobal,
                                                    const Float64Array& aArray64,
                                                    ErrorResult& aRv) {
-  RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports());
   aArray64.ComputeLengthAndData();
-  SetDataInMatrix(obj, aArray64.Data(), aArray64.Length(), aRv);
+
+  const int length = aArray64.Length();
+  const bool is2D = length == 6;
+  RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D);
+  SetDataInMatrix(obj, aArray64.Data(), length, aRv);
 
   return obj.forget();
 }
 
 already_AddRefed<DOMMatrix> DOMMatrix::Constructor(
     const GlobalObject& aGlobal, const Sequence<double>& aNumberSequence,
     ErrorResult& aRv) {
-  RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports());
-  SetDataInMatrix(obj, aNumberSequence.Elements(), aNumberSequence.Length(),
-                  aRv);
+  const int length = aNumberSequence.Length();
+  const bool is2D = length == 6;
+  RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D);
+  SetDataInMatrix(obj, aNumberSequence.Elements(), length, aRv);
 
   return obj.forget();
 }
 
 already_AddRefed<DOMMatrix> DOMMatrix::ReadStructuredClone(
     nsISupports* aParent, JSStructuredCloneReader* aReader) {
   uint8_t is2D;
 
@@ -765,17 +778,19 @@ DOMMatrixReadOnly* DOMMatrixReadOnly::Se
   if (!ServoCSSParser::ParseTransformIntoMatrix(
           aTransformList, contains3dTransform, transform)) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return nullptr;
   }
 
   if (!contains3dTransform) {
     mMatrix3D = nullptr;
-    mMatrix2D = new gfx::MatrixDouble();
+    if (!mMatrix2D) {
+      mMatrix2D = new gfx::MatrixDouble();
+    }
 
     SetA(transform._11);
     SetB(transform._12);
     SetC(transform._21);
     SetD(transform._22);
     SetE(transform._41);
     SetF(transform._42);
   } else {
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2426,17 +2426,17 @@ class MethodDefiner(PropertyDefiner):
                 raise TypeError("Cannot have indexed getter/attr on "
                                 "interface %s with other members "
                                 "that generate @@iterator, such as "
                                 "maplike/setlike or aliased functions." %
                                 self.descriptor.interface.identifier.name)
             self.regular.append({
                 "name": "@@iterator",
                 "methodInfo": False,
-                "selfHostedName": "ArrayValues",
+                "selfHostedName": "$ArrayValues",
                 "length": 0,
                 "flags": "0", # Not enumerable, per spec.
                 "condition": MemberCondition()
             })
 
         # Generate the keys/values/entries aliases for value iterables.
         maplikeOrSetlikeOrIterable = descriptor.interface.maplikeOrSetlikeOrIterable
         if (not static and
@@ -2452,17 +2452,17 @@ class MethodDefiner(PropertyDefiner):
                 "length": 0,
                 "flags": "JSPROP_ENUMERATE",
                 "condition": PropertyDefiner.getControllingCondition(m,
                                                                      descriptor)
             })
             self.regular.append({
                 "name": "values",
                 "methodInfo": False,
-                "selfHostedName": "ArrayValues",
+                "selfHostedName": "$ArrayValues",
                 "length": 0,
                 "flags": "JSPROP_ENUMERATE",
                 "condition": PropertyDefiner.getControllingCondition(m,
                                                                      descriptor)
             })
             self.regular.append({
                 "name": "entries",
                 "methodInfo": False,
--- a/dom/gamepad/Gamepad.cpp
+++ b/dom/gamepad/Gamepad.cpp
@@ -65,16 +65,19 @@ Gamepad::Gamepad(nsISupports* aParent, c
   for (uint32_t i = 0; i < aNumLightIndicator; ++i) {
     mLightIndicators.AppendElement(
         new GamepadLightIndicator(mParent, mHashKey, i));
   }
   for (uint32_t i = 0; i < aNumTouchEvents; ++i) {
     mTouchEvents.AppendElement(new GamepadTouch(mParent));
   }
 
+  // Mapping touchId(0) to touchIdHash(0) by default.
+  mTouchIdHash.Put(0, mTouchIdHashValue);
+  ++mTouchIdHashValue;
   UpdateTimestamp();
 }
 
 void Gamepad::SetIndex(uint32_t aIndex) { mIndex = aIndex; }
 
 void Gamepad::SetConnected(bool aConnected) { mConnected = aConnected; }
 
 void Gamepad::SetButton(uint32_t aButton, bool aPressed, bool aTouched,
--- a/dom/gamepad/GamepadRemapping.cpp
+++ b/dom/gamepad/GamepadRemapping.cpp
@@ -356,16 +356,21 @@ class StadiaControllerRemapper final : p
     STADIA_BUTTON_EXTRA1 = BUTTON_INDEX_COUNT,
     STADIA_BUTTON_EXTRA2,
     STADIA_BUTTON_COUNT
   };
 };
 
 class Dualshock4Remapper final : public GamepadRemapper {
  public:
+  Dualshock4Remapper() {
+    mLastTouches.SetLength(TOUCH_EVENT_COUNT);
+    mLastTouchId.SetLength(TOUCH_EVENT_COUNT);
+  }
+
   virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
 
   virtual uint32_t GetButtonCount() const override {
     return DUALSHOCK_BUTTON_COUNT;
   }
 
   virtual uint32_t GetLightIndicatorCount() const override {
     return LIGHT_INDICATOR_COUNT;
@@ -398,64 +403,69 @@ class Dualshock4Remapper final : public 
     aReport[8] = aBlue;
   }
 
   virtual void GetTouchData(uint32_t aIndex, void* aInput) override {
     nsTArray<GamepadTouchState> touches(TOUCH_EVENT_COUNT);
     touches.SetLength(TOUCH_EVENT_COUNT);
     uint8_t* rawData = (uint8_t*)aInput;
 
+    const uint32_t kTouchDimensionX = 1920;
+    const uint32_t kTouchDimensionY = 942;
     bool touch0Pressed = (((rawData[35] & 0xff) >> 7) == 0);
     bool touch1Pressed = (((rawData[39] & 0xff) >> 7) == 0);
 
-    if (!touch0Pressed && !touch1Pressed) {
-      return;
-    }
-
-    if ((touch0Pressed && (rawData[35] & 0xff) < mLastTouch0Id) ||
-        (touch1Pressed && (rawData[39] & 0xff) < mLastTouch1Id)) {
+    if ((touch0Pressed && (rawData[35] & 0xff) < mLastTouchId[0]) ||
+        (touch1Pressed && (rawData[39] & 0xff) < mLastTouchId[1])) {
       mTouchIdBase += 128;
     }
 
-    const uint32_t kTouchDimensionX = 1920;
-    const uint32_t kTouchDimensionY = 942;
-
-    touches[0].touchId = mTouchIdBase + (rawData[35] & 0x7f);
-    touches[0].surfaceId = 0;
-    touches[0].position[0] = NormalizeTouch(
-        ((rawData[37] & 0xf) << 8) | rawData[36], 0, (kTouchDimensionX - 1));
-    touches[0].position[1] =
-        NormalizeTouch(rawData[38] << 4 | ((rawData[37] & 0xf0) >> 4), 0,
-                       (kTouchDimensionY - 1));
-    touches[0].surfaceDimensions[0] = kTouchDimensionX;
-    touches[0].surfaceDimensions[1] = kTouchDimensionY;
-    touches[0].isSurfaceDimensionsValid = true;
-    mLastTouch0Id = rawData[35] & 0x7f;
-
-    touches[1].touchId = mTouchIdBase + (rawData[39] & 0x7f);
-    touches[1].surfaceId = 0;
-    touches[1].position[0] =
-        NormalizeTouch((((rawData[41] & 0xf) << 8) | rawData[40]) + 1, 0,
-                       (kTouchDimensionX - 1));
-    touches[1].position[1] =
-        NormalizeTouch(rawData[42] << 4 | ((rawData[41] & 0xf0) >> 4), 0,
-                       (kTouchDimensionY - 1));
-    touches[1].surfaceDimensions[0] = kTouchDimensionX;
-    touches[1].surfaceDimensions[1] = kTouchDimensionY;
-    touches[1].isSurfaceDimensionsValid = true;
-    mLastTouch1Id = rawData[39] & 0x7f;
+    if (touch0Pressed) {
+      touches[0].touchId = mTouchIdBase + (rawData[35] & 0x7f);
+      touches[0].surfaceId = 0;
+      touches[0].position[0] = NormalizeTouch(
+          ((rawData[37] & 0xf) << 8) | rawData[36], 0, (kTouchDimensionX - 1));
+      touches[0].position[1] =
+          NormalizeTouch(rawData[38] << 4 | ((rawData[37] & 0xf0) >> 4), 0,
+                         (kTouchDimensionY - 1));
+      touches[0].surfaceDimensions[0] = kTouchDimensionX;
+      touches[0].surfaceDimensions[1] = kTouchDimensionY;
+      touches[0].isSurfaceDimensionsValid = true;
+      mLastTouchId[0] = rawData[35] & 0x7f;
+    }
+    if (touch1Pressed) {
+      touches[1].touchId = mTouchIdBase + (rawData[39] & 0x7f);
+      touches[1].surfaceId = 0;
+      touches[1].position[0] =
+          NormalizeTouch((((rawData[41] & 0xf) << 8) | rawData[40]) + 1, 0,
+                         (kTouchDimensionX - 1));
+      touches[1].position[1] =
+          NormalizeTouch(rawData[42] << 4 | ((rawData[41] & 0xf0) >> 4), 0,
+                         (kTouchDimensionY - 1));
+      touches[1].surfaceDimensions[0] = kTouchDimensionX;
+      touches[1].surfaceDimensions[1] = kTouchDimensionY;
+      touches[1].isSurfaceDimensionsValid = true;
+      mLastTouchId[1] = rawData[39] & 0x7f;
+    }
 
     RefPtr<GamepadPlatformService> service =
         GamepadPlatformService::GetParentService();
     if (!service) {
       return;
     }
 
-    service->NewMultiTouchEvent(aIndex, 0, touches[0]);
-    service->NewMultiTouchEvent(aIndex, 1, touches[1]);
+    // Avoid to send duplicate untouched events to the gamepad service.
+    if ((mLastTouches[0] != touch0Pressed) || touch0Pressed) {
+      service->NewMultiTouchEvent(aIndex, 0, touches[0]);
+    }
+    if ((mLastTouches[1] != touch1Pressed) || touch1Pressed) {
+      service->NewMultiTouchEvent(aIndex, 1, touches[1]);
+    }
+    mLastTouches[0] = touch0Pressed;
+    mLastTouches[1] = touch1Pressed;
   }
 
   virtual void RemapAxisMoveEvent(uint32_t aIndex, uint32_t aAxis,
                                   double aValue) const override {
     RefPtr<GamepadPlatformService> service =
         GamepadPlatformService::GetParentService();
     if (!service) {
       return;
@@ -532,18 +542,18 @@ class Dualshock4Remapper final : public 
   enum Dualshock4Buttons {
     DUALSHOCK_BUTTON_TOUCHPAD = BUTTON_INDEX_COUNT,
     DUALSHOCK_BUTTON_COUNT
   };
 
   static const uint32_t LIGHT_INDICATOR_COUNT = 1;
   static const uint32_t TOUCH_EVENT_COUNT = 2;
 
-  unsigned long mLastTouch0Id = 0;
-  unsigned long mLastTouch1Id = 0;
+  nsTArray<unsigned long> mLastTouchId;
+  nsTArray<bool> mLastTouches;
   unsigned long mTouchIdBase = 0;
 };
 
 class LogitechDInputRemapper final : public GamepadRemapper {
  public:
   virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }
 
   virtual uint32_t GetButtonCount() const override {
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -4056,20 +4056,16 @@ nsresult HTMLMediaElement::BindToTree(Bi
   nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
 
   if (IsInComposedDoc()) {
     // Construct Shadow Root so web content can be hidden in the DOM.
     AttachAndSetUAShadowRoot();
     NotifyUAWidgetSetupOrChange();
   }
 
-  // FIXME(emilio, bug 1555946): mUnboundFromTree doesn't make any sense, should
-  // just use IsInComposedDoc() in the relevant places or something.
-  mUnboundFromTree = false;
-
   if (IsInUncomposedDoc()) {
     // The preload action depends on the value of the autoplay attribute.
     // It's value may have changed, so update it.
     UpdatePreloadAction();
   }
 
   NotifyDecoderActivityChanges();
 
@@ -4273,36 +4269,42 @@ void HTMLMediaElement::ReportTelemetry()
                this, key.get()));
         }
       }
     }
   }
 }
 
 void HTMLMediaElement::UnbindFromTree(bool aNullParent) {
-  mUnboundFromTree = true;
   mVisibilityState = Visibility::Untracked;
 
   if (IsInComposedDoc()) {
     NotifyUAWidgetTeardown();
   }
 
   nsGenericHTMLElement::UnbindFromTree(aNullParent);
 
   MOZ_ASSERT(IsHidden());
   NotifyDecoderActivityChanges();
 
-  RefPtr<HTMLMediaElement> self(this);
-  nsCOMPtr<nsIRunnable> task =
-      NS_NewRunnableFunction("dom::HTMLMediaElement::UnbindFromTree", [self]() {
-        if (self->mUnboundFromTree) {
-          self->Pause();
-        }
-      });
-  RunInStableState(task);
+  // Dispatch a task to run once we're in a stable state which ensures we're
+  // paused if we're no longer in a document. Note we set a flag here to
+  // ensure we don't dispatch redundant tasks.
+  if (!mDispatchedTaskToPauseIfNotInDocument) {
+    mDispatchedTaskToPauseIfNotInDocument = true;
+    nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
+        "dom::HTMLMediaElement::UnbindFromTree",
+        [self = RefPtr<HTMLMediaElement>(this)]() {
+          self->mDispatchedTaskToPauseIfNotInDocument = false;
+          if (!self->IsInComposedDoc()) {
+            self->Pause();
+          }
+        });
+    RunInStableState(task);
+  }
 }
 
 /* static */
 CanPlayStatus HTMLMediaElement::GetCanPlay(
     const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics) {
   Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType);
   if (!containerType) {
     return CANPLAY_NO;
@@ -5691,17 +5693,17 @@ void HTMLMediaElement::CheckAutoplayData
 }
 
 bool HTMLMediaElement::IsActive() const {
   Document* ownerDoc = OwnerDoc();
   return ownerDoc && ownerDoc->IsActive() && ownerDoc->IsVisible();
 }
 
 bool HTMLMediaElement::IsHidden() const {
-  return mUnboundFromTree || OwnerDoc()->Hidden();
+  return !IsInComposedDoc() || OwnerDoc()->Hidden();
 }
 
 VideoFrameContainer* HTMLMediaElement::GetVideoFrameContainer() {
   if (mShuttingDown) {
     return nullptr;
   }
 
   if (mVideoFrameContainer) return mVideoFrameContainer;
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1666,20 +1666,21 @@ class HTMLMediaElement : public nsGeneri
   RefPtr<VideoTrackList> mVideoTrackList;
 
   UniquePtr<MediaStreamTrackListener> mMediaStreamTrackListener;
 
   // The principal guarding mVideoFrameContainer access when playing a
   // MediaStream.
   nsCOMPtr<nsIPrincipal> mSrcStreamVideoPrincipal;
 
-  // True if UnbindFromTree() is called on the element.
-  // Note this flag is false when the element is in a phase after creation and
-  // before attaching to the DOM tree.
-  bool mUnboundFromTree = false;
+  // True if we've dispatched a task in UnbindFromTree() which runs in a
+  // stable state and attempts to pause playback if we're not in a composed
+  // document. The flag stops us dispatching multiple tasks if the element
+  // is involved in a series of append/remove cycles.
+  bool mDispatchedTaskToPauseIfNotInDocument = false;
 
   // True if the autoplay media was blocked because it hadn't loaded metadata
   // yet.
   bool mBlockedAsWithoutMetadata = false;
 
   // This promise is used to notify MediaElementAudioSourceNode that media
   // element is allowed to play when MediaElement is used as a source for web
   // audio.
--- a/dom/html/HTMLVideoElement.cpp
+++ b/dom/html/HTMLVideoElement.cpp
@@ -381,40 +381,40 @@ void HTMLVideoElement::ReleaseVideoWakeL
     mScreenWakeLock = nullptr;
     return;
   }
 }
 
 bool HTMLVideoElement::SetVisualCloneTarget(
     HTMLVideoElement* aVisualCloneTarget) {
   MOZ_DIAGNOSTIC_ASSERT(
-      !aVisualCloneTarget || !aVisualCloneTarget->mUnboundFromTree,
+      !aVisualCloneTarget || aVisualCloneTarget->IsInComposedDoc(),
       "Can't set the clone target to a disconnected video "
       "element.");
   MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneSource,
                         "Can't clone a video element that is already a clone.");
   if (!aVisualCloneTarget ||
-      (!aVisualCloneTarget->mUnboundFromTree && !mVisualCloneSource)) {
+      (aVisualCloneTarget->IsInComposedDoc() && !mVisualCloneSource)) {
     mVisualCloneTarget = aVisualCloneTarget;
     return true;
   }
   return false;
 }
 
 bool HTMLVideoElement::SetVisualCloneSource(
     HTMLVideoElement* aVisualCloneSource) {
   MOZ_DIAGNOSTIC_ASSERT(
-      !aVisualCloneSource || !aVisualCloneSource->mUnboundFromTree,
+      !aVisualCloneSource || aVisualCloneSource->IsInComposedDoc(),
       "Can't set the clone source to a disconnected video "
       "element.");
   MOZ_DIAGNOSTIC_ASSERT(!mVisualCloneTarget,
                         "Can't clone a video element that is already a "
                         "clone.");
   if (!aVisualCloneSource ||
-      (!aVisualCloneSource->mUnboundFromTree && !mVisualCloneTarget)) {
+      (aVisualCloneSource->IsInComposedDoc() && !mVisualCloneTarget)) {
     mVisualCloneSource = aVisualCloneSource;
     return true;
   }
   return false;
 }
 
 /* static */
 void HTMLVideoElement::InitStatics() {
@@ -447,21 +447,21 @@ double HTMLVideoElement::TotalPlayTime()
     }
   }
 
   return total;
 }
 
 void HTMLVideoElement::CloneElementVisually(HTMLVideoElement& aTargetVideo,
                                             ErrorResult& rv) {
-  MOZ_ASSERT(!mUnboundFromTree,
+  MOZ_ASSERT(IsInComposedDoc(),
              "Can't clone a video that's not bound to a DOM tree.");
-  MOZ_ASSERT(!aTargetVideo.mUnboundFromTree,
+  MOZ_ASSERT(aTargetVideo.IsInComposedDoc(),
              "Can't clone to a video that's not bound to a DOM tree.");
-  if (mUnboundFromTree || aTargetVideo.mUnboundFromTree) {
+  if (!IsInComposedDoc() || !aTargetVideo.IsInComposedDoc()) {
     rv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 
   // Do we already have a visual clone target? If so, shut it down.
   if (mVisualCloneTarget) {
     EndCloningVisually();
   }
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -3327,24 +3327,26 @@ bool BrowserChild::DeallocPBrowserBridge
 
 ScreenIntSize BrowserChild::GetInnerSize() {
   LayoutDeviceIntSize innerSize =
       RoundedToInt(mUnscaledInnerSize * mPuppetWidget->GetDefaultScale());
   return ViewAs<ScreenPixel>(
       innerSize, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
 };
 
-nsRect BrowserChild::GetVisibleRect() {
+LayoutDeviceIntRect BrowserChild::GetVisibleRect() {
+  CSSRect visibleRect;
   if (mIsTopLevel) {
     // We are conservative about visible rects for top-level browsers to avoid
     // artifacts when resizing
-    return nsRect(nsPoint(), CSSPixel::ToAppUnits(mUnscaledInnerSize));
+    visibleRect = CSSRect(CSSPoint(), mUnscaledInnerSize);
   } else {
-    return mEffectsInfo.mVisibleRect;
+    visibleRect = CSSPixel::FromAppUnits(mEffectsInfo.mVisibleRect);
   }
+  return RoundedToInt(visibleRect * mPuppetWidget->GetDefaultScale());
 }
 
 ScreenIntRect BrowserChild::GetOuterRect() {
   LayoutDeviceIntRect outerRect =
       RoundedToInt(mUnscaledOuterRect * mPuppetWidget->GetDefaultScale());
   return ViewAs<ScreenPixel>(
       outerRect, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
 }
--- a/dom/ipc/BrowserChild.h
+++ b/dom/ipc/BrowserChild.h
@@ -544,17 +544,17 @@ class BrowserChild final : public nsMess
 
   bool ParentIsActive() const { return mParentIsActive; }
 
   const mozilla::layers::CompositorOptions& GetCompositorOptions() const;
   bool AsyncPanZoomEnabled() const;
 
   ScreenIntSize GetInnerSize();
 
-  nsRect GetVisibleRect();
+  LayoutDeviceIntRect GetVisibleRect();
 
   // Call RecvShow(nsIntSize(0, 0)) and block future calls to RecvShow().
   void DoFakeShow(const ShowInfo& aShowInfo);
 
   void ContentReceivedInputBlock(uint64_t aInputBlockId,
                                  bool aPreventDefault) const;
   void SetTargetAPZC(
       uint64_t aInputBlockId,
--- a/dom/media/webaudio/MediaElementAudioSourceNode.cpp
+++ b/dom/media/webaudio/MediaElementAudioSourceNode.cpp
@@ -9,17 +9,18 @@
 #include "AudioDestinationNode.h"
 #include "nsIScriptError.h"
 #include "AudioNodeStream.h"
 
 namespace mozilla {
 namespace dom {
 
 MediaElementAudioSourceNode::MediaElementAudioSourceNode(AudioContext* aContext)
-    : MediaStreamAudioSourceNode(aContext) {}
+    : MediaStreamAudioSourceNode(aContext, TrackChangeBehavior::FollowChanges) {
+}
 
 /* static */
 already_AddRefed<MediaElementAudioSourceNode>
 MediaElementAudioSourceNode::Create(
     AudioContext& aAudioContext, const MediaElementAudioSourceOptions& aOptions,
     ErrorResult& aRv) {
   if (aAudioContext.IsOffline()) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/dom/MediaStreamAudioSourceNodeBinding.h"
 #include "AudioNodeEngine.h"
 #include "AudioNodeExternalInputStream.h"
 #include "AudioStreamTrack.h"
 #include "mozilla/dom/Document.h"
 #include "mozilla/CORSMode.h"
 #include "nsContentUtils.h"
 #include "nsIScriptError.h"
+#include "nsID.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamAudioSourceNode)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamAudioSourceNode)
   tmp->Destroy();
@@ -32,19 +33,21 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamAudioSourceNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(MediaStreamAudioSourceNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(MediaStreamAudioSourceNode, AudioNode)
 
-MediaStreamAudioSourceNode::MediaStreamAudioSourceNode(AudioContext* aContext)
+MediaStreamAudioSourceNode::MediaStreamAudioSourceNode(
+    AudioContext* aContext, TrackChangeBehavior aBehavior)
     : AudioNode(aContext, 2, ChannelCountMode::Max,
-                ChannelInterpretation::Speakers) {}
+                ChannelInterpretation::Speakers),
+      mBehavior(aBehavior) {}
 
 /* static */
 already_AddRefed<MediaStreamAudioSourceNode> MediaStreamAudioSourceNode::Create(
     AudioContext& aAudioContext, const MediaStreamAudioSourceOptions& aOptions,
     ErrorResult& aRv) {
   if (aAudioContext.IsOffline()) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
@@ -58,17 +61,17 @@ already_AddRefed<MediaStreamAudioSourceN
                                     NS_LITERAL_CSTRING("Web Audio"), document,
                                     nsContentUtils::eDOM_PROPERTIES,
                                     "MediaStreamAudioSourceNodeDifferentRate");
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
   }
 
   RefPtr<MediaStreamAudioSourceNode> node =
-      new MediaStreamAudioSourceNode(&aAudioContext);
+      new MediaStreamAudioSourceNode(&aAudioContext, LockOnTrackPicked);
 
   node->Init(aOptions.mMediaStream, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   return node.forget();
 }
@@ -91,17 +94,17 @@ void MediaStreamAudioSourceNode::Init(DO
   AudioNodeEngine* engine = new MediaStreamAudioSourceNodeEngine(this);
   mStream = AudioNodeExternalInputStream::Create(graph, engine);
   mInputStream->AddConsumerToKeepAlive(ToSupports(this));
 
   mInputStream->RegisterTrackListener(this);
   if (mInputStream->Active()) {
     NotifyActive();
   }
-  AttachToFirstTrack(mInputStream);
+  AttachToRightTrack(mInputStream, aRv);
 }
 
 void MediaStreamAudioSourceNode::Destroy() {
   if (mInputStream) {
     mInputStream->UnregisterTrackListener(this);
     mInputStream = nullptr;
   }
   DetachFromTrack();
@@ -132,56 +135,82 @@ void MediaStreamAudioSourceNode::DetachF
     mInputTrack = nullptr;
   }
   if (mInputPort) {
     mInputPort->Destroy();
     mInputPort = nullptr;
   }
 }
 
-void MediaStreamAudioSourceNode::AttachToFirstTrack(
-    const RefPtr<DOMMediaStream>& aMediaStream) {
+static int AudioTrackCompare(const RefPtr<AudioStreamTrack>& aLhs,
+                             const RefPtr<AudioStreamTrack>& aRhs) {
+  nsAutoStringN<NSID_LENGTH> IDLhs;
+  nsAutoStringN<NSID_LENGTH> IDRhs;
+  aLhs->GetId(IDLhs);
+  aRhs->GetId(IDRhs);
+  return NS_ConvertUTF16toUTF8(IDLhs).Compare(
+      NS_ConvertUTF16toUTF8(IDRhs).get());
+}
+
+void MediaStreamAudioSourceNode::AttachToRightTrack(
+    const RefPtr<DOMMediaStream>& aMediaStream, ErrorResult& aRv) {
   nsTArray<RefPtr<AudioStreamTrack>> tracks;
   aMediaStream->GetAudioTracks(tracks);
 
+  if (tracks.IsEmpty() && mBehavior == LockOnTrackPicked) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  // Sort the track to have a stable order, on their ID by lexicographic
+  // ordering on sequences of code unit values.
+  tracks.Sort(AudioTrackCompare);
+
   for (const RefPtr<AudioStreamTrack>& track : tracks) {
-    if (track->Ended()) {
-      continue;
+    if (mBehavior == FollowChanges) {
+      if (track->Ended()) {
+        continue;
+      }
     }
 
     AttachToTrack(track);
     MarkActive();
     return;
   }
 
   // There was no track available. We'll allow the node to be garbage collected.
   MarkInactive();
 }
 
 void MediaStreamAudioSourceNode::NotifyTrackAdded(
     const RefPtr<MediaStreamTrack>& aTrack) {
+  if (mBehavior != FollowChanges) {
+    return;
+  }
   if (mInputTrack) {
     return;
   }
 
   if (!aTrack->AsAudioStreamTrack()) {
     return;
   }
 
   AttachToTrack(aTrack);
 }
 
 void MediaStreamAudioSourceNode::NotifyTrackRemoved(
     const RefPtr<MediaStreamTrack>& aTrack) {
-  if (aTrack != mInputTrack) {
-    return;
+  if (mBehavior == FollowChanges) {
+    if (aTrack != mInputTrack) {
+      return;
+    }
+
+    DetachFromTrack();
+    AttachToRightTrack(mInputStream, IgnoreErrors());
   }
-
-  DetachFromTrack();
-  AttachToFirstTrack(mInputStream);
 }
 
 void MediaStreamAudioSourceNode::NotifyActive() {
   MOZ_ASSERT(mInputStream);
   Context()->StartBlockedAudioContextIfAllowed();
 }
 
 /**
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.h
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.h
@@ -75,34 +75,50 @@ class MediaStreamAudioSourceNode
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
   // Attaches to aTrack so that its audio content will be used as input.
   void AttachToTrack(const RefPtr<MediaStreamTrack>& aTrack);
 
   // Detaches from the currently attached track if there is one.
   void DetachFromTrack();
 
-  // Attaches to the first available audio track in aMediaStream.
-  void AttachToFirstTrack(const RefPtr<DOMMediaStream>& aMediaStream);
+  // Attaches to the first audio track in the MediaStream, when the tracks are
+  // ordered by id.
+  void AttachToRightTrack(const RefPtr<DOMMediaStream>& aMediaStream,
+                          ErrorResult& aRv);
 
   // From DOMMediaStream::TrackListener.
   void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override;
   void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override;
   void NotifyActive() override;
 
   // From PrincipalChangeObserver<MediaStreamTrack>.
   void PrincipalChanged(MediaStreamTrack* aMediaStreamTrack) override;
 
+  // This allows implementing the correct behaviour for both
+  // MediaElementAudioSourceNode and MediaStreamAudioSourceNode, that have most
+  // of their behaviour shared.
+  enum TrackChangeBehavior {
+    // MediaStreamAudioSourceNode locks on the track it picked, and never
+    // changes.
+    LockOnTrackPicked,
+    // MediaElementAudioSourceNode can change track, depending on what the
+    // HTMLMediaElement does.
+    FollowChanges
+  };
+
  protected:
-  explicit MediaStreamAudioSourceNode(AudioContext* aContext);
+  MediaStreamAudioSourceNode(AudioContext* aContext,
+                             TrackChangeBehavior aBehavior);
   void Init(DOMMediaStream* aMediaStream, ErrorResult& aRv);
   virtual void Destroy();
   virtual ~MediaStreamAudioSourceNode();
 
  private:
+  const TrackChangeBehavior mBehavior;
   RefPtr<MediaInputPort> mInputPort;
   RefPtr<DOMMediaStream> mInputStream;
 
   // On construction we set this to the first audio track of mInputStream.
   RefPtr<MediaStreamTrack> mInputTrack;
 };
 
 }  // namespace dom
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -99,17 +99,16 @@ skip-if = (os == "win" && processor == "
 [test_bug964376.html]
 [test_bug966247.html]
 tags=capturestream
 [test_bug972678.html]
 [test_bug1113634.html]
 [test_bug1118372.html]
 [test_bug1027864.html]
 [test_bug1056032.html]
-skip-if = toolkit == 'android' # bug 1056706
 [test_bug1255618.html]
 skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1538360
 [test_bug1267579.html]
 [test_bug1355798.html]
 [test_bug1447273.html]
 skip-if = toolkit == 'android' && debug # bug 1485407
 [test_channelMergerNode.html]
 [test_channelMergerNodeWithVolume.html]
@@ -135,17 +134,16 @@ skip-if = !asan && toolkit != android
 [test_currentTime.html]
 [test_decodeAudioDataOnDetachedBuffer.html]
 [test_decodeAudioDataPromise.html]
 [test_decodeMultichannel.html]
 [test_decodeOpusTail.html]
 [test_delayNode.html]
 [test_delayNodeAtMax.html]
 [test_delayNodeChannelChanges.html]
-skip-if = toolkit == 'android' # bug 1056706
 [test_delayNodeCycles.html]
 [test_delayNodePassThrough.html]
 [test_delayNodeSmallMaxDelay.html]
 [test_delayNodeTailIncrease.html]
 [test_delayNodeTailWithDisconnect.html]
 [test_delayNodeTailWithGain.html]
 [test_delayNodeTailWithReconnect.html]
 [test_delayNodeWithGain.html]
--- a/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html
+++ b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html
@@ -28,17 +28,17 @@ function tryToCreateNodeOnClosedContext(
     { name: "createChannelSplitter" },
     { name: "createChannelMerger" },
     { name: "createDynamicsCompressor" },
     { name: "createOscillator" },
     { name: "createMediaElementSource",
       args: [new Audio()],
       onOfflineAudioContext: false },
     { name: "createMediaStreamSource",
-      args: [new Audio().mozCaptureStream()],
+      args: [(new AudioContext()).createMediaStreamDestination().stream],
       onOfflineAudioContext: false } ].forEach(function(e) {
 
       if (e.onOfflineAudioContext == false &&
           ctx instanceof OfflineAudioContext) {
         return;
       }
 
       expectNoException(function() {
@@ -69,17 +69,16 @@ function tryLegalOpeerationsOnClosedCont
   ].forEach(function(e) {
     expectNoException(function() {
       ctx[e.name].apply(ctx, e.args);
     });
   });
   loadFile("ting-44.1k-1ch.ogg", function(buf) {
     ctx.decodeAudioData(buf).then(function(decodedBuf) {
       ok(true, "decodeAudioData on a closed context should work, it did.")
-      todo(false, "0 " + (ctx instanceof OfflineAudioContext ? "Offline" : "Realtime"));
       finish();
     }).catch(function(e){
       ok(false, "decodeAudioData on a closed context should work, it did not");
       finish();
     });
   });
 }
 
@@ -116,27 +115,24 @@ function testMultiContextOutput() {
       var input = e.inputBuffer.getChannelData(0);
       var silent = true;
       for (var i = 0; i < input.length; i++) {
         if (input[i] != 0.0) {
           silent = false;
         }
       }
 
-      todo(false, "input buffer is " + (silent ? "silent" : "noisy"));
-
       if (silent) {
         silentBuffersInARow++;
         if (silentBuffersInARow == 10) {
           ok(true,
               "MediaStreams produce silence when their input is blocked.");
           sp2.onaudioprocess = null;
           ac1.close();
           ac2.close();
-          todo(false,"1");
           finish();
         }
       } else {
         is(silentBuffersInARow, 0,
             "No non silent buffer inbetween silent buffers.");
       }
     }
 
@@ -177,17 +173,16 @@ function testMultiContextInput() {
         var delta = Math.abs(inputBuffer[1] - sp2.value),
             theoreticalIncrement = 2048 * 3 * Math.PI * 2 * osc1.frequency.value / ac1.sampleRate;
         ok(delta >= theoreticalIncrement,
             "Buffering did not occur when the context was suspended (delta:" + delta + " increment: " + theoreticalIncrement+")");
         ac1.close();
         ac2.close();
         sp1.onaudioprocess = null;
         sp2.onaudioprocess = null;
-        todo(false, "2");
         finish();
       }
     }
 
     sp2.onaudioprocess = function(e) {
       var inputBuffer = e.inputBuffer.getChannelData(0);
       sp2.value = inputBuffer[inputBuffer.length - 1];
       ac2.suspend().then(function() {
@@ -226,17 +221,16 @@ function testScriptProcessNodeSuspended(
             ac.resume().then(function() {
               remainingIterations = 30;
               afterResume = true;
             });
           });
         }
       } else {
         sp.onaudioprocess = null;
-        todo(false,"3");
         finish();
       }
     }
   }
   sp.connect(ac.destination);
 }
 
 // Take an AudioContext, make sure it switches to running when the audio starts
--- a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeCrossOrigin.html
+++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeCrossOrigin.html
@@ -7,51 +7,54 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
 var audio = new Audio("http://example.org:80/tests/dom/media/webaudio/test/small-shot.ogg");
+audio.load();
 var context = new AudioContext();
-var node = context.createMediaStreamSource(audio.mozCaptureStreamUntilEnded());
-var sp = context.createScriptProcessor(2048, 1);
-node.connect(sp);
-var nonzeroSampleCount = 0;
-var complete = false;
-var iterationCount = 0;
+audio.onloadedmetadata = function() {
+  var node = context.createMediaStreamSource(audio.mozCaptureStreamUntilEnded());
+  var sp = context.createScriptProcessor(2048, 1);
+  node.connect(sp);
+  var nonzeroSampleCount = 0;
+  var complete = false;
+  var iterationCount = 0;
+
+  // This test ensures we receive at least expectedSampleCount nonzero samples
+  function processSamples(e) {
+    if (complete) {
+      return;
+    }
 
-// This test ensures we receive at least expectedSampleCount nonzero samples
-function processSamples(e) {
-  if (complete) {
-    return;
+    if (iterationCount == 0) {
+      // Don't start playing the audio until the AudioContext stuff is connected
+      // and running.
+      audio.play();
+    }
+    ++iterationCount;
+
+    var buf = e.inputBuffer.getChannelData(0);
+    var nonzeroSamplesThisBuffer = 0;
+    for (var i = 0; i < buf.length; ++i) {
+      if (buf[i] != 0) {
+        ++nonzeroSamplesThisBuffer;
+      }
+    }
+    is(nonzeroSamplesThisBuffer, 0,
+       "Checking all samples are zero");
+    if (iterationCount >= 20) {
+      SimpleTest.finish();
+      complete = true;
+    }
   }
 
-  if (iterationCount == 0) {
-    // Don't start playing the audio until the AudioContext stuff is connected
-    // and running.
-    audio.play();
-  }
-  ++iterationCount;
-
-  var buf = e.inputBuffer.getChannelData(0);
-  var nonzeroSamplesThisBuffer = 0;
-  for (var i = 0; i < buf.length; ++i) {
-    if (buf[i] != 0) {
-      ++nonzeroSamplesThisBuffer;
-    }
-  }
-  is(nonzeroSamplesThisBuffer, 0,
-     "Checking all samples are zero");
-  if (iterationCount >= 20) {
-    SimpleTest.finish();
-    complete = true;
-  }
+  audio.oncanplaythrough = function() {
+    sp.onaudioprocess = processSamples;
+  };
 }
-
-audio.oncanplaythrough = function() {
-  sp.onaudioprocess = processSamples;
-};
 </script>
 </pre>
 </body>
 </html>
--- a/dom/svg/SVGGeometryElement.cpp
+++ b/dom/svg/SVGGeometryElement.cpp
@@ -10,16 +10,17 @@
 #include "gfxPlatform.h"
 #include "nsCOMPtr.h"
 #include "nsSVGUtils.h"
 #include "SVGAnimatedLength.h"
 #include "SVGCircleElement.h"
 #include "SVGEllipseElement.h"
 #include "SVGGeometryProperty.h"
 #include "SVGRectElement.h"
+#include "mozilla/dom/DOMPointBinding.h"
 #include "mozilla/dom/SVGLengthBinding.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/SVGContentUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::dom;
@@ -109,16 +110,28 @@ already_AddRefed<Path> SVGGeometryElemen
 
 already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForMeasuring() {
   RefPtr<DrawTarget> drawTarget =
       gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
   FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule();
   return GetOrBuildPath(drawTarget, fillRule);
 }
 
+// This helper is currently identical to GetOrBuildPathForMeasuring.
+// We keep it a separate method because for length measuring purpose,
+// fillRule isn't really needed. Derived class (e.g. SVGPathElement)
+// may override GetOrBuildPathForMeasuring() to ignore fillRule. And
+// GetOrBuildPathForMeasuring() itself may be modified in the future.
+already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForHitTest() {
+  RefPtr<DrawTarget> drawTarget =
+      gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+  FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule();
+  return GetOrBuildPath(drawTarget, fillRule);
+}
+
 bool SVGGeometryElement::IsGeometryChangedViaCSS(
     ComputedStyle const& aNewStyle, ComputedStyle const& aOldStyle) const {
   if (IsSVGElement(nsGkAtoms::rect)) {
     return SVGRectElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle);
   }
 
   if (IsSVGElement(nsGkAtoms::circle)) {
     return SVGCircleElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle);
@@ -148,16 +161,64 @@ FillRule SVGGeometryElement::GetFillRule
 
   if (!res) {
     NS_WARNING("Couldn't get ComputedStyle for content in GetFillRule");
   }
 
   return fillRule;
 }
 
+static Point GetPointFrom(const DOMPointInit& aPoint) {
+  return Point(aPoint.mX, aPoint.mY);
+}
+
+bool SVGGeometryElement::IsPointInFill(const DOMPointInit& aPoint) {
+  auto point = GetPointFrom(aPoint);
+
+  RefPtr<Path> path = GetOrBuildPathForHitTest();
+  if (!path) {
+    return false;
+  }
+
+  return path->ContainsPoint(point, {});
+}
+
+bool SVGGeometryElement::IsPointInStroke(const DOMPointInit& aPoint) {
+  auto point = GetPointFrom(aPoint);
+
+  RefPtr<Path> path = GetOrBuildPathForHitTest();
+  if (!path) {
+    return false;
+  }
+
+  bool res = false;
+  SVGGeometryProperty::DoForComputedStyle(this, [&](const ComputedStyle* s) {
+    // Per spec, we should take vector-effect into account.
+    if (s->StyleSVGReset()->HasNonScalingStroke()) {
+      auto mat = SVGContentUtils::GetCTM(this, true);
+      if (mat.HasNonTranslation()) {
+        // We have non-scaling-stroke as well as a non-translation transform.
+        // We should transform the path first then apply the stroke on the
+        // transformed path to preserve the stroke-width.
+        RefPtr<PathBuilder> builder = path->TransformedCopyToBuilder(mat);
+
+        path = builder->Finish();
+        point = mat.TransformPoint(point);
+      }
+    }
+
+    SVGContentUtils::AutoStrokeOptions strokeOptions;
+    SVGContentUtils::GetStrokeOptions(&strokeOptions, this, s, nullptr);
+
+    res = path->StrokeContainsPoint(strokeOptions, point, {});
+  });
+
+  return res;
+}
+
 float SVGGeometryElement::GetTotalLength() {
   RefPtr<Path> flat = GetOrBuildPathForMeasuring();
   return flat ? flat->ComputeLength() : 0.f;
 }
 
 already_AddRefed<nsISVGPoint> SVGGeometryElement::GetPointAtLength(
     float distance, ErrorResult& rv) {
   RefPtr<Path> path = GetOrBuildPathForMeasuring();
--- a/dom/svg/SVGGeometryElement.h
+++ b/dom/svg/SVGGeometryElement.h
@@ -211,25 +211,30 @@ class SVGGeometryElement : public SVGGeo
    * estimated length (as provided by the element's 'pathLength' attribute).
    * This is used to scale stroke dashing, and to scale offsets along a
    * textPath.
    */
   float GetPathLengthScale(PathLengthScaleForType aFor);
 
   // WebIDL
   already_AddRefed<DOMSVGAnimatedNumber> PathLength();
+  bool IsPointInFill(const DOMPointInit& aPoint);
+  bool IsPointInStroke(const DOMPointInit& aPoint);
   float GetTotalLength();
   already_AddRefed<nsISVGPoint> GetPointAtLength(float distance,
                                                  ErrorResult& rv);
 
  protected:
   // SVGElement method
   virtual NumberAttributesInfo GetNumberInfo() override;
 
   SVGAnimatedNumber mPathLength;
   static NumberInfo sNumberInfo;
   mutable RefPtr<Path> mCachedPath;
+
+ private:
+  already_AddRefed<Path> GetOrBuildPathForHitTest();
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_SVGGeometryElement_h
--- a/dom/tests/mochitest/general/test_DOMMatrix.html
+++ b/dom/tests/mochitest/general/test_DOMMatrix.html
@@ -457,17 +457,17 @@ function testSkewYInPlace()
 
 function testCreateMatrix3D()
 {
   var m = new DOMMatrix([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
 
   // Should be initialised to identity
   cmpMatrix(m, [1, 0, 0, 1, 0, 0],
             "DOMMatrix should produce identity matrix");
-  is(m.is2D, true, "should not produce 3d matrix");
+  is(m.is2D, false, "should produce 3d matrix");
 
   m = new DOMMatrix([1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]);
 
   // Should be initialised to identity
   cmpMatrix(m, [1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
             "DOMMatrix should produce identity matrix");
   is(m.is2D, false, "should produce 3d matrix");
 }
@@ -702,26 +702,26 @@ function testParsing()
   ok(CompareDOMMatrix(m2, m), "string parsing didn't match");
 
   m = new DOMMatrix();
   m.setMatrixValue("translate(10px, 20px) scale(.5, 2) rotate(45deg)");
   ok(CompareDOMMatrix(m2, m), "string parsing didn't match");
 }
 
 
-function testStringify() {
-    var m = new DOMMatrix();
-    var s = "" + m;
-    ok(s == "matrix" + formatMatrix(m), "stringifier 1 produced wrong result: " + s);
-    m.a = 100;
-    s = "" + m;
-    ok(s == "matrix" + formatMatrix(m), "stringifier 2 produced wrong result: " + s);
-    m.m43 = 200;
-    s = "" + m;
-    ok(s == "matrix3d" + formatMatrix(m), "stringifier 3 produced wrong result:" + s);
+function testStringify() {
+    var m = new DOMMatrix();
+    var s = "" + m;
+    ok(s == "matrix" + formatMatrix(m), "stringifier 1 produced wrong result: " + s);
+    m.a = 100;
+    s = "" + m;
+    ok(s == "matrix" + formatMatrix(m), "stringifier 2 produced wrong result: " + s);
+    m.m43 = 200;
+    s = "" + m;
+    ok(s == "matrix3d" + formatMatrix(m), "stringifier 3 produced wrong result:" + s);
 }
 
 window.addEventListener("load", main);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/webidl/SVGGeometryElement.webidl
+++ b/dom/webidl/SVGGeometryElement.webidl
@@ -9,12 +9,15 @@
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 interface SVGGeometryElement : SVGGraphicsElement {
   [SameObject]
   readonly attribute SVGAnimatedNumber pathLength;
 
+  boolean isPointInFill(optional DOMPointInit point);
+  boolean isPointInStroke(optional DOMPointInit point);
+
   float getTotalLength();
   [NewObject, Throws]
   SVGPoint getPointAtLength(float distance);
 };
--- a/gfx/ipc/CompositorSession.cpp
+++ b/gfx/ipc/CompositorSession.cpp
@@ -6,29 +6,45 @@
 #include "CompositorSession.h"
 #include "base/process_util.h"
 #include "GPUChild.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/gfx/GPUProcessHost.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 
+#if defined(MOZ_WIDGET_ANDROID)
+#  include "mozilla/widget/nsWindow.h"
+#endif  // defined(MOZ_WIDGET_ANDROID)
+
 namespace mozilla {
 namespace layers {
 
 using namespace gfx;
 using namespace widget;
 
-CompositorSession::CompositorSession(CompositorWidgetDelegate* aDelegate,
+CompositorSession::CompositorSession(nsBaseWidget* aWidget,
+                                     CompositorWidgetDelegate* aDelegate,
                                      CompositorBridgeChild* aChild,
                                      const LayersId& aRootLayerTreeId)
-    : mCompositorWidgetDelegate(aDelegate),
+    : mWidget(aWidget),
+      mCompositorWidgetDelegate(aDelegate),
       mCompositorBridgeChild(aChild),
       mRootLayerTreeId(aRootLayerTreeId) {}
 
 CompositorSession::~CompositorSession() {}
 
 CompositorBridgeChild* CompositorSession::GetCompositorBridgeChild() {
   return mCompositorBridgeChild;
 }
 
+#if defined(MOZ_WIDGET_ANDROID)
+void CompositorSession::NotifyDisablingWebRender() {
+  if (!mWidget) {
+    return;
+  }
+  nsWindow* window = static_cast<nsWindow*>(mWidget);
+  window->NotifyDisablingWebRender();
+}
+#endif  // defined(MOZ_WIDGET_ANDROID)
+
 }  // namespace layers
 }  // namespace mozilla
--- a/gfx/ipc/CompositorSession.h
+++ b/gfx/ipc/CompositorSession.h
@@ -9,17 +9,17 @@
 #include "base/basictypes.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/layers/CompositorTypes.h"
 #include "nsISupportsImpl.h"
 #if defined(MOZ_WIDGET_ANDROID)
 #  include "mozilla/layers/UiCompositorControllerChild.h"
 #endif  // defined(MOZ_WIDGET_ANDROID)
 
-class nsIWidget;
+class nsBaseWidget;
 
 namespace mozilla {
 namespace widget {
 class CompositorWidget;
 class CompositorWidgetDelegate;
 }  // namespace widget
 namespace gfx {
 class GPUProcessHost;
@@ -75,24 +75,27 @@ class CompositorSession {
   void SetUiCompositorControllerChild(
       RefPtr<UiCompositorControllerChild> aUiController) {
     mUiCompositorControllerChild = aUiController;
   }
 
   RefPtr<UiCompositorControllerChild> GetUiCompositorControllerChild() {
     return mUiCompositorControllerChild;
   }
+
+  void NotifyDisablingWebRender();
 #endif  // defined(MOZ_WIDGET_ANDROID)
  protected:
-  CompositorSession(CompositorWidgetDelegate* aDelegate,
+  CompositorSession(nsBaseWidget* aWidget, CompositorWidgetDelegate* aDelegate,
                     CompositorBridgeChild* aChild,
                     const LayersId& aRootLayerTreeId);
   virtual ~CompositorSession();
 
  protected:
+  nsBaseWidget* mWidget;
   CompositorWidgetDelegate* mCompositorWidgetDelegate;
   RefPtr<CompositorBridgeChild> mCompositorBridgeChild;
   LayersId mRootLayerTreeId;
 #if defined(MOZ_WIDGET_ANDROID)
   RefPtr<UiCompositorControllerChild> mUiCompositorControllerChild;
 #endif  // defined(MOZ_WIDGET_ANDROID)
  private:
   DISALLOW_COPY_AND_ASSIGN(CompositorSession);
--- a/gfx/ipc/GPUProcessManager.cpp
+++ b/gfx/ipc/GPUProcessManager.cpp
@@ -450,16 +450,25 @@ void GPUProcessManager::DisableWebRender
         .ForceDisable(gfx::FeatureStatus::Unavailable,
                       "Failed to render WebRender",
                       NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBRENDER_RENDER"));
   } else {
     MOZ_ASSERT_UNREACHABLE("Invalid value");
   }
   gfx::gfxVars::SetUseWebRender(false);
 
+#if defined(MOZ_WIDGET_ANDROID)
+  // If aError is not wr::WebRenderError::INITIALIZE, nsWindow does not
+  // re-create LayerManager. Needs to trigger re-creating LayerManager on
+  // android
+  if (aError != wr::WebRenderError::INITIALIZE) {
+    NotifyDisablingWebRender();
+  }
+#endif
+
   if (mProcess) {
     OnRemoteProcessDeviceReset(mProcess);
   } else {
     OnInProcessDeviceReset();
   }
 }
 
 void GPUProcessManager::NotifyWebRenderError(wr::WebRenderError aError) {
@@ -636,16 +645,28 @@ void GPUProcessManager::RebuildInProcess
 
   // Notify each widget that we have lost the GPU process. This will ensure
   // that each widget destroys its layer manager and CompositorBridgeChild.
   for (const auto& session : sessions) {
     session->NotifySessionLost();
   }
 }
 
+void GPUProcessManager::NotifyDisablingWebRender() {
+#if defined(MOZ_WIDGET_ANDROID)
+  for (const auto& session : mRemoteSessions) {
+    session->NotifyDisablingWebRender();
+  }
+
+  for (const auto& session : mInProcessSessions) {
+    session->NotifyDisablingWebRender();
+  }
+#endif
+}
+
 void GPUProcessManager::NotifyRemoteActorDestroyed(
     const uint64_t& aProcessToken) {
   if (!NS_IsMainThread()) {
     RefPtr<Runnable> task = mTaskFactory.NewRunnableMethod(
         &GPUProcessManager::NotifyRemoteActorDestroyed, aProcessToken);
     NS_DispatchToMainThread(task.forget());
     return;
   }
--- a/gfx/ipc/GPUProcessManager.h
+++ b/gfx/ipc/GPUProcessManager.h
@@ -205,16 +205,18 @@ class GPUProcessManager final : public G
   // Called from InProcessCompositorSession. We track in process sessino so we
   // can notify their owning widgets that the session must be restarted
   void RegisterInProcessSession(InProcessCompositorSession* aSession);
   void UnregisterInProcessSession(InProcessCompositorSession* aSession);
 
   void RebuildRemoteSessions();
   void RebuildInProcessSessions();
 
+  void NotifyDisablingWebRender();
+
   void FallbackToSoftware(const char* aMessage);
 
  private:
   GPUProcessManager();
 
   // Permanently disable the GPU process and record a message why.
   void DisableGPUProcess(const char* aMessage);
 
--- a/gfx/ipc/InProcessCompositorSession.cpp
+++ b/gfx/ipc/InProcessCompositorSession.cpp
@@ -8,23 +8,22 @@
 
 #include "mozilla/layers/IAPZCTreeManager.h"
 #include "nsBaseWidget.h"
 
 namespace mozilla {
 namespace layers {
 
 InProcessCompositorSession::InProcessCompositorSession(
-    widget::CompositorWidget* aWidget, nsBaseWidget* baseWidget,
+    nsBaseWidget* aWidget, widget::CompositorWidget* aCompositorWidget,
     CompositorBridgeChild* aChild, CompositorBridgeParent* aParent)
-    : CompositorSession(aWidget->AsDelegate(), aChild,
+    : CompositorSession(aWidget, aCompositorWidget->AsDelegate(), aChild,
                         aParent->RootLayerTreeId()),
-      mWidget(baseWidget),
       mCompositorBridgeParent(aParent),
-      mCompositorWidget(aWidget) {
+      mCompositorWidget(aCompositorWidget) {
   GPUProcessManager::Get()->RegisterInProcessSession(this);
 }
 
 /* static */
 RefPtr<InProcessCompositorSession> InProcessCompositorSession::Create(
     nsBaseWidget* aWidget, LayerManager* aLayerManager,
     const LayersId& aRootLayerTreeId, CSSToLayoutDeviceScale aScale,
     const CompositorOptions& aOptions, bool aUseExternalSurfaceSize,
@@ -40,17 +39,17 @@ RefPtr<InProcessCompositorSession> InPro
   MOZ_ASSERT(parent);
   parent->InitSameProcess(widget, aRootLayerTreeId);
 
   RefPtr<CompositorBridgeChild> child =
       CompositorManagerChild::CreateSameProcessWidgetCompositorBridge(
           aLayerManager, aNamespace);
   MOZ_ASSERT(child);
 
-  return new InProcessCompositorSession(widget, aWidget, child, parent);
+  return new InProcessCompositorSession(aWidget, widget, child, parent);
 }
 
 void InProcessCompositorSession::NotifySessionLost() {
   mWidget->NotifyCompositorSessionLost(this);
 }
 
 CompositorBridgeParent* InProcessCompositorSession::GetInProcessBridge() const {
   return mCompositorBridgeParent;
--- a/gfx/ipc/InProcessCompositorSession.h
+++ b/gfx/ipc/InProcessCompositorSession.h
@@ -29,23 +29,22 @@ class InProcessCompositorSession final :
   void SetContentController(GeckoContentController* aController) override;
   nsIWidget* GetWidget() const;
   RefPtr<IAPZCTreeManager> GetAPZCTreeManager() const override;
   void Shutdown() override;
 
   void NotifySessionLost();
 
  private:
-  InProcessCompositorSession(widget::CompositorWidget* aWidget,
-                             nsBaseWidget* baseWidget,
+  InProcessCompositorSession(nsBaseWidget* aWidget,
+                             widget::CompositorWidget* aCompositorWidget,
                              CompositorBridgeChild* aChild,
                              CompositorBridgeParent* aParent);
 
  private:
-  nsBaseWidget* mWidget;
   RefPtr<CompositorBridgeParent> mCompositorBridgeParent;
   RefPtr<CompositorWidget> mCompositorWidget;
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif  // _include_mozilla_gfx_ipc_InProcessCompositorSession_h_
--- a/gfx/ipc/RemoteCompositorSession.cpp
+++ b/gfx/ipc/RemoteCompositorSession.cpp
@@ -19,18 +19,17 @@ namespace layers {
 
 using namespace gfx;
 using namespace widget;
 
 RemoteCompositorSession::RemoteCompositorSession(
     nsBaseWidget* aWidget, CompositorBridgeChild* aChild,
     CompositorWidgetDelegate* aWidgetDelegate, APZCTreeManagerChild* aAPZ,
     const LayersId& aRootLayerTreeId)
-    : CompositorSession(aWidgetDelegate, aChild, aRootLayerTreeId),
-      mWidget(aWidget),
+    : CompositorSession(aWidget, aWidgetDelegate, aChild, aRootLayerTreeId),
       mAPZ(aAPZ) {
   MOZ_ASSERT(!gfxPlatform::IsHeadless());
   GPUProcessManager::Get()->RegisterRemoteProcessSession(this);
   if (mAPZ) {
     mAPZ->SetCompositorSession(this);
   }
 }
 
--- a/gfx/ipc/RemoteCompositorSession.h
+++ b/gfx/ipc/RemoteCompositorSession.h
@@ -26,17 +26,16 @@ class RemoteCompositorSession final : pu
   GeckoContentController* GetContentController();
   nsIWidget* GetWidget() const;
   RefPtr<IAPZCTreeManager> GetAPZCTreeManager() const override;
   void Shutdown() override;
 
   void NotifySessionLost();
 
  private:
-  nsBaseWidget* mWidget;
   RefPtr<APZCTreeManagerChild> mAPZ;
   RefPtr<GeckoContentController> mContentController;
 };
 
 }  // namespace layers
 }  // namespace mozilla
 
 #endif  // include_mozilla_gfx_ipc_RemoteCompositorSession_h
--- a/gfx/webrender_bindings/RenderAndroidSurfaceTextureHostOGL.cpp
+++ b/gfx/webrender_bindings/RenderAndroidSurfaceTextureHostOGL.cpp
@@ -98,16 +98,22 @@ wr::WrExternalImage RenderAndroidSurface
 
 void RenderAndroidSurfaceTextureHostOGL::Unlock() {}
 
 void RenderAndroidSurfaceTextureHostOGL::DeleteTextureHandle() {
   NotifyNotUsed();
 }
 
 bool RenderAndroidSurfaceTextureHostOGL::EnsureAttachedToGLContext() {
+  // During handling WebRenderError, GeckoSurfaceTexture should not be attached
+  // to GLContext.
+  if (RenderThread::Get()->IsHandlingWebRenderError()) {
+    return false;
+  }
+
   if (mAttachedToGLContext) {
     return true;
   }
 
   if (!mGL) {
     mGL = RenderThread::Get()->SharedGL();
   }
 
@@ -192,35 +198,41 @@ void RenderAndroidSurfaceTextureHostOGL:
 
   if (mPrepareStatus == STATUS_MIGHT_BE_USED) {
     // This happens when SurfaceTexture of video is rendered on WebRender and
     // SurfaceTexture is not bounded to gl context yet.
     // It is necessary to handle a case that SurfaceTexture is not rendered on
     // WebRender, instead rendered to WebGL.
     // It is not a good way. But it is same to Compositor rendering.
     MOZ_ASSERT(!mSurfTex || !mSurfTex->IsSingleBuffer());
-    EnsureAttachedToGLContext();
+    if (!EnsureAttachedToGLContext()) {
+      return;
+    }
     mPrepareStatus = STATUS_PREPARE_NEEDED;
   }
 }
 
 void RenderAndroidSurfaceTextureHostOGL::NotifyNotUsed() {
   MOZ_ASSERT(RenderThread::IsInRenderThread());
 
   if (mSurfTex && mSurfTex->IsSingleBuffer() &&
       mPrepareStatus == STATUS_PREPARED) {
-    EnsureAttachedToGLContext();
+    if (!EnsureAttachedToGLContext()) {
+      return;
+    }
     // Release SurfaceTexture's buffer to client side.
     mGL->MakeCurrent();
     mSurfTex->ReleaseTexImage();
   } else if (mSurfTex && mPrepareStatus == STATUS_PREPARE_NEEDED) {
     // This could happen when video frame was skipped. UpdateTexImage() neeeds
     // to be called for adjusting SurfaceTexture's buffer status.
     MOZ_ASSERT(!mSurfTex->IsSingleBuffer());
-    EnsureAttachedToGLContext();
+    if (!EnsureAttachedToGLContext()) {
+      return;
+    }
     mSurfTex->UpdateTexImage();
   }
 
   mPrepareStatus = STATUS_NONE;
 }
 
 }  // namespace wr
 }  // namespace mozilla
--- a/gfx/webrender_bindings/RenderCompositorEGL.cpp
+++ b/gfx/webrender_bindings/RenderCompositorEGL.cpp
@@ -50,17 +50,23 @@ EGLSurface RenderCompositorEGL::CreateEG
   }
   return surface;
 }
 
 RenderCompositorEGL::RenderCompositorEGL(
     RefPtr<widget::CompositorWidget> aWidget)
     : RenderCompositor(std::move(aWidget)), mEGLSurface(EGL_NO_SURFACE) {}
 
-RenderCompositorEGL::~RenderCompositorEGL() { DestroyEGLSurface(); }
+RenderCompositorEGL::~RenderCompositorEGL() {
+#ifdef MOZ_WIDGET_ANDROID
+  java::GeckoSurfaceTexture::DestroyUnused((int64_t)gl());
+  java::GeckoSurfaceTexture::DetachAllFromGLContext((int64_t)gl());
+#endif
+  DestroyEGLSurface();
+}
 
 bool RenderCompositorEGL::BeginFrame() {
 #ifdef MOZ_WAYLAND
   bool newSurface =
       mWidget->AsX11() && mWidget->AsX11()->WaylandRequestsUpdatingEGLSurface();
   if (newSurface) {
     // Destroy EGLSurface if it exists and create a new one. We will set the
     // swap interval after MakeCurrent() has been called.
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -8,31 +8,33 @@
 #include "GeckoProfiler.h"
 #include "RenderThread.h"
 #include "nsThreadUtils.h"
 #include "mtransport/runnable_utils.h"
 #include "mozilla/layers/AsyncImagePipelineManager.h"
 #include "mozilla/gfx/GPUParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorManagerParent.h"
 #include "mozilla/layers/WebRenderBridgeParent.h"
 #include "mozilla/layers/SharedSurfacesParent.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/webrender/RendererOGL.h"
 #include "mozilla/webrender/RenderTextureHost.h"
 #include "mozilla/widget/CompositorWidget.h"
 
 #ifdef XP_WIN
 #  include "GLLibraryEGL.h"
 #  include "mozilla/widget/WinCompositorWindowThread.h"
 #endif
 
 #ifdef MOZ_WIDGET_ANDROID
 #  include "GLLibraryEGL.h"
+#  include "GeneratedJNIWrappers.h"
 #endif
 
 using namespace mozilla;
 
 static already_AddRefed<gl::GLContext> CreateGLContext();
 
 MOZ_DEFINE_MALLOC_SIZE_OF(WebRenderRendererMallocSizeOf)
 
@@ -41,17 +43,18 @@ namespace wr {
 
 static StaticRefPtr<RenderThread> sRenderThread;
 
 RenderThread::RenderThread(base::Thread* aThread)
     : mThread(aThread),
       mWindowInfos("RenderThread.mWindowInfos"),
       mRenderTextureMapLock("RenderThread.mRenderTextureMapLock"),
       mHasShutdown(false),
-      mHandlingDeviceReset(false) {}
+      mHandlingDeviceReset(false),
+      mHandlingWebRenderError(false) {}
 
 RenderThread::~RenderThread() {
   MOZ_ASSERT(mRenderTexturesDeferred.empty());
   delete mThread;
 }
 
 // static
 RenderThread* RenderThread::Get() { return sRenderThread; }
@@ -803,16 +806,44 @@ void RenderThread::SimulateDeviceReset()
   } else {
     // When this function is called GPUProcessManager::SimulateDeviceReset()
     // already triggers destroying all CompositorSessions before re-creating
     // them.
     HandleDeviceReset("SimulateDeviceReset", /* aNotify */ false);
   }
 }
 
+static void DoNotifyWebRenderError(WebRenderError aError) {
+  layers::CompositorManagerParent::NotifyWebRenderError(aError);
+}
+
+void RenderThread::HandleWebRenderError(WebRenderError aError) {
+  if (mHandlingWebRenderError) {
+    return;
+  }
+
+  layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableFunction(
+      "DoNotifyWebRenderErrorRunnable", &DoNotifyWebRenderError, aError));
+  {
+    MutexAutoLock lock(mRenderTextureMapLock);
+    mRenderTexturesDeferred.clear();
+    for (const auto& entry : mRenderTextures) {
+      entry.second->ClearCachedResources();
+    }
+  }
+  mHandlingWebRenderError = true;
+  // WebRender is going to be disabled by
+  // GPUProcessManager::NotifyWebRenderError()
+}
+
+bool RenderThread::IsHandlingWebRenderError() {
+  MOZ_ASSERT(IsInRenderThread());
+  return mHandlingWebRenderError;
+}
+
 gl::GLContext* RenderThread::SharedGL() {
   MOZ_ASSERT(IsInRenderThread());
   if (!mSharedGL) {
     mSharedGL = CreateGLContext();
     mShaders = nullptr;
   }
   if (mSharedGL && !mShaders) {
     mShaders = MakeUnique<WebRenderShaders>(mSharedGL, mProgramCache.get());
--- a/gfx/webrender_bindings/RenderThread.h
+++ b/gfx/webrender_bindings/RenderThread.h
@@ -250,16 +250,21 @@ class RenderThread final {
 
   /// Can only be called from the render thread.
   void HandleDeviceReset(const char* aWhere, bool aNotify);
   /// Can only be called from the render thread.
   bool IsHandlingDeviceReset();
   /// Can be called from any thread.
   void SimulateDeviceReset();
 
+  /// Can only be called from the render thread.
+  void HandleWebRenderError(WebRenderError aError);
+  /// Can only be called from the render thread.
+  bool IsHandlingWebRenderError();
+
   size_t RendererCount();
 
   void SetCompositionRecorderForWindow(
       wr::WindowId aWindowId,
       RefPtr<layers::WebRenderCompositionRecorder>&& aCompositionRecorder);
 
  private:
   explicit RenderThread(base::Thread* aThread);
@@ -309,14 +314,15 @@ class RenderThread final {
   // Used to remove all RenderTextureHost that are going to be removed by
   // a deferred callback and remove them right away without waiting for the
   // callback. On device reset we have to remove all GL related resources right
   // away.
   std::list<RefPtr<RenderTextureHost>> mRenderTexturesDeferred;
   bool mHasShutdown;
 
   bool mHandlingDeviceReset;
+  bool mHandlingWebRenderError;
 };
 
 }  // namespace wr
 }  // namespace mozilla
 
 #endif
--- a/gfx/webrender_bindings/RendererOGL.cpp
+++ b/gfx/webrender_bindings/RendererOGL.cpp
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "RendererOGL.h"
 #include "GLContext.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/Types.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
-#include "mozilla/layers/CompositorManagerParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/webrender/RenderCompositor.h"
 #include "mozilla/webrender/RenderTextureHost.h"
 #include "mozilla/widget/CompositorWidget.h"
 
 namespace mozilla {
 namespace wr {
@@ -115,17 +114,18 @@ bool RendererOGL::UpdateAndRender(const 
   }
 
   wr_renderer_update(mRenderer);
 
   auto size = mCompositor->GetBufferSize();
 
   if (!wr_renderer_render(mRenderer, size.width, size.height, aHadSlowFrame,
                           aOutStats)) {
-    NotifyWebRenderError(WebRenderError::RENDER);
+    RenderThread::Get()->HandleWebRenderError(WebRenderError::RENDER);
+    return false;
   }
 
   if (aReadbackBuffer.isSome()) {
     MOZ_ASSERT(aReadbackSize.isSome());
     MOZ_ASSERT(aReadbackFormat.isSome());
     wr_renderer_readback(mRenderer, aReadbackSize.ref().width,
                          aReadbackSize.ref().height, aReadbackFormat.ref(),
                          &aReadbackBuffer.ref()[0],
@@ -211,19 +211,10 @@ void RendererOGL::AccumulateMemoryReport
   // Assume BGRA8 for the format since it's not exposed anywhere,
   // and all compositor backends should be using that.
   uintptr_t swapChainSize = size.width * size.height *
                             BytesPerPixel(SurfaceFormat::B8G8R8A8) *
                             (mCompositor->UseTripleBuffering() ? 3 : 2);
   aReport->swap_chain += swapChainSize;
 }
 
-static void DoNotifyWebRenderError(WebRenderError aError) {
-  layers::CompositorManagerParent::NotifyWebRenderError(aError);
-}
-
-void RendererOGL::NotifyWebRenderError(WebRenderError aError) {
-  layers::CompositorThreadHolder::Loop()->PostTask(NewRunnableFunction(
-      "DoNotifyWebRenderErrorRunnable", &DoNotifyWebRenderError, aError));
-}
-
 }  // namespace wr
 }  // namespace mozilla
--- a/gfx/webrender_bindings/RendererOGL.h
+++ b/gfx/webrender_bindings/RendererOGL.h
@@ -99,18 +99,16 @@ class RendererOGL {
 
   void AccumulateMemoryReport(MemoryReport* aReport);
 
   wr::Renderer* GetRenderer() { return mRenderer; }
 
   gl::GLContext* gl() const;
 
  protected:
-  void NotifyWebRenderError(WebRenderError aError);
-
   RefPtr<RenderThread> mThread;
   UniquePtr<RenderCompositor> mCompositor;
   wr::Renderer* mRenderer;
   layers::CompositorBridgeParent* mBridge;
   wr::WindowId mWindowId;
   TimeStamp mFrameStartTime;
 
   RendererScreenshotGrabber mScreenshotGrabber;
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -242,16 +242,18 @@ struct Symbol {
  * A GC pointer, tagged with the trace kind.
  *
  * In general, a GC pointer should be stored with an exact type. This class
  * is for use when that is not possible because a single pointer must point
  * to several kinds of GC thing.
  */
 class JS_FRIEND_API GCCellPtr {
  public:
+  GCCellPtr() : GCCellPtr(nullptr) {}
+
   // Construction from a void* and trace kind.
   GCCellPtr(void* gcthing, JS::TraceKind traceKind)
       : ptr(checkedCast(gcthing, traceKind)) {}
 
   // Automatically construct a null GCCellPtr from nullptr.
   MOZ_IMPLICIT GCCellPtr(decltype(nullptr))
       : ptr(checkedCast(nullptr, JS::TraceKind::Null)) {}
 
--- a/js/src/builtin/Array.cpp
+++ b/js/src/builtin/Array.cpp
@@ -3760,20 +3760,20 @@ static const JSFunctionSpec array_method
 
     /* ES6 additions */
     JS_SELF_HOSTED_FN("find", "ArrayFind", 1, 0),
     JS_SELF_HOSTED_FN("findIndex", "ArrayFindIndex", 1, 0),
     JS_SELF_HOSTED_FN("copyWithin", "ArrayCopyWithin", 3, 0),
 
     JS_SELF_HOSTED_FN("fill", "ArrayFill", 3, 0),
 
-    JS_SELF_HOSTED_SYM_FN(iterator, "ArrayValues", 0, 0),
+    JS_SELF_HOSTED_SYM_FN(iterator, "$ArrayValues", 0, 0),
     JS_SELF_HOSTED_FN("entries", "ArrayEntries", 0, 0),
     JS_SELF_HOSTED_FN("keys", "ArrayKeys", 0, 0),
-    JS_SELF_HOSTED_FN("values", "ArrayValues", 0, 0),
+    JS_SELF_HOSTED_FN("values", "$ArrayValues", 0, 0),
 
     /* ES7 additions */
     JS_SELF_HOSTED_FN("includes", "ArrayIncludes", 2, 0),
 
     /* Future additions */
     JS_SELF_HOSTED_FN("flatMap", "ArrayFlatMap", 1, 0),
     JS_SELF_HOSTED_FN("flat", "ArrayFlat", 0, 0),
 
@@ -3801,17 +3801,17 @@ static const JSFunctionSpec array_static
     JS_SELF_HOSTED_FN("splice", "ArrayStaticSplice", 3, 0),
     JS_SELF_HOSTED_FN("slice", "ArrayStaticSlice", 3, 0),
     JS_SELF_HOSTED_FN("from", "ArrayFrom", 3, 0),
     JS_FN("of", array_of, 0, 0),
 
     JS_FS_END};
 
 const JSPropertySpec array_static_props[] = {
-    JS_SELF_HOSTED_SYM_GET(species, "ArraySpecies", 0), JS_PS_END};
+    JS_SELF_HOSTED_SYM_GET(species, "$ArraySpecies", 0), JS_PS_END};
 
 static inline bool ArrayConstructorImpl(JSContext* cx, CallArgs& args,
                                         bool isConstructor) {
   RootedObject proto(cx);
   if (isConstructor) {
     if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Array, &proto)) {
       return false;
     }
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -784,20 +784,22 @@ function ArrayIteratorNext() {
     }
 
     // Step 12.
     assert(itemKind === ITEM_KIND_KEY, itemKind);
     result.value = index;
     return result;
 }
 
-function ArrayValues() {
+// Uncloned functions with `$` prefix are allocated as extended function
+// to store the original name in `_SetCanonicalName`.
+function $ArrayValues() {
     return CreateArrayIterator(this, ITEM_KIND_VALUE);
 }
-_SetCanonicalName(ArrayValues, "values");
+_SetCanonicalName($ArrayValues, "values");
 
 function ArrayEntries() {
     return CreateArrayIterator(this, ITEM_KIND_KEY_AND_VALUE);
 }
 
 function ArrayKeys() {
     return CreateArrayIterator(this, ITEM_KIND_KEY);
 }
@@ -968,21 +970,21 @@ function ArrayToLocaleString(locales, op
         }
     }
 
     // Step 10.
     return R;
 }
 
 // ES 2016 draft Mar 25, 2016 22.1.2.5.
-function ArraySpecies() {
+function $ArraySpecies() {
     // Step 1.
     return this;
 }
-_SetCanonicalName(ArraySpecies, "get [Symbol.species]");
+_SetCanonicalName($ArraySpecies, "get [Symbol.species]");
 
 // ES 2016 draft Mar 25, 2016 9.4.2.3.
 function ArraySpeciesCreate(originalArray, length) {
     // Step 1.
     assert(typeof length == "number", "length should be a number");
     assert(length >= 0, "length should be a non-negative number");
 
     // Step 2.
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -38,20 +38,33 @@ static void AssertInnerizedEnvironmentCh
   RootedObject obj(cx);
   for (obj = &env; obj; obj = obj->enclosingEnvironment()) {
     MOZ_ASSERT(!IsWindowProxy(obj));
   }
 #endif
 }
 
 static bool IsEvalCacheCandidate(JSScript* script) {
+  if (!script->isDirectEvalInFunction()) {
+    return false;
+  }
+
   // Make sure there are no inner objects which might use the wrong parent
   // and/or call scope by reusing the previous eval's script.
-  return script->isDirectEvalInFunction() && !script->hasSingletons() &&
-         !script->hasObjects();
+  if (script->hasSingletons()) {
+    return false;
+  }
+
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (gcThing.is<JSObject>()) {
+      return false;
+    }
+  }
+
+  return true;
 }
 
 /* static */
 HashNumber EvalCacheHashPolicy::hash(const EvalCacheLookup& l) {
   AutoCheckCannotGC nogc;
   uint32_t hash = l.str->hasLatin1Chars()
                       ? HashString(l.str->latin1Chars(nogc), l.str->length())
                       : HashString(l.str->twoByteChars(nogc), l.str->length());
--- a/js/src/builtin/Map.js
+++ b/js/src/builtin/Map.js
@@ -58,20 +58,22 @@ function MapForEach(callbackfn, thisArg 
         var value = mapIterationResultPair[1];
         mapIterationResultPair[0] = null;
         mapIterationResultPair[1] = null;
 
         callContentFunction(callbackfn, thisArg, value, key, M);
     }
 }
 
-function MapEntries() {
+// Uncloned functions with `$` prefix are allocated as extended function
+// to store the original name in `_SetCanonicalName`.
+function $MapEntries() {
     return callFunction(std_Map_iterator, this);
 }
-_SetCanonicalName(MapEntries, "entries");
+_SetCanonicalName($MapEntries, "entries");
 
 var iteratorTemp = { mapIterationResultPair: null };
 
 function MapIteratorNext() {
     // Step 1.
     var O = this;
 
     // Steps 2-3.
@@ -116,13 +118,13 @@ function MapIteratorNext() {
         retVal.done = false;
     }
 
     // Steps 7, 12.
     return retVal;
 }
 
 // ES6 final draft 23.1.2.2.
-function MapSpecies() {
+function $MapSpecies() {
     // Step 1.
     return this;
 }
-_SetCanonicalName(MapSpecies, "get [Symbol.species]");
+_SetCanonicalName($MapSpecies, "get [Symbol.species]");
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -406,21 +406,21 @@ const JSPropertySpec MapObject::properti
 
 const JSFunctionSpec MapObject::methods[] = {
     JS_FN("get", get, 1, 0), JS_FN("has", has, 1, 0), JS_FN("set", set, 2, 0),
     JS_FN("delete", delete_, 1, 0), JS_FN("keys", keys, 0, 0),
     JS_FN("values", values, 0, 0), JS_FN("clear", clear, 0, 0),
     JS_SELF_HOSTED_FN("forEach", "MapForEach", 2, 0),
     // MapEntries only exists to preseve the equal identity of
     // entries and @@iterator.
-    JS_SELF_HOSTED_FN("entries", "MapEntries", 0, 0),
-    JS_SELF_HOSTED_SYM_FN(iterator, "MapEntries", 0, 0), JS_FS_END};
+    JS_SELF_HOSTED_FN("entries", "$MapEntries", 0, 0),
+    JS_SELF_HOSTED_SYM_FN(iterator, "$MapEntries", 0, 0), JS_FS_END};
 
 const JSPropertySpec MapObject::staticProperties[] = {
-    JS_SELF_HOSTED_SYM_GET(species, "MapSpecies", 0), JS_PS_END};
+    JS_SELF_HOSTED_SYM_GET(species, "$MapSpecies", 0), JS_PS_END};
 
 template <class Range>
 static void TraceKey(Range& r, const HashableValue& key, JSTracer* trc) {
   HashableValue newKey = key.trace(trc);
 
   if (newKey.get() != key.get()) {
     // The hash function must take account of the fact that the thing being
     // hashed may have been moved by GC. This is only an issue for BigInt as for
@@ -1155,22 +1155,22 @@ const JSPropertySpec SetObject::properti
 
 const JSFunctionSpec SetObject::methods[] = {
     JS_FN("has", has, 1, 0), JS_FN("add", add, 1, 0),
     JS_FN("delete", delete_, 1, 0), JS_FN("entries", entries, 0, 0),
     JS_FN("clear", clear, 0, 0),
     JS_SELF_HOSTED_FN("forEach", "SetForEach", 2, 0),
     // SetValues only exists to preseve the equal identity of
     // values, keys and @@iterator.
-    JS_SELF_HOSTED_FN("values", "SetValues", 0, 0),
-    JS_SELF_HOSTED_FN("keys", "SetValues", 0, 0),
-    JS_SELF_HOSTED_SYM_FN(iterator, "SetValues", 0, 0), JS_FS_END};
+    JS_SELF_HOSTED_FN("values", "$SetValues", 0, 0),
+    JS_SELF_HOSTED_FN("keys", "$SetValues", 0, 0),
+    JS_SELF_HOSTED_SYM_FN(iterator, "$SetValues", 0, 0), JS_FS_END};
 
 const JSPropertySpec SetObject::staticProperties[] = {
-    JS_SELF_HOSTED_SYM_GET(species, "SetSpecies", 0), JS_PS_END};
+    JS_SELF_HOSTED_SYM_GET(species, "$SetSpecies", 0), JS_PS_END};
 
 bool SetObject::keys(JSContext* cx, HandleObject obj,
                      JS::MutableHandle<GCVector<JS::Value>> keys) {
   ValueSet* set = obj->as<SetObject>().getData();
   if (!set) {
     return false;
   }
 
--- a/js/src/builtin/Module.js
+++ b/js/src/builtin/Module.js
@@ -323,17 +323,16 @@ function ModuleInstantiate()
 
     // Step 7
     assert(stack.length === 0,
            "Stack should be empty after successful instantiation");
 
     // Step 8
     return undefined;
 }
-_SetCanonicalName(ModuleInstantiate, "ModuleInstantiate");
 
 // 15.2.1.16.4.1 InnerModuleInstantiation(module, stack, index)
 function InnerModuleInstantiation(module, stack, index)
 {
     // Step 1
     // TODO: Support module records other than source text module records.
 
     // Step 2
@@ -547,17 +546,16 @@ function ModuleEvaluate()
 
     assert(module.status === MODULE_STATUS_EVALUATED,
            "Bad module status after successful evaluation");
     assert(stack.length === 0,
            "Stack should be empty after successful evaluation");
 
     return undefined;
 }
-_SetCanonicalName(ModuleEvaluate, "ModuleEvaluate");
 
 // 15.2.1.16.5.1 InnerModuleEvaluation(module, stack, index)
 function InnerModuleEvaluation(module, stack, index)
 {
     // Step 1
     // TODO: Support module records other than source text module records.
 
     // Step 2
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -766,28 +766,28 @@ bool js::regexp_unicode(JSContext* cx, u
     return true;
   }
 
   // Steps 1-3.
   return CallNonGenericMethod<IsRegExpObject, regexp_unicode_impl>(cx, args);
 }
 
 const JSPropertySpec js::regexp_properties[] = {
-    JS_SELF_HOSTED_GET("flags", "RegExpFlagsGetter", 0),
+    JS_SELF_HOSTED_GET("flags", "$RegExpFlagsGetter", 0),
     JS_PSG("global", regexp_global, 0),
     JS_PSG("ignoreCase", regexp_ignoreCase, 0),
     JS_PSG("multiline", regexp_multiline, 0),
     JS_PSG("source", regexp_source, 0),
     JS_PSG("sticky", regexp_sticky, 0),
     JS_PSG("unicode", regexp_unicode, 0),
     JS_PS_END};
 
 const JSFunctionSpec js::regexp_methods[] = {
-    JS_SELF_HOSTED_FN(js_toSource_str, "RegExpToString", 0, 0),
-    JS_SELF_HOSTED_FN(js_toString_str, "RegExpToString", 0, 0),
+    JS_SELF_HOSTED_FN(js_toSource_str, "$RegExpToString", 0, 0),
+    JS_SELF_HOSTED_FN(js_toString_str, "$RegExpToString", 0, 0),
     JS_FN("compile", regexp_compile, 2, 0),
     JS_SELF_HOSTED_FN("exec", "RegExp_prototype_Exec", 1, 0),
     JS_SELF_HOSTED_FN("test", "RegExpTest", 1, 0),
     JS_SELF_HOSTED_SYM_FN(match, "RegExpMatch", 1, 0),
     JS_SELF_HOSTED_SYM_FN(matchAll, "RegExpMatchAll", 1, 0),
     JS_SELF_HOSTED_SYM_FN(replace, "RegExpReplace", 2, 0),
     JS_SELF_HOSTED_SYM_FN(search, "RegExpSearch", 1, 0),
     JS_SELF_HOSTED_SYM_FN(split, "RegExpSplit", 2, 0),
@@ -885,17 +885,17 @@ const JSPropertySpec js::regexp_static_p
     JS_PSG("$7", static_paren7_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
     JS_PSG("$8", static_paren8_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
     JS_PSG("$9", static_paren9_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
     JS_PSGS("$_", static_input_getter, static_input_setter, JSPROP_PERMANENT),
     JS_PSG("$&", static_lastMatch_getter, JSPROP_PERMANENT),
     JS_PSG("$+", static_lastParen_getter, JSPROP_PERMANENT),
     JS_PSG("$`", static_leftContext_getter, JSPROP_PERMANENT),
     JS_PSG("$'", static_rightContext_getter, JSPROP_PERMANENT),
-    JS_SELF_HOSTED_SYM_GET(species, "RegExpSpecies", 0),
+    JS_SELF_HOSTED_SYM_GET(species, "$RegExpSpecies", 0),
     JS_PS_END};
 
 template <typename CharT>
 static bool IsTrailSurrogateWithLeadSurrogateImpl(HandleLinearString input,
                                                   size_t index) {
   JS::AutoCheckCannotGC nogc;
   MOZ_ASSERT(index > 0 && index < input->length());
   const CharT* inputChars = input->chars<CharT>(nogc);
--- a/js/src/builtin/RegExp.js
+++ b/js/src/builtin/RegExp.js
@@ -1,14 +1,16 @@
 /* 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/. */
 
 // ES6 draft rev34 (2015/02/20) 21.2.5.3 get RegExp.prototype.flags
-function RegExpFlagsGetter() {
+// Uncloned functions with `$` prefix are allocated as extended function
+// to store the original name in `_SetCanonicalName`.
+function $RegExpFlagsGetter() {
     // Steps 1-2.
     var R = this;
     if (!IsObject(R))
         ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R);
 
     // Step 3.
     var result = "";
 
@@ -30,20 +32,20 @@ function RegExpFlagsGetter() {
 
     // Steps 16-18.
     if (R.sticky)
         result += "y";
 
     // Step 19.
     return result;
 }
-_SetCanonicalName(RegExpFlagsGetter, "get flags");
+_SetCanonicalName($RegExpFlagsGetter, "get flags");
 
 // ES 2017 draft 40edb3a95a475c1b251141ac681b8793129d9a6d 21.2.5.14.
-function RegExpToString()
+function $RegExpToString()
 {
     // Step 1.
     var R = this;
 
     // Step 2.
     if (!IsObject(R))
         ThrowTypeError(JSMSG_OBJECT_REQUIRED, R === null ? "null" : typeof R);
 
@@ -51,17 +53,17 @@ function RegExpToString()
     var pattern = ToString(R.source);
 
     // Step 4.
     var flags = ToString(R.flags);
 
     // Steps 5-6.
     return "/" + pattern + "/" + flags;
 }
-_SetCanonicalName(RegExpToString, "toString");
+_SetCanonicalName($RegExpToString, "toString");
 
 // ES 2016 draft Mar 25, 2016 21.2.5.2.3.
 function AdvanceStringIndex(S, index) {
     // Step 1.
     assert(typeof S === "string", "Expected string as 1st argument");
 
     // Step 2.
     assert(index >= 0 && index <= MAX_NUMERIC_INDEX, "Expected integer as 2nd argument");
@@ -1065,21 +1067,21 @@ function RegExpTest(string) {
     // Steps 3-4.
     var S = ToString(string);
 
     // Steps 5-6.
     return RegExpExec(R, S, true);
 }
 
 // ES 2016 draft Mar 25, 2016 21.2.4.2.
-function RegExpSpecies() {
+function $RegExpSpecies() {
     // Step 1.
     return this;
 }
-_SetCanonicalName(RegExpSpecies, "get [Symbol.species]");
+_SetCanonicalName($RegExpSpecies, "get [Symbol.species]");
 
 function IsRegExpMatchAllOptimizable(rx, C) {
     if (!IsRegExpObject(rx))
         return false;
 
     var RegExpCtor = GetBuiltinConstructor("RegExp");
     if (C !== RegExpCtor)
         return false;
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -58,18 +58,22 @@
 #define ACCESSOR_DESCRIPTOR_KIND 0x200
 
 // Property descriptor array indices.
 #define PROP_DESC_ATTRS_AND_KIND_INDEX 0
 #define PROP_DESC_VALUE_INDEX 1
 #define PROP_DESC_GETTER_INDEX 1
 #define PROP_DESC_SETTER_INDEX 2
 
-// The extended slot in which the self-hosted name for self-hosted builtins is
-// stored.
+// The extended slot of uncloned self-hosted function, in which the original
+// name for self-hosted builtins is stored by `_SetCanonicalName`.
+#define ORIGINAL_FUNCTION_NAME_SLOT 0
+
+// The extended slot of cloned self-hosted function, in which the self-hosted
+// name for self-hosted builtins is stored.
 #define LAZY_FUNCTION_NAME_SLOT 0
 
 // Stores the length for bound functions, so the .length property doesn't need
 // to be resolved eagerly.
 #define BOUND_FUN_LENGTH_SLOT 1
 
 #define ITERATOR_SLOT_TARGET 0
 // Used for collection iterators.
--- a/js/src/builtin/Set.js
+++ b/js/src/builtin/Set.js
@@ -48,27 +48,29 @@ function SetForEach(callbackfn, thisArg 
 
         var value = setIterationResult[0];
         setIterationResult[0] = null;
 
         callContentFunction(callbackfn, thisArg, value, value, S);
     }
 }
 
-function SetValues() {
+// Uncloned functions with `$` prefix are allocated as extended function
+// to store the original name in `_SetCanonicalName`.
+function $SetValues() {
     return callFunction(std_Set_iterator, this);
 }
-_SetCanonicalName(SetValues, "values");
+_SetCanonicalName($SetValues, "values");
 
 // ES6 final draft 23.2.2.2.
-function SetSpecies() {
+function $SetSpecies() {
     // Step 1.
     return this;
 }
-_SetCanonicalName(SetSpecies, "get [Symbol.species]");
+_SetCanonicalName($SetSpecies, "get [Symbol.species]");
 
 
 var setIteratorTemp = { setIterationResult: null };
 
 function SetIteratorNext() {
     // Step 1.
     var O = this;
 
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -1440,27 +1440,30 @@ function TypedArraySubarray(begin, end) 
     // Step 15.
     var beginByteOffset = srcByteOffset + (beginIndex << elementShift);
 
     // Steps 16-17.
     return TypedArraySpeciesCreateWithBuffer(obj, buffer, beginByteOffset, newLength);
 }
 
 // ES6 draft rev30 (2014/12/24) 22.2.3.30 %TypedArray%.prototype.values()
-function TypedArrayValues() {
+//
+// Uncloned functions with `$` prefix are allocated as extended function
+// to store the original name in `_SetCanonicalName`.
+function $TypedArrayValues() {
     // Step 1.
     var O = this;
 
     // See the big comment in TypedArrayEntries for what we're doing here.
     IsTypedArrayEnsuringArrayBuffer(O);
 
     // Step 7.
     return CreateArrayIterator(O, ITEM_KIND_VALUE);
 }
-_SetCanonicalName(TypedArrayValues, "values");
+_SetCanonicalName($TypedArrayValues, "values");
 
 // Proposed for ES7:
 // https://github.com/tc39/Array.prototype.includes/blob/7c023c19a0/spec.md
 function TypedArrayIncludes(searchElement, fromIndex = 0) {
     // This function is not generic.
     if (!IsObject(this) || !IsTypedArray(this)) {
         return callFunction(CallTypedArrayMethodIfWrapped, this, searchElement,
                             fromIndex, "TypedArrayIncludes");
@@ -1546,17 +1549,17 @@ function TypedArrayStaticFrom(source, ma
         // Inlined: GetMethod, step 4.
         if (!IsCallable(usingIterator))
             ThrowTypeError(JSMSG_NOT_ITERABLE, DecompileArg(0, source));
 
         // Try to take a fast path when there's no mapper function and the
         // constructor is a built-in TypedArray constructor.
         if (!mapping && IsTypedArrayConstructor(C) && IsObject(source)) {
             // The source is a TypedArray using the default iterator.
-            if (usingIterator === TypedArrayValues && IsTypedArray(source) &&
+            if (usingIterator === $TypedArrayValues && IsTypedArray(source) &&
                 ArrayIteratorPrototypeOptimizable())
             {
                 // Step 7.a.
                 // Omitted but we still need to throw if |source| was detached.
                 GetAttachedArrayBuffer(source);
 
                 // Step 7.b.
                 var len = TypedArrayLength(source);
@@ -1569,17 +1572,17 @@ function TypedArrayStaticFrom(source, ma
                     targetObj[k] = source[k];
                 }
 
                 // Step 7.g.
                 return targetObj;
             }
 
             // The source is a packed array using the default iterator.
-            if (usingIterator === ArrayValues && IsPackedArray(source) &&
+            if (usingIterator === $ArrayValues && IsPackedArray(source) &&
                 ArrayIteratorPrototypeOptimizable())
             {
                 // Steps 7.b-c.
                 var targetObj = new C(source.length);
 
                 // Steps 7.a, 7.d-f.
                 TypedArrayInitFromPackedArray(targetObj, source);
 
@@ -1669,21 +1672,21 @@ function TypedArrayStaticOf(/*...items*/
     for (var k = 0; k < len; k++)
         newObj[k] = items[k];
 
     // Step 8.
     return newObj;
 }
 
 // ES 2016 draft Mar 25, 2016 22.2.2.4.
-function TypedArraySpecies() {
+function $TypedArraySpecies() {
     // Step 1.
     return this;
 }
-_SetCanonicalName(TypedArraySpecies, "get [Symbol.species]");
+_SetCanonicalName($TypedArraySpecies, "get [Symbol.species]");
 
 // ES2018 draft rev 0525bb33861c7f4e9850f8a222c89642947c4b9c
 // 22.2.2.1.1 Runtime Semantics: IterableToList( items, method )
 function IterableToList(items, method) {
     // Step 1 (Inlined GetIterator).
 
     // 7.4.1 GetIterator, step 1.
     assert(IsCallable(method), "method argument is a function");
@@ -1801,28 +1804,28 @@ function ArrayBufferSlice(start, end) {
     return new_;
 }
 
 function IsDetachedBufferThis() {
   return IsDetachedBuffer(this);
 }
 
 // ES 2016 draft Mar 25, 2016 24.1.3.3.
-function ArrayBufferSpecies() {
+function $ArrayBufferSpecies() {
     // Step 1.
     return this;
 }
-_SetCanonicalName(ArrayBufferSpecies, "get [Symbol.species]");
+_SetCanonicalName($ArrayBufferSpecies, "get [Symbol.species]");
 
 // Shared memory and atomics proposal (30 Oct 2016)
-function SharedArrayBufferSpecies() {
+function $SharedArrayBufferSpecies() {
     // Step 1.
     return this;
 }
-_SetCanonicalName(SharedArrayBufferSpecies, "get [Symbol.species]");
+_SetCanonicalName($SharedArrayBufferSpecies, "get [Symbol.species]");
 
 // Shared memory and atomics proposal 6.2.1.5.3 (30 Oct 2016)
 // http://tc39.github.io/ecmascript_sharedmem/shmem.html
 function SharedArrayBufferSlice(start, end) {
     // Step 1.
     var O = this;
 
     // Steps 2-4,
--- a/js/src/builtin/intl/Collator.cpp
+++ b/js/src/builtin/intl/Collator.cpp
@@ -63,17 +63,17 @@ static const JSFunctionSpec collator_sta
                       1, 0),
     JS_FS_END};
 
 static const JSFunctionSpec collator_methods[] = {
     JS_SELF_HOSTED_FN("resolvedOptions", "Intl_Collator_resolvedOptions", 0, 0),
     JS_FN(js_toSource_str, collator_toSource, 0, 0), JS_FS_END};
 
 static const JSPropertySpec collator_properties[] = {
-    JS_SELF_HOSTED_GET("compare", "Intl_Collator_compare_get", 0),
+    JS_SELF_HOSTED_GET("compare", "$Intl_Collator_compare_get", 0),
     JS_STRING_SYM_PS(toStringTag, "Object", JSPROP_READONLY), JS_PS_END};
 
 /**
  * 10.1.2 Intl.Collator([ locales [, options]])
  *
  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool Collator(JSContext* cx, const CallArgs& args) {
--- a/js/src/builtin/intl/Collator.js
+++ b/js/src/builtin/intl/Collator.js
@@ -343,39 +343,41 @@ function collatorCompareToBind(x, y) {
  * Returns a function bound to this Collator that compares x (converted to a
  * String value) and y (converted to a String value),
  * and returns a number less than 0 if x < y, 0 if x = y, or a number greater
  * than 0 if x > y according to the sort order for the locale and collation
  * options of this Collator object.
  *
  * Spec: ECMAScript Internationalization API Specification, 10.3.3.
  */
-function Intl_Collator_compare_get() {
+// Uncloned functions with `$` prefix are allocated as extended function
+// to store the original name in `_SetCanonicalName`.
+function $Intl_Collator_compare_get() {
     // Step 1.
     var collator = this;
 
     // Steps 2-3.
     if (!IsObject(collator) || (collator = GuardToCollator(collator)) === null)
-        return callFunction(CallCollatorMethodIfWrapped, this, "Intl_Collator_compare_get");
+        return callFunction(CallCollatorMethodIfWrapped, this, "$Intl_Collator_compare_get");
 
     var internals = getCollatorInternals(collator);
 
     // Step 4.
     if (internals.boundCompare === undefined) {
         // Steps 4.a-b.
         var F = callFunction(FunctionBind, collatorCompareToBind, collator);
 
         // Step 4.c.
         internals.boundCompare = F;
     }
 
     // Step 5.
     return internals.boundCompare;
 }
-_SetCanonicalName(Intl_Collator_compare_get, "get compare");
+_SetCanonicalName($Intl_Collator_compare_get, "get compare");
 
 /**
  * Returns the resolved options for a Collator object.
  *
  * Spec: ECMAScript Internationalization API Specification, 10.3.4.
  */
 function Intl_Collator_resolvedOptions() {
     // Step 1.
--- a/js/src/builtin/intl/DateTimeFormat.cpp
+++ b/js/src/builtin/intl/DateTimeFormat.cpp
@@ -74,17 +74,17 @@ static const JSFunctionSpec dateTimeForm
 static const JSFunctionSpec dateTimeFormat_methods[] = {
     JS_SELF_HOSTED_FN("resolvedOptions", "Intl_DateTimeFormat_resolvedOptions",
                       0, 0),
     JS_SELF_HOSTED_FN("formatToParts", "Intl_DateTimeFormat_formatToParts", 1,
                       0),
     JS_FN(js_toSource_str, dateTimeFormat_toSource, 0, 0), JS_FS_END};
 
 static const JSPropertySpec dateTimeFormat_properties[] = {
-    JS_SELF_HOSTED_GET("format", "Intl_DateTimeFormat_format_get", 0),
+    JS_SELF_HOSTED_GET("format", "$Intl_DateTimeFormat_format_get", 0),
     JS_STRING_SYM_PS(toStringTag, "Object", JSPROP_READONLY), JS_PS_END};
 
 /**
  * 12.2.1 Intl.DateTimeFormat([ locales [, options]])
  *
  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool DateTimeFormat(JSContext* cx, const CallArgs& args, bool construct,
--- a/js/src/builtin/intl/DateTimeFormat.js
+++ b/js/src/builtin/intl/DateTimeFormat.js
@@ -786,40 +786,42 @@ function dateTimeFormatFormatToBind(date
 
 /**
  * Returns a function bound to this DateTimeFormat that returns a String value
  * representing the result of calling ToNumber(date) according to the
  * effective locale and the formatting options of this DateTimeFormat.
  *
  * Spec: ECMAScript Internationalization API Specification, 12.4.3.
  */
-function Intl_DateTimeFormat_format_get() {
+// Uncloned functions with `$` prefix are allocated as extended function
+// to store the original name in `_SetCanonicalName`.
+function $Intl_DateTimeFormat_format_get() {
     // Steps 1-3.
     var thisArg = UnwrapDateTimeFormat(this);
     var dtf = thisArg;
     if (!IsObject(dtf) || (dtf = GuardToDateTimeFormat(dtf)) === null) {
         return callFunction(CallDateTimeFormatMethodIfWrapped, thisArg,
-                            "Intl_DateTimeFormat_format_get");
+                            "$Intl_DateTimeFormat_format_get");
     }
 
     var internals = getDateTimeFormatInternals(dtf);
 
     // Step 4.
     if (internals.boundFormat === undefined) {
         // Steps 4.a-b.
         var F = callFunction(FunctionBind, dateTimeFormatFormatToBind, dtf);
 
         // Step 4.c.
         internals.boundFormat = F;
     }
 
     // Step 5.
     return internals.boundFormat;
 }
-_SetCanonicalName(Intl_DateTimeFormat_format_get, "get format");
+_SetCanonicalName($Intl_DateTimeFormat_format_get, "get format");
 
 /**
  * Intl.DateTimeFormat.prototype.formatToParts ( date )
  *
  * Spec: ECMAScript Internationalization API Specification, 12.4.4.
  */
 function Intl_DateTimeFormat_formatToParts(date) {
     // Step 1.
--- a/js/src/builtin/intl/NumberFormat.cpp
+++ b/js/src/builtin/intl/NumberFormat.cpp
@@ -73,17 +73,17 @@ static const JSFunctionSpec numberFormat
 
 static const JSFunctionSpec numberFormat_methods[] = {
     JS_SELF_HOSTED_FN("resolvedOptions", "Intl_NumberFormat_resolvedOptions", 0,
                       0),
     JS_SELF_HOSTED_FN("formatToParts", "Intl_NumberFormat_formatToParts", 1, 0),
     JS_FN(js_toSource_str, numberFormat_toSource, 0, 0), JS_FS_END};
 
 static const JSPropertySpec numberFormat_properties[] = {
-    JS_SELF_HOSTED_GET("format", "Intl_NumberFormat_format_get", 0),
+    JS_SELF_HOSTED_GET("format", "$Intl_NumberFormat_format_get", 0),
     JS_STRING_SYM_PS(toStringTag, "Object", JSPROP_READONLY), JS_PS_END};
 
 /**
  * 11.2.1 Intl.NumberFormat([ locales [, options]])
  *
  * ES2017 Intl draft rev 94045d234762ad107a3d09bb6f7381a65f1a2f9b
  */
 static bool NumberFormat(JSContext* cx, const CallArgs& args, bool construct) {
--- a/js/src/builtin/intl/NumberFormat.js
+++ b/js/src/builtin/intl/NumberFormat.js
@@ -413,40 +413,42 @@ function numberFormatFormatToBind(value)
 
 /**
  * Returns a function bound to this NumberFormat that returns a String value
  * representing the result of calling ToNumber(value) according to the
  * effective locale and the formatting options of this NumberFormat.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.4.3.
  */
-function Intl_NumberFormat_format_get() {
+// Uncloned functions with `$` prefix are allocated as extended function
+// to store the original name in `_SetCanonicalName`.
+function $Intl_NumberFormat_format_get() {
     // Steps 1-3.
     var thisArg = UnwrapNumberFormat(this);
     var nf = thisArg;
     if (!IsObject(nf) || (nf = GuardToNumberFormat(nf)) === null) {
         return callFunction(CallNumberFormatMethodIfWrapped, thisArg,
-                            "Intl_NumberFormat_format_get");
+                            "$Intl_NumberFormat_format_get");
     }
 
     var internals = getNumberFormatInternals(nf);
 
     // Step 4.
     if (internals.boundFormat === undefined) {
         // Steps 4.a-b.
         var F = callFunction(FunctionBind, numberFormatFormatToBind, nf);
 
         // Step 4.c.
         internals.boundFormat = F;
     }
 
     // Step 5.
     return internals.boundFormat;
 }
-_SetCanonicalName(Intl_NumberFormat_format_get, "get format");
+_SetCanonicalName($Intl_NumberFormat_format_get, "get format");
 
 /**
  * 11.4.4 Intl.NumberFormat.prototype.formatToParts ( value )
  */
 function Intl_NumberFormat_formatToParts(value) {
     // Step 1.
     var nf = this;
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -918,35 +918,44 @@ bool BytecodeEmitter::emitAtomOp(JSAtom*
 bool BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op) {
   MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
 
   return emitIndexOp(op, atomIndex);
 }
 
 bool BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op) {
   MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
-  MOZ_ASSERT(index < perScriptData().scopeList().length());
+  MOZ_ASSERT(index < perScriptData().gcThingList().length());
   return emitIndex32(op, index);
 }
 
 bool BytecodeEmitter::emitInternedObjectOp(uint32_t index, JSOp op) {
   MOZ_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT);
-  MOZ_ASSERT(index < perScriptData().objectList().length);
+  MOZ_ASSERT(index < perScriptData().gcThingList().length());
   return emitIndex32(op, index);
 }
 
 bool BytecodeEmitter::emitObjectOp(ObjectBox* objbox, JSOp op) {
-  return emitInternedObjectOp(perScriptData().objectList().add(objbox), op);
+  uint32_t index;
+  if (!perScriptData().gcThingList().append(objbox, &index)) {
+    return false;
+  }
+
+  return emitInternedObjectOp(index, op);
 }
 
 bool BytecodeEmitter::emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2,
                                        JSOp op) {
-  uint32_t index = perScriptData().objectList().add(objbox1);
-  perScriptData().objectList().add(objbox2);
-  return emitInternedObjectOp(index, op);
+  uint32_t index1, index2;
+  if (!perScriptData().gcThingList().append(objbox1, &index1) ||
+      !perScriptData().gcThingList().append(objbox2, &index2)) {
+    return false;
+  }
+
+  return emitInternedObjectOp(index1, op);
 }
 
 bool BytecodeEmitter::emitRegExp(uint32_t index) {
   return emitIndex32(JSOP_REGEXP, index);
 }
 
 bool BytecodeEmitter::emitLocalOp(JSOp op, uint32_t slot) {
   MOZ_ASSERT(JOF_OPTYPE(op) != JOF_ENVCOORD);
@@ -1673,17 +1682,17 @@ bool BytecodeEmitter::emitNewInit() {
   code[1] = 0;
   code[2] = 0;
   code[3] = 0;
   code[4] = 0;
   bytecodeSection().updateDepth(offset);
   return true;
 }
 
-bool BytecodeEmitter::iteratorResultShape(unsigned* shape) {
+bool BytecodeEmitter::iteratorResultShape(uint32_t* shape) {
   // No need to do any guessing for the object kind, since we know exactly how
   // many properties we plan to have.
   gc::AllocKind kind = gc::GetGCObjectKind(2);
   RootedPlainObject obj(
       cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
   if (!obj) {
     return false;
   }
@@ -1699,23 +1708,21 @@ bool BytecodeEmitter::iteratorResultShap
     return false;
   }
 
   ObjectBox* objbox = parser->newObjectBox(obj);
   if (!objbox) {
     return false;
   }
 
-  *shape = perScriptData().objectList().add(objbox);
-
-  return true;
+  return perScriptData().gcThingList().append(objbox, shape);
 }
 
 bool BytecodeEmitter::emitPrepareIteratorResult() {
-  unsigned shape;
+  uint32_t shape;
   if (!iteratorResultShape(&shape)) {
     return false;
   }
   return emitIndex32(JSOP_NEWOBJECT, shape);
 }
 
 bool BytecodeEmitter::emitFinishIteratorResult(bool done) {
   uint32_t value_id;
@@ -5001,20 +5008,21 @@ bool BytecodeEmitter::emitCopyDataProper
     return false;
   }
 
   MOZ_ASSERT(depth - int(argc) == bytecodeSection().stackDepth());
   return true;
 }
 
 bool BytecodeEmitter::emitBigIntOp(BigInt* bigint) {
-  if (!perScriptData().bigIntList().append(bigint)) {
-    return false;
-  }
-  return emitIndex32(JSOP_BIGINT, perScriptData().bigIntList().length() - 1);
+  uint32_t index;
+  if (!perScriptData().gcThingList().append(bigint, &index)) {
+    return false;
+  }
+  return emitIndex32(JSOP_BIGINT, index);
 }
 
 bool BytecodeEmitter::emitIterator() {
   // Convert iterable to iterator.
   if (!emit1(JSOP_DUP)) {
     //              [stack] OBJ OBJ
     return false;
   }
@@ -8241,17 +8249,21 @@ bool BytecodeEmitter::replaceNewInitWith
   if (!objbox) {
     return false;
   }
 
   static_assert(
       JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH,
       "newinit and newobject must have equal length to edit in-place");
 
-  uint32_t index = perScriptData().objectList().add(objbox);
+  uint32_t index;
+  if (!perScriptData().gcThingList().append(objbox, &index)) {
+    return false;
+  }
+
   jsbytecode* code = bytecodeSection().code(offset);
 
   MOZ_ASSERT(code[0] == JSOP_NEWINIT);
   code[0] = JSOP_NEWOBJECT;
   SET_UINT32(code, index);
 
   return true;
 }
@@ -9334,22 +9346,27 @@ bool BytecodeEmitter::emitTree(
       break;
 
     case ParseNodeKind::BigIntExpr:
       if (!emitBigIntOp(pn->as<BigIntLiteral>().box()->value())) {
         return false;
       }
       break;
 
-    case ParseNodeKind::RegExpExpr:
-      if (!emitRegExp(perScriptData().objectList().add(
-              pn->as<RegExpLiteral>().objbox()))) {
+    case ParseNodeKind::RegExpExpr: {
+      ObjectBox* obj = pn->as<RegExpLiteral>().objbox();
+      uint32_t index;
+      if (!perScriptData().gcThingList().append(obj, &index)) {
+        return false;
+      }
+      if (!emitRegExp(index)) {
         return false;
       }
       break;
+    }
 
     case ParseNodeKind::TrueExpr:
       if (!emit1(JSOP_TRUE)) {
         return false;
       }
       break;
     case ParseNodeKind::FalseExpr:
       if (!emit1(JSOP_FALSE)) {
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -273,22 +273,21 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
 
   void setVarEmitterScope(EmitterScope* emitterScope) {
     MOZ_ASSERT(emitterScope);
     MOZ_ASSERT(!varEmitterScope);
     varEmitterScope = emitterScope;
   }
 
   Scope* outermostScope() const {
-    return perScriptData().scopeList().vector[0];
+    return perScriptData().gcThingList().firstScope();
   }
   Scope* innermostScope() const;
   Scope* bodyScope() const {
-    MOZ_ASSERT(bodyScopeIndex < perScriptData().scopeList().length());
-    return perScriptData().scopeList().vector[bodyScopeIndex];
+    return perScriptData().gcThingList().getScope(bodyScopeIndex);
   }
 
   MOZ_ALWAYS_INLINE
   MOZ_MUST_USE bool makeAtomIndex(JSAtom* atom, uint32_t* indexp) {
     MOZ_ASSERT(perScriptData().atomIndices());
     AtomIndexMap::AddPtr p = perScriptData().atomIndices()->lookupForAdd(atom);
     if (p) {
       *indexp = p->value();
@@ -541,17 +540,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
                                       bool* emitSetFunName);
   MOZ_MUST_USE bool emitAssignmentRhs(uint8_t offset);
 
   MOZ_MUST_USE bool emitNewInit();
   MOZ_MUST_USE bool emitSingletonInitialiser(ListNode* objOrArray);
 
   MOZ_MUST_USE bool emitPrepareIteratorResult();
   MOZ_MUST_USE bool emitFinishIteratorResult(bool done);
-  MOZ_MUST_USE bool iteratorResultShape(unsigned* shape);
+  MOZ_MUST_USE bool iteratorResultShape(uint32_t* shape);
 
   MOZ_MUST_USE bool emitGetDotGeneratorInInnermostScope() {
     return emitGetDotGeneratorInScope(*innermostEmitterScope());
   }
   MOZ_MUST_USE bool emitGetDotGeneratorInScope(EmitterScope& currentScope);
 
   MOZ_MUST_USE bool allocateResumeIndex(BytecodeOffset offset,
                                         uint32_t* resumeIndex);
--- a/js/src/frontend/BytecodeSection.cpp
+++ b/js/src/frontend/BytecodeSection.cpp
@@ -13,69 +13,45 @@
 #include "frontend/ParseNode.h"      // ObjectBox
 #include "frontend/SharedContext.h"  // FunctionBox
 #include "vm/BytecodeUtil.h"         // INDEX_LIMIT, StackUses, StackDefs
 #include "vm/JSContext.h"            // JSContext
 
 using namespace js;
 using namespace js::frontend;
 
-void CGBigIntList::finish(mozilla::Span<GCPtrBigInt> array) {
-  MOZ_ASSERT(length() == array.size());
-
-  for (unsigned i = 0; i < length(); i++) {
-    array[i].init(vector[i]);
-  }
-}
+bool GCThingList::append(ObjectBox* objbox, uint32_t* index) {
+  // Append the object to the vector and return the index in *index. Also add
+  // the ObjectBox to the |lastbox| linked list for finishInnerFunctions below.
 
-/*
- * Find the index of the given object for code generator.
- *
- * Since the emitter refers to each parsed object only once, for the index we
- * use the number of already indexed objects. We also add the object to a list
- * to convert the list to a fixed-size array when we complete code generation,
- * see js::CGObjectList::finish below.
- */
-unsigned CGObjectList::add(ObjectBox* objbox) {
   MOZ_ASSERT(objbox->isObjectBox());
   MOZ_ASSERT(!objbox->emitLink);
   objbox->emitLink = lastbox;
   lastbox = objbox;
-  return length++;
+
+  *index = vector.length();
+  return vector.append(JS::GCCellPtr(objbox->object()));
 }
 
-void CGObjectList::finish(mozilla::Span<GCPtrObject> array) {
-  MOZ_ASSERT(length <= INDEX_LIMIT);
-  MOZ_ASSERT(length == array.size());
-
-  ObjectBox* objbox = lastbox;
-  for (GCPtrObject& obj : mozilla::Reversed(array)) {
-    MOZ_ASSERT(obj == nullptr);
-    MOZ_ASSERT(objbox->object()->isTenured());
-    obj.init(objbox->object());
-    objbox = objbox->emitLink;
-  }
-}
-
-void CGObjectList::finishInnerFunctions() {
+void GCThingList::finishInnerFunctions() {
   ObjectBox* objbox = lastbox;
   while (objbox) {
     if (objbox->isFunctionBox()) {
       objbox->asFunctionBox()->finish();
     }
     objbox = objbox->emitLink;
   }
 }
 
-void CGScopeList::finish(mozilla::Span<GCPtrScope> array) {
+void GCThingList::finish(mozilla::Span<JS::GCCellPtr> array) {
   MOZ_ASSERT(length() <= INDEX_LIMIT);
   MOZ_ASSERT(length() == array.size());
 
   for (uint32_t i = 0; i < length(); i++) {
-    array[i].init(vector[i]);
+    array[i] = vector[i].get().get();
   }
 }
 
 bool CGTryNoteList::append(JSTryNoteKind kind, uint32_t stackDepth,
                            BytecodeOffset start, BytecodeOffset end) {
   MOZ_ASSERT(start <= end);
 
   // Offsets are given relative to sections, but we only expect main-section
@@ -165,13 +141,11 @@ void BytecodeSection::updateDepth(Byteco
   stackDepth_ += ndefs;
 
   if (uint32_t(stackDepth_) > maxStackDepth_) {
     maxStackDepth_ = stackDepth_;
   }
 }
 
 PerScriptData::PerScriptData(JSContext* cx)
-    : scopeList_(cx),
-      bigIntList_(cx),
-      atomIndices_(cx->frontendCollectionPool()) {}
+    : gcThingList_(cx), atomIndices_(cx->frontendCollectionPool()) {}
 
 bool PerScriptData::init(JSContext* cx) { return atomIndices_.acquire(cx); }
--- a/js/src/frontend/BytecodeSection.h
+++ b/js/src/frontend/BytecodeSection.h
@@ -3,16 +3,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef frontend_BytecodeSection_h
 #define frontend_BytecodeSection_h
 
 #include "mozilla/Attributes.h"  // MOZ_MUST_USE, MOZ_STACK_CLASS
+#include "mozilla/Maybe.h"       // mozilla::Maybe
 #include "mozilla/Span.h"        // mozilla::Span
 
 #include <stddef.h>  // ptrdiff_t, size_t
 #include <stdint.h>  // uint16_t, int32_t, uint32_t
 
 #include "NamespaceImports.h"          // ValueVector
 #include "frontend/BytecodeOffset.h"   // BytecodeOffset
 #include "frontend/JumpList.h"         // JumpTarget
@@ -32,47 +33,55 @@ namespace js {
 class Scope;
 
 using BigIntVector = JS::GCVector<js::BigInt*>;
 
 namespace frontend {
 
 class ObjectBox;
 
-class CGBigIntList {
-  JS::Rooted<BigIntVector> vector;
+struct MOZ_STACK_CLASS GCThingList {
+  JS::RootedVector<StackGCCellPtr> vector;
 
- public:
-  explicit CGBigIntList(JSContext* cx) : vector(cx, BigIntVector(cx)) {}
-  MOZ_MUST_USE bool append(js::BigInt* bi) { return vector.append(bi); }
-  size_t length() const { return vector.length(); }
-  void finish(mozilla::Span<GCPtrBigInt> array);
-};
+  // Last emitted object.
+  ObjectBox* lastbox = nullptr;
 
-struct CGObjectList {
-  // Number of emitted so far objects.
-  uint32_t length;
-  // Last emitted object.
-  ObjectBox* lastbox;
+  // Index of the first scope in the vector.
+  mozilla::Maybe<uint32_t> firstScopeIndex;
+
+  explicit GCThingList(JSContext* cx) : vector(cx) {}
 
-  CGObjectList() : length(0), lastbox(nullptr) {}
-
-  unsigned add(ObjectBox* objbox);
-  void finish(mozilla::Span<GCPtrObject> array);
-  void finishInnerFunctions();
-};
+  MOZ_MUST_USE bool append(Scope* scope, uint32_t* index) {
+    *index = vector.length();
+    if (!vector.append(JS::GCCellPtr(scope))) {
+      return false;
+    }
+    if (!firstScopeIndex) {
+      firstScopeIndex.emplace(*index);
+    }
+    return true;
+  }
+  MOZ_MUST_USE bool append(BigInt* bi, uint32_t* index) {
+    *index = vector.length();
+    return vector.append(JS::GCCellPtr(bi));
+  }
+  MOZ_MUST_USE bool append(ObjectBox* obj, uint32_t* index);
 
-struct MOZ_STACK_CLASS CGScopeList {
-  JS::Rooted<GCVector<Scope*>> vector;
+  uint32_t length() const { return vector.length(); }
+  void finish(mozilla::Span<JS::GCCellPtr> array);
+  void finishInnerFunctions();
 
-  explicit CGScopeList(JSContext* cx) : vector(cx, GCVector<Scope*>(cx)) {}
+  Scope* getScope(size_t index) const {
+    return &vector[index].get().get().as<Scope>();
+  }
 
-  bool append(Scope* scope) { return vector.append(scope); }
-  uint32_t length() const { return vector.length(); }
-  void finish(mozilla::Span<GCPtrScope> array);
+  Scope* firstScope() const {
+    MOZ_ASSERT(firstScopeIndex.isSome());
+    return getScope(*firstScopeIndex);
+  }
 };
 
 struct CGTryNoteList {
   Vector<JSTryNote> list;
   explicit CGTryNoteList(JSContext* cx) : list(cx) {}
 
   MOZ_MUST_USE bool append(JSTryNoteKind kind, uint32_t stackDepth,
                            BytecodeOffset start, BytecodeOffset end);
@@ -320,45 +329,25 @@ class BytecodeSection {
 // Data that is not directly associated with specific opcode/index inside
 // bytecode, but referred from bytecode is stored in this class.
 class PerScriptData {
  public:
   explicit PerScriptData(JSContext* cx);
 
   MOZ_MUST_USE bool init(JSContext* cx);
 
-  // ---- Scope ----
-
-  CGScopeList& scopeList() { return scopeList_; }
-  const CGScopeList& scopeList() const { return scopeList_; }
-
-  // ---- Literals ----
-
-  CGBigIntList& bigIntList() { return bigIntList_; }
-  const CGBigIntList& bigIntList() const { return bigIntList_; }
-
-  CGObjectList& objectList() { return objectList_; }
-  const CGObjectList& objectList() const { return objectList_; }
+  GCThingList& gcThingList() { return gcThingList_; }
+  const GCThingList& gcThingList() const { return gcThingList_; }
 
   PooledMapPtr<AtomIndexMap>& atomIndices() { return atomIndices_; }
   const PooledMapPtr<AtomIndexMap>& atomIndices() const { return atomIndices_; }
 
  private:
-  // ---- Scope ----
-
-  // List of emitted scopes.
-  CGScopeList scopeList_;
-
-  // ---- Literals ----
-
-  // List of bigints used by script.
-  CGBigIntList bigIntList_;
-
-  // List of emitted objects.
-  CGObjectList objectList_;
+  // List of emitted scopes/objects/bigints.
+  GCThingList gcThingList_;
 
   // Map from atom to index.
   PooledMapPtr<AtomIndexMap> atomIndices_;
 };
 
 } /* namespace frontend */
 } /* namespace js */
 
--- a/js/src/frontend/EmitterScope.cpp
+++ b/js/src/frontend/EmitterScope.cpp
@@ -337,26 +337,25 @@ NameLocation EmitterScope::searchAndCach
 template <typename ScopeCreator>
 bool EmitterScope::internScope(BytecodeEmitter* bce, ScopeCreator createScope) {
   RootedScope enclosing(bce->cx, enclosingScope(bce));
   Scope* scope = createScope(bce->cx, enclosing);
   if (!scope) {
     return false;
   }
   hasEnvironment_ = scope->hasEnvironment();
-  scopeIndex_ = bce->perScriptData().scopeList().length();
-  return bce->perScriptData().scopeList().append(scope);
+  return bce->perScriptData().gcThingList().append(scope, &scopeIndex_);
 }
 
 template <typename ScopeCreator>
 bool EmitterScope::internBodyScope(BytecodeEmitter* bce,
                                    ScopeCreator createScope) {
   MOZ_ASSERT(bce->bodyScopeIndex == UINT32_MAX,
              "There can be only one body scope");
-  bce->bodyScopeIndex = bce->perScriptData().scopeList().length();
+  bce->bodyScopeIndex = bce->perScriptData().gcThingList().length();
   return internScope(bce, createScope);
 }
 
 bool EmitterScope::appendScopeNote(BytecodeEmitter* bce) {
   MOZ_ASSERT(ScopeKindIsInBody(scope(bce)->kind()) && enclosingInFrame(),
              "Scope notes are not needed for body-level scopes.");
   noteIndex_ = bce->bytecodeSection().scopeNoteList().length();
   return bce->bytecodeSection().scopeNoteList().append(
@@ -1066,17 +1065,17 @@ bool EmitterScope::leave(BytecodeEmitter
       }
     }
   }
 
   return true;
 }
 
 Scope* EmitterScope::scope(const BytecodeEmitter* bce) const {
-  return bce->perScriptData().scopeList().vector[index()];
+  return bce->perScriptData().gcThingList().getScope(index());
 }
 
 NameLocation EmitterScope::lookup(BytecodeEmitter* bce, JSAtom* name) {
   if (Maybe<NameLocation> loc = lookupInCache(bce, name)) {
     return *loc;
   }
   return searchAndCache(bce, name);
 }
--- a/js/src/frontend/FunctionEmitter.cpp
+++ b/js/src/frontend/FunctionEmitter.cpp
@@ -215,17 +215,20 @@ bool FunctionEmitter::emitAsmJSModule() 
 #ifdef DEBUG
   state_ = State::End;
 #endif
   return true;
 }
 
 bool FunctionEmitter::emitFunction() {
   // Make the function object a literal in the outer script's pool.
-  unsigned index = bce_->perScriptData().objectList().add(funbox_);
+  uint32_t index;
+  if (!bce_->perScriptData().gcThingList().append(funbox_, &index)) {
+    return false;
+  }
 
   //                [stack]
 
   if (isHoisted_ == IsHoisted::No) {
     return emitNonHoisted(index);
     //              [stack] FUN?
   }
 
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -2183,17 +2183,17 @@ class TraceListNode {
 class BigIntBox : public TraceListNode {
  public:
   BigIntBox(BigInt* bi, TraceListNode* link);
   BigInt* value() const { return gcThing->as<BigInt>(); }
 };
 
 class ObjectBox : public TraceListNode {
  protected:
-  friend struct CGObjectList;
+  friend struct GCThingList;
   ObjectBox* emitLink;
 
   ObjectBox(JSFunction* function, TraceListNode* link);
 
  public:
   ObjectBox(JSObject* obj, TraceListNode* link);
 
   JSObject* object() const { return gcThing->as<JSObject>(); }
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -46,16 +46,17 @@
 #include "js/RegExpFlags.h"  // JS::RegExpFlags
 #include "vm/BytecodeUtil.h"
 #include "vm/JSAtom.h"
 #include "vm/JSContext.h"
 #include "vm/JSFunction.h"
 #include "vm/JSScript.h"
 #include "vm/ModuleBuilder.h"  // js::ModuleBuilder
 #include "vm/RegExpObject.h"
+#include "vm/SelfHosting.h"
 #include "vm/StringType.h"
 #include "wasm/AsmJS.h"
 
 #include "frontend/ParseContext-inl.h"
 #include "frontend/SharedContext-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 
 using namespace js;
@@ -1968,16 +1969,20 @@ JSFunction* AllocNewFunction(JSContext* 
                              bool isSelfHosting /* = false */,
                              bool inFunctionBox /* = false */) {
   MOZ_ASSERT_IF(kind == FunctionSyntaxKind::Statement, atom != nullptr);
 
   RootedFunction fun(cx);
 
   gc::AllocKind allocKind = gc::AllocKind::FUNCTION;
   JSFunction::Flags flags;
+  bool isExtendedUnclonedSelfHostedFunctionName =
+      isSelfHosting && atom && IsExtendedUnclonedSelfHostedFunctionName(atom);
+  MOZ_ASSERT_IF(isExtendedUnclonedSelfHostedFunctionName, !inFunctionBox);
+
   switch (kind) {
     case FunctionSyntaxKind::Expression:
       flags = (generatorKind == GeneratorKind::NotGenerator &&
                        asyncKind == FunctionAsyncKind::SyncFunction
                    ? JSFunction::INTERPRETED_LAMBDA
                    : JSFunction::INTERPRETED_LAMBDA_GENERATOR_OR_ASYNC);
       break;
     case FunctionSyntaxKind::Arrow:
@@ -1998,17 +2003,17 @@ JSFunction* AllocNewFunction(JSContext* 
       allocKind = gc::AllocKind::FUNCTION_EXTENDED;
       break;
     case FunctionSyntaxKind::Setter:
       flags = JSFunction::INTERPRETED_SETTER;
       allocKind = gc::AllocKind::FUNCTION_EXTENDED;
       break;
     default:
       MOZ_ASSERT(kind == FunctionSyntaxKind::Statement);
-      if (isSelfHosting && !inFunctionBox) {
+      if (isExtendedUnclonedSelfHostedFunctionName) {
         allocKind = gc::AllocKind::FUNCTION_EXTENDED;
       }
       flags = (generatorKind == GeneratorKind::NotGenerator &&
                        asyncKind == FunctionAsyncKind::SyncFunction
                    ? JSFunction::INTERPRETED_NORMAL
                    : JSFunction::INTERPRETED_GENERATOR_OR_ASYNC);
   }
 
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -952,16 +952,31 @@ struct WeakHeapPtrHasher {
 
   static HashNumber hash(Lookup obj) { return DefaultHasher<T>::hash(obj); }
   static bool match(const Key& k, Lookup l) { return k.unbarrieredGet() == l; }
   static void rekey(Key& k, const Key& newKey) {
     k.set(newKey.unbarrieredGet());
   }
 };
 
+// Wrapper around GCCellPtr for use with RootedVector<StackGCCellPtr>.
+class MOZ_STACK_CLASS StackGCCellPtr {
+  JS::GCCellPtr ptr_;
+
+ public:
+  MOZ_IMPLICIT StackGCCellPtr(JS::GCCellPtr ptr) : ptr_(ptr) {}
+  StackGCCellPtr() = default;
+
+  void operator=(const StackGCCellPtr& other) { ptr_ = other.ptr_; }
+
+  void trace(JSTracer* trc);
+
+  JS::GCCellPtr get() const { return ptr_; }
+};
+
 }  // namespace js
 
 namespace mozilla {
 
 /* Specialized hashing policy for GCPtrs. */
 template <class T>
 struct DefaultHasher<js::GCPtr<T>> : js::GCPtrHasher<T> {};
 
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -547,16 +547,24 @@ void js::TraceManuallyBarrieredGenericPo
                                   TraceManuallyBarrieredEdge(trc, &t, name);
                                   return t;
                                 });
   if (traced != thing) {
     *thingp = traced;
   }
 }
 
+void StackGCCellPtr::trace(JSTracer* trc) {
+  Cell* thing = ptr_.asCell();
+  TraceGenericPointerRoot(trc, &thing, "stack-gc-cell-ptr");
+  if (thing != ptr_.asCell()) {
+    ptr_ = JS::GCCellPtr(thing, ptr_.kind());
+  }
+}
+
 // This method is responsible for dynamic dispatch to the real tracer
 // implementation. Consider replacing this choke point with virtual dispatch:
 // a sufficiently smart C++ compiler may be able to devirtualize some paths.
 template <typename T>
 void js::gc::TraceEdgeInternal(JSTracer* trc, T* thingp, const char* name) {
 #define IS_SAME_TYPE_OR(name, type, _, _1) mozilla::IsSame<type*, T>::value ||
   static_assert(JS_FOR_EACH_TRACEKIND(IS_SAME_TYPE_OR)
                         mozilla::IsSame<T, JS::Value>::value ||
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -2297,17 +2297,22 @@ static bool IsRegExpHoistableCall(Compil
     return false;
   }
 
   JSAtom* name;
   if (WrappedFunction* fun = call->getSingleTarget()) {
     if (!fun->isSelfHostedBuiltin()) {
       return false;
     }
-    name = GetSelfHostedFunctionName(fun->rawJSFunction());
+
+    // Avoid accessing `JSFunction.flags_` via `JSFunction::isExtended`.
+    if (!fun->isExtended()) {
+      return false;
+    }
+    name = GetClonedSelfHostedFunctionNameOffMainThread(fun->rawJSFunction());
   } else {
     MDefinition* funDef = call->getFunction();
     if (funDef->isDebugCheckSelfHosted()) {
       funDef = funDef->toDebugCheckSelfHosted()->input();
     }
     if (funDef->isTypeBarrier()) {
       funDef = funDef->toTypeBarrier()->input();
     }
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -1518,17 +1518,18 @@ bool MParameter::congruentTo(const MDefi
 
 WrappedFunction::WrappedFunction(JSFunction* fun)
     : fun_(fun),
       nargs_(fun->nargs()),
       isNative_(fun->isNative()),
       isNativeWithJitEntry_(fun->isNativeWithJitEntry()),
       isConstructor_(fun->isConstructor()),
       isClassConstructor_(fun->isClassConstructor()),
-      isSelfHostedBuiltin_(fun->isSelfHostedBuiltin()) {}
+      isSelfHostedBuiltin_(fun->isSelfHostedBuiltin()),
+      isExtended_(fun->isExtended()) {}
 
 MCall* MCall::New(TempAllocator& alloc, JSFunction* target, size_t maxArgc,
                   size_t numActualArgs, bool construct, bool ignoresReturnValue,
                   bool isDOMCall, DOMObjectKind objectKind) {
   WrappedFunction* wrappedTarget =
       target ? new (alloc) WrappedFunction(target) : nullptr;
   MOZ_ASSERT(maxArgc >= numActualArgs);
   MCall* ins;
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -2664,30 +2664,32 @@ class MInitElemGetterSetter
 class WrappedFunction : public TempObject {
   CompilerFunction fun_;
   uint16_t nargs_;
   bool isNative_ : 1;
   bool isNativeWithJitEntry_ : 1;
   bool isConstructor_ : 1;
   bool isClassConstructor_ : 1;
   bool isSelfHostedBuiltin_ : 1;
+  bool isExtended_ : 1;
 
  public:
   explicit WrappedFunction(JSFunction* fun);
   size_t nargs() const { return nargs_; }
 
   bool isNative() const { return isNative_; }
   bool isNativeWithJitEntry() const { return isNativeWithJitEntry_; }
   bool isNativeWithCppEntry() const {
     return isNative() && !isNativeWithJitEntry();
   }
 
   bool isConstructor() const { return isConstructor_; }
   bool isClassConstructor() const { return isClassConstructor_; }
   bool isSelfHostedBuiltin() const { return isSelfHostedBuiltin_; }
+  bool isExtended() const { return isExtended_; }
 
   // fun->native() and fun->jitInfo() can safely be called off-thread: these
   // fields never change.
   JSNative native() const { return fun_->native(); }
   bool hasJitInfo() const { return fun_->hasJitInfo(); }
   const JSJitInfo* jitInfo() const { return fun_->jitInfo(); }
 
   JSFunction* rawJSFunction() const { return fun_; }
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3274,18 +3274,23 @@ static MOZ_MUST_USE bool DisassembleScri
   }
   if (!TryNotes(cx, script, sp)) {
     return false;
   }
   if (!ScopeNotes(cx, script, sp)) {
     return false;
   }
 
-  if (recursive && script->hasObjects()) {
-    for (JSObject* obj : script->objects()) {
+  if (recursive) {
+    for (JS::GCCellPtr gcThing : script->gcthings()) {
+      if (!gcThing.is<JSObject>()) {
+        continue;
+      }
+
+      JSObject* obj = &gcThing.as<JSObject>();
       if (obj->is<JSFunction>()) {
         if (!sp->put("\n")) {
           return false;
         }
 
         RootedFunction fun(cx, &obj->as<JSFunction>());
         if (fun->isInterpreted()) {
           RootedScript script(cx, JSFunction::getOrCreateScript(cx, fun));
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -300,17 +300,17 @@ static const ClassOps ArrayBufferObjectC
     nullptr, /* construct   */
     nullptr, /* 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};
+    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};
 
--- a/js/src/vm/BytecodeUtil.cpp
+++ b/js/src/vm/BytecodeUtil.cpp
@@ -2908,22 +2908,24 @@ static bool GenerateLcovInfo(JSContext* 
     if (!script->isTopLevel()) {
       continue;
     }
 
     // Iterate from the last to the first object in order to have
     // the functions them visited in the opposite order when popping
     // elements from the stack of remaining scripts, such that the
     // functions are more-less listed with increasing line numbers.
-    if (!script->hasObjects()) {
-      continue;
-    }
-    auto objects = script->objects();
-    for (JSObject* obj : mozilla::Reversed(objects)) {
+    auto gcthings = script->gcthings();
+    for (JS::GCCellPtr gcThing : mozilla::Reversed(gcthings)) {
+      if (!gcThing.is<JSObject>()) {
+        continue;
+      }
+
       // Only continue on JSFunction objects.
+      JSObject* obj = &gcThing.as<JSObject>();
       if (!obj->is<JSFunction>()) {
         continue;
       }
       fun = &obj->as<JSFunction>();
 
       // Let's skip wasm for now.
       if (!fun->isInterpreted()) {
         continue;
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -14,25 +14,25 @@
 #define FOR_EACH_COMMON_PROPERTYNAME(MACRO)                                    \
   MACRO(add, add, "add")                                                       \
   MACRO(allowContentIter, allowContentIter, "allowContentIter")                \
   MACRO(anonymous, anonymous, "anonymous")                                     \
   MACRO(Any, Any, "Any")                                                       \
   MACRO(apply, apply, "apply")                                                 \
   MACRO(args, args, "args")                                                    \
   MACRO(arguments, arguments, "arguments")                                     \
-  MACRO(ArrayBufferSpecies, ArrayBufferSpecies, "ArrayBufferSpecies")          \
+  MACRO(ArrayBufferSpecies, ArrayBufferSpecies, "$ArrayBufferSpecies")         \
   MACRO(ArrayIterator, ArrayIterator, "Array Iterator")                        \
   MACRO(ArrayIteratorNext, ArrayIteratorNext, "ArrayIteratorNext")             \
   MACRO(ArraySort, ArraySort, "ArraySort")                                     \
-  MACRO(ArraySpecies, ArraySpecies, "ArraySpecies")                            \
+  MACRO(ArraySpecies, ArraySpecies, "$ArraySpecies")                           \
   MACRO(ArraySpeciesCreate, ArraySpeciesCreate, "ArraySpeciesCreate")          \
   MACRO(ArrayToLocaleString, ArrayToLocaleString, "ArrayToLocaleString")       \
   MACRO(ArrayType, ArrayType, "ArrayType")                                     \
-  MACRO(ArrayValues, ArrayValues, "ArrayValues")                               \
+  MACRO(ArrayValues, ArrayValues, "$ArrayValues")                              \
   MACRO(as, as, "as")                                                          \
   MACRO(Async, Async, "Async")                                                 \
   MACRO(AsyncFromSyncIterator, AsyncFromSyncIterator,                          \
         "Async-from-Sync Iterator")                                            \
   MACRO(AsyncFunction, AsyncFunction, "AsyncFunction")                         \
   MACRO(AsyncFunctionNext, AsyncFunctionNext, "AsyncFunctionNext")             \
   MACRO(AsyncFunctionThrow, AsyncFunctionThrow, "AsyncFunctionThrow")          \
   MACRO(AsyncGenerator, AsyncGenerator, "AsyncGenerator")                      \
@@ -338,17 +338,17 @@
   MACRO(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable")    \
   MACRO(protected, protected_, "protected")                                    \
   MACRO(proto, proto, "__proto__")                                             \
   MACRO(prototype, prototype, "prototype")                                     \
   MACRO(proxy, proxy, "proxy")                                                 \
   MACRO(raw, raw, "raw")                                                       \
   MACRO(reason, reason, "reason")                                              \
   MACRO(RegExpBuiltinExec, RegExpBuiltinExec, "RegExpBuiltinExec")             \
-  MACRO(RegExpFlagsGetter, RegExpFlagsGetter, "RegExpFlagsGetter")             \
+  MACRO(RegExpFlagsGetter, RegExpFlagsGetter, "$RegExpFlagsGetter")            \
   MACRO(RegExpMatcher, RegExpMatcher, "RegExpMatcher")                         \
   MACRO(RegExpSearcher, RegExpSearcher, "RegExpSearcher")                      \
   MACRO(RegExpStringIterator, RegExpStringIterator, "RegExp String Iterator")  \
   MACRO(RegExpTester, RegExpTester, "RegExpTester")                            \
   MACRO(RegExp_prototype_Exec, RegExp_prototype_Exec, "RegExp_prototype_Exec") \
   MACRO(Reify, Reify, "Reify")                                                 \
   MACRO(reject, reject, "reject")                                              \
   MACRO(rejected, rejected, "rejected")                                        \
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -6454,39 +6454,40 @@ static bool DebuggerScript_getChildScrip
   THIS_DEBUGSCRIPT_SCRIPT_DELAZIFY(cx, argc, vp, "getChildScripts", args, obj,
                                    script);
   Debugger* dbg = Debugger::fromChildJSObject(obj);
 
   RootedObject result(cx, NewDenseEmptyArray(cx));
   if (!result) {
     return false;
   }
-  if (script->hasObjects()) {
-    // script->savedCallerFun indicates that this is a direct eval script
-    // and the calling function is stored as script->objects()->vector[0].
-    // It is not really a child script of this script, so skip it using
-    // innerObjectsStart().
-    RootedFunction fun(cx);
-    RootedScript funScript(cx);
-    RootedObject s(cx);
-    for (const GCPtrObject& obj : script->objects()) {
-      if (obj->is<JSFunction>()) {
-        fun = &obj->as<JSFunction>();
-        // The inner function could be an asm.js native.
-        if (!IsInterpretedNonSelfHostedFunction(fun)) {
-          continue;
-        }
-        funScript = GetOrCreateFunctionScript(cx, fun);
-        if (!funScript) {
-          return false;
-        }
-        s = dbg->wrapScript(cx, funScript);
-        if (!s || !NewbornArrayPush(cx, result, ObjectValue(*s))) {
-          return false;
-        }
+
+  // Wrap and append scripts for the inner functions in script->gcthings().
+  RootedFunction fun(cx);
+  RootedScript funScript(cx);
+  RootedObject s(cx);
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (!gcThing.is<JSObject>()) {
+      continue;
+    }
+
+    JSObject* obj = &gcThing.as<JSObject>();
+    if (obj->is<JSFunction>()) {
+      fun = &obj->as<JSFunction>();
+      // The inner function could be an asm.js native.
+      if (!IsInterpretedNonSelfHostedFunction(fun)) {
+        continue;
+      }
+      funScript = GetOrCreateFunctionScript(cx, fun);
+      if (!funScript) {
+        return false;
+      }
+      s = dbg->wrapScript(cx, funScript);
+      if (!s || !NewbornArrayPush(cx, result, ObjectValue(*s))) {
+        return false;
       }
     }
   }
   args.rval().setObject(*result);
   return true;
 }
 
 static bool ScriptOffset(JSContext* cx, const Value& v, size_t* offsetp) {
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -3796,30 +3796,33 @@ static bool RemoveReferencedNames(JSCont
         break;
     }
 
     if (name) {
       remainingNames.remove(name);
     }
   }
 
-  if (script->hasObjects()) {
-    RootedFunction fun(cx);
-    RootedScript innerScript(cx);
-    for (JSObject* obj : script->objects()) {
-      if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
-        fun = &obj->as<JSFunction>();
-        innerScript = JSFunction::getOrCreateScript(cx, fun);
-        if (!innerScript) {
-          return false;
-        }
-
-        if (!RemoveReferencedNames(cx, innerScript, remainingNames)) {
-          return false;
-        }
+  RootedFunction fun(cx);
+  RootedScript innerScript(cx);
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (!gcThing.is<JSObject>()) {
+      continue;
+    }
+
+    JSObject* obj = &gcThing.as<JSObject>();
+    if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
+      fun = &obj->as<JSFunction>();
+      innerScript = JSFunction::getOrCreateScript(cx, fun);
+      if (!innerScript) {
+        return false;
+      }
+
+      if (!RemoveReferencedNames(cx, innerScript, remainingNames)) {
+        return false;
       }
     }
   }
 
   return true;
 }
 
 static bool AnalyzeEntrainedVariablesInScript(JSContext* cx,
@@ -3868,27 +3871,30 @@ static bool AnalyzeEntrainedVariablesInS
          r.popFront()) {
       buf.printf(" ");
       buf.putString(r.front());
     }
 
     printf("%s\n", buf.string());
   }
 
-  if (innerScript->hasObjects()) {
-    RootedFunction fun(cx);
-    RootedScript innerInnerScript(cx);
-    for (JSObject* obj : script->objects()) {
-      if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
-        fun = &obj->as<JSFunction>();
-        innerInnerScript = JSFunction::getOrCreateScript(cx, fun);
-        if (!innerInnerScript ||
-            !AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript)) {
-          return false;
-        }
+  RootedFunction fun(cx);
+  RootedScript innerInnerScript(cx);
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (!gcThing.is<JSObject>()) {
+      continue;
+    }
+
+    JSObject* obj = &gcThing.as<JSObject>();
+    if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
+      fun = &obj->as<JSFunction>();
+      innerInnerScript = JSFunction::getOrCreateScript(cx, fun);
+      if (!innerInnerScript ||
+          !AnalyzeEntrainedVariablesInScript(cx, script, innerInnerScript)) {
+        return false;
       }
     }
   }
 
   return true;
 }
 
 // Look for local variables in script or any other script inner to it, which are
@@ -3898,23 +3904,24 @@ static bool AnalyzeEntrainedVariablesInS
 // function foo() {
 //   var a, b;
 //   function bar() { return a; }
 //   function baz() { return b; }
 // }
 //
 // |bar| unnecessarily entrains |b|, and |baz| unnecessarily entrains |a|.
 bool js::AnalyzeEntrainedVariables(JSContext* cx, HandleScript script) {
-  if (!script->hasObjects()) {
-    return true;
-  }
-
   RootedFunction fun(cx);
   RootedScript innerScript(cx);
-  for (JSObject* obj : script->objects()) {
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (!gcThing.is<JSObject>()) {
+      continue;
+    }
+
+    JSObject* obj = &gcThing.as<JSObject>();
     if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted()) {
       fun = &obj->as<JSFunction>();
       innerScript = JSFunction::getOrCreateScript(cx, fun);
       if (!innerScript) {
         return false;
       }
 
       if (script->functionDelazifying() &&
--- a/js/src/vm/JSFunction.cpp
+++ b/js/src/vm/JSFunction.cpp
@@ -1215,17 +1215,17 @@ const Class JSFunction::class_ = {js_Fun
 const Class* const js::FunctionClassPtr = &JSFunction::class_;
 
 bool JSFunction::isDerivedClassConstructor() {
   bool derived;
   if (isInterpretedLazy()) {
     // There is only one plausible lazy self-hosted derived
     // constructor.
     if (isSelfHostedBuiltin()) {
-      JSAtom* name = GetSelfHostedFunctionName(this);
+      JSAtom* name = GetClonedSelfHostedFunctionName(this);
 
       // This function is called from places without access to a
       // JSContext. Trace some plumbing to get what we want.
       derived = name == compartment()
                             ->runtimeFromAnyThread()
                             ->commonNames->DefaultDerivedClassConstructor;
     } else {
       derived = lazyScript()->isDerivedClassConstructor();
@@ -1680,17 +1680,17 @@ bool JSFunction::createScriptForLazilyIn
       }
     }
 
     return true;
   }
 
   /* Lazily cloned self-hosted script. */
   MOZ_ASSERT(fun->isSelfHostedBuiltin());
-  RootedAtom funAtom(cx, GetSelfHostedFunctionName(fun));
+  RootedAtom funAtom(cx, GetClonedSelfHostedFunctionName(fun));
   if (!funAtom) {
     return false;
   }
   Rooted<PropertyName*> funName(cx, funAtom->asPropertyName());
   return cx->runtime()->cloneSelfHostedFunctionScript(cx, funName, fun);
 }
 
 void JSFunction::maybeRelazify(JSRuntime* rt) {
@@ -1744,17 +1744,17 @@ void JSFunction::maybeRelazify(JSRuntime
   flags_ |= INTERPRETED_LAZY;
   LazyScript* lazy = script->maybeLazyScript();
   u.scripted.s.lazy_ = lazy;
   if (lazy) {
     MOZ_ASSERT(!isSelfHostedBuiltin());
   } else {
     MOZ_ASSERT(isSelfHostedBuiltin());
     MOZ_ASSERT(isExtended());
-    MOZ_ASSERT(GetSelfHostedFunctionName(this));
+    MOZ_ASSERT(GetClonedSelfHostedFunctionName(this));
   }
 
   realm->scheduleDelazificationForDebugger();
 }
 
 // ES2018 draft rev 2aea8f3e617b49df06414eb062ab44fad87661d3
 // 19.2.1.1.1 CreateDynamicFunction( constructor, newTarget, kind, args )
 static bool CreateDynamicFunction(JSContext* cx, const CallArgs& args,
--- a/js/src/vm/JSFunction.h
+++ b/js/src/vm/JSFunction.h
@@ -124,21 +124,29 @@ class JSFunction : public js::NativeObje
   static_assert((INTERPRETED | INTERPRETED_LAZY) ==
                     js::JS_FUNCTION_INTERPRETED_BITS,
                 "jsfriendapi.h's JSFunction::INTERPRETED-alike is wrong");
   static_assert(((FunctionKindLimit - 1) << FUNCTION_KIND_SHIFT) <=
                     FUNCTION_KIND_MASK,
                 "FunctionKind doesn't fit into flags_");
 
  private:
-  uint16_t
-      nargs_;      /* number of formal arguments
-                      (including defaults and the rest parameter unlike f.length) */
-  uint16_t flags_; /* bitfield composed of the above Flags enum, as well as the
-                      kind */
+  /*
+   * number of formal arguments
+   * (including defaults and the rest parameter unlike f.length)
+   */
+  uint16_t nargs_;
+
+  /*
+   * Bitfield composed of the above Flags enum, as well as the kind.
+   *
+   * If any of these flags needs to be accessed in off-thread JIT
+   * compilation, copy it to js::jit::WrappedFunction.
+   */
+  uint16_t flags_;
   union U {
     class {
       friend class JSFunction;
       js::Native func_; /* native method pointer or null */
       union {
         // Information about this function to be used by the JIT, only
         // used if isBuiltinNative(); use the accessor!
         const JSJitInfo* jitInfo_;
@@ -815,16 +823,30 @@ class JSFunction : public js::NativeObje
    * the function has already been initialized. Otherwise use
    * initExtendedSlot.
    */
   inline void initializeExtended();
   inline void initExtendedSlot(size_t which, const js::Value& val);
   inline void setExtendedSlot(size_t which, const js::Value& val);
   inline const js::Value& getExtendedSlot(size_t which) const;
 
+  /*
+   * Same as `toExtended` and `getExtendedSlot`, but `this` is guaranteed to be
+   * an extended function.
+   *
+   * This function is supposed to be used off-thread, especially the JIT
+   * compilation thread, that cannot access JSFunction.flags_, because of
+   * a race condition.
+   *
+   * See Also: WrappedFunction.isExtended_
+   */
+  inline js::FunctionExtended* toExtendedOffMainThread();
+  inline const js::FunctionExtended* toExtendedOffMainThread() const;
+  inline const js::Value& getExtendedSlotOffMainThread(size_t which) const;
+
   /* Constructs a new type for the function if necessary. */
   static bool setTypeForScriptedFunction(JSContext* cx, js::HandleFunction fun,
                                          bool singleton = false);
 
   /* GC support. */
   js::gc::AllocKind getAllocKind() const {
     static_assert(
         js::gc::AllocKind::FUNCTION != js::gc::AllocKind::FUNCTION_EXTENDED,
@@ -1003,16 +1025,24 @@ inline js::FunctionExtended* JSFunction:
   return static_cast<js::FunctionExtended*>(this);
 }
 
 inline const js::FunctionExtended* JSFunction::toExtended() const {
   MOZ_ASSERT(isExtended());
   return static_cast<const js::FunctionExtended*>(this);
 }
 
+inline js::FunctionExtended* JSFunction::toExtendedOffMainThread() {
+  return static_cast<js::FunctionExtended*>(this);
+}
+
+inline const js::FunctionExtended* JSFunction::toExtendedOffMainThread() const {
+  return static_cast<const js::FunctionExtended*>(this);
+}
+
 inline void JSFunction::initializeExtended() {
   MOZ_ASSERT(isExtended());
 
   MOZ_ASSERT(mozilla::ArrayLength(toExtended()->extendedSlots) == 2);
   toExtended()->extendedSlots[0].init(js::UndefinedValue());
   toExtended()->extendedSlots[1].init(js::UndefinedValue());
 }
 
@@ -1028,16 +1058,23 @@ inline void JSFunction::setExtendedSlot(
   toExtended()->extendedSlots[which] = val;
 }
 
 inline const js::Value& JSFunction::getExtendedSlot(size_t which) const {
   MOZ_ASSERT(which < mozilla::ArrayLength(toExtended()->extendedSlots));
   return toExtended()->extendedSlots[which];
 }
 
+inline const js::Value& JSFunction::getExtendedSlotOffMainThread(
+    size_t which) const {
+  MOZ_ASSERT(which <
+             mozilla::ArrayLength(toExtendedOffMainThread()->extendedSlots));
+  return toExtendedOffMainThread()->extendedSlots[which];
+}
+
 namespace js {
 
 JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool isToSource);
 
 template <XDRMode mode>
 XDRResult XDRInterpretedFunction(XDRState<mode>* xdr,
                                  HandleScope enclosingScope,
                                  HandleScriptSourceObject sourceObject,
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -349,21 +349,21 @@ XDRResult ScopeNote::XDR(XDRState<mode>*
   MOZ_TRY(xdr->codeUint32(&index));
   MOZ_TRY(xdr->codeUint32(&start));
   MOZ_TRY(xdr->codeUint32(&length));
   MOZ_TRY(xdr->codeUint32(&parent));
 
   return Ok();
 }
 
-static inline uint32_t FindScopeIndex(mozilla::Span<const GCPtrScope> scopes,
+static inline uint32_t FindScopeIndex(mozilla::Span<const JS::GCCellPtr> scopes,
                                       Scope& scope) {
   unsigned length = scopes.size();
   for (uint32_t i = 0; i < length; ++i) {
-    if (scopes[i] == &scope) {
+    if (scopes[i].asCell() == &scope) {
       return i;
     }
   }
 
   MOZ_CRASH("Scope not found");
 }
 
 template <XDRMode mode>
@@ -418,23 +418,24 @@ static XDRResult XDRInnerObject(XDRState
         } else if (function->isInterpreted()) {
           funEnclosingScope = function->nonLazyScript()->enclosingScope();
         } else {
           MOZ_ASSERT(function->isAsmJSNative());
           return xdr->fail(JS::TranscodeResult_Failure_AsmJSNotSupported);
         }
 
         funEnclosingScopeIndex =
-            FindScopeIndex(data->scopes(), *funEnclosingScope);
+            FindScopeIndex(data->gcthings(), *funEnclosingScope);
       }
 
       MOZ_TRY(xdr->codeUint32(&funEnclosingScopeIndex));
 
       if (mode == XDR_DECODE) {
-        funEnclosingScope = data->scopes()[funEnclosingScopeIndex];
+        funEnclosingScope =
+            &data->gcthings()[funEnclosingScopeIndex].as<Scope>();
       }
 
       // Code nested function and script.
       RootedFunction tmp(cx);
       if (mode == XDR_ENCODE) {
         tmp = &inner->as<JSFunction>();
       }
       MOZ_TRY(
@@ -467,46 +468,45 @@ static XDRResult XDRInnerObject(XDRState
   }
 
   return Ok();
 }
 
 template <XDRMode mode>
 static XDRResult XDRScope(XDRState<mode>* xdr, js::PrivateScriptData* data,
                           HandleScope scriptEnclosingScope, HandleFunction fun,
-                          uint32_t scopeIndex, MutableHandleScope scope) {
+                          bool isFirstScope, MutableHandleScope scope) {
   JSContext* cx = xdr->cx();
 
   ScopeKind scopeKind;
   RootedScope enclosing(cx);
   uint32_t enclosingIndex = 0;
 
   // The enclosingScope is encoded using an integer index into the scope array.
   // This means that scopes must be topologically sorted.
   if (mode == XDR_ENCODE) {
     scopeKind = scope->kind();
 
-    if (scopeIndex == 0) {
+    if (isFirstScope) {
       enclosingIndex = UINT32_MAX;
     } else {
       MOZ_ASSERT(scope->enclosing());
-      enclosingIndex = FindScopeIndex(data->scopes(), *scope->enclosing());
+      enclosingIndex = FindScopeIndex(data->gcthings(), *scope->enclosing());
     }
   }
 
   MOZ_TRY(xdr->codeEnum32(&scopeKind));
   MOZ_TRY(xdr->codeUint32(&enclosingIndex));
 
   if (mode == XDR_DECODE) {
-    if (scopeIndex == 0) {
+    if (isFirstScope) {
       MOZ_ASSERT(enclosingIndex == UINT32_MAX);
       enclosing = scriptEnclosingScope;
     } else {
-      MOZ_ASSERT(enclosingIndex < scopeIndex);
-      enclosing = data->scopes()[enclosingIndex];
+      enclosing = &data->gcthings()[enclosingIndex].as<Scope>();
     }
   }
 
   switch (scopeKind) {
     case ScopeKind::Function:
       MOZ_TRY(FunctionScope::XDR(xdr, fun, enclosing, scope));
       break;
     case ScopeKind::FunctionBodyVar:
@@ -543,120 +543,136 @@ static XDRResult XDRScope(XDRState<mode>
       MOZ_ASSERT(false, "Bad XDR scope kind");
       return xdr->fail(JS::TranscodeResult_Failure_BadDecode);
   }
 
   return Ok();
 }
 
 template <XDRMode mode>
+static XDRResult XDRScriptGCThing(XDRState<mode>* xdr, PrivateScriptData* data,
+                                  HandleScriptSourceObject sourceObject,
+                                  HandleScope scriptEnclosingScope,
+                                  HandleFunction fun, bool* isFirstScope,
+                                  JS::GCCellPtr* thingp) {
+  JSContext* cx = xdr->cx();
+
+  enum class GCThingTag { Object, Scope, BigInt };
+
+  JS::GCCellPtr thing;
+
+  GCThingTag tag;
+  if (mode == XDR_ENCODE) {
+    thing = *thingp;
+    if (thing.is<JSObject>()) {
+      tag = GCThingTag::Object;
+    } else if (thing.is<Scope>()) {
+      tag = GCThingTag::Scope;
+    } else {
+      MOZ_ASSERT(thing.is<BigInt>());
+      tag = GCThingTag::BigInt;
+    }
+  }
+
+  MOZ_TRY(xdr->codeEnum32(&tag));
+
+  switch (tag) {
+    case GCThingTag::Object: {
+      RootedObject obj(cx);
+      if (mode == XDR_ENCODE) {
+        obj = &thing.as<JSObject>();
+      }
+      MOZ_TRY(XDRInnerObject(xdr, data, sourceObject, &obj));
+      if (mode == XDR_DECODE) {
+        *thingp = JS::GCCellPtr(obj.get());
+      }
+      break;
+    }
+    case GCThingTag::Scope: {
+      RootedScope scope(cx);
+      if (mode == XDR_ENCODE) {
+        scope = &thing.as<Scope>();
+      }
+      MOZ_TRY(XDRScope(xdr, data, scriptEnclosingScope, fun, *isFirstScope,
+                       &scope));
+      if (mode == XDR_DECODE) {
+        *thingp = JS::GCCellPtr(scope.get());
+      }
+      *isFirstScope = false;
+      break;
+    }
+    case GCThingTag::BigInt: {
+      RootedBigInt bi(cx);
+      if (mode == XDR_ENCODE) {
+        bi = &thing.as<BigInt>();
+      }
+      MOZ_TRY(XDRBigInt(xdr, &bi));
+      if (mode == XDR_DECODE) {
+        *thingp = JS::GCCellPtr(bi.get());
+      }
+      break;
+    }
+    default:
+      // Fail in debug, but only soft-fail in release.
+      MOZ_ASSERT(false, "Bad XDR GCThingTag");
+      return xdr->fail(JS::TranscodeResult_Failure_BadDecode);
+  }
+  return Ok();
+}
+
+template <XDRMode mode>
 /* static */
 XDRResult js::PrivateScriptData::XDR(XDRState<mode>* xdr, HandleScript script,
                                      HandleScriptSourceObject sourceObject,
                                      HandleScope scriptEnclosingScope,
                                      HandleFunction fun) {
-  uint32_t nscopes = 0;
-  uint32_t nbigints = 0;
-  uint32_t nobjects = 0;
+  uint32_t ngcthings = 0;
   uint32_t ntrynotes = 0;
   uint32_t nscopenotes = 0;
   uint32_t nresumeoffsets = 0;
 
   JSContext* cx = xdr->cx();
   PrivateScriptData* data = nullptr;
 
   if (mode == XDR_ENCODE) {
     data = script->data_;
 
-    nscopes = data->scopes().size();
-    if (data->hasBigInts()) {
-      nbigints = data->bigints().size();
-    }
-    if (data->hasObjects()) {
-      nobjects = data->objects().size();
-    }
+    ngcthings = data->gcthings().size();
     if (data->hasTryNotes()) {
       ntrynotes = data->tryNotes().size();
     }
     if (data->hasScopeNotes()) {
       nscopenotes = data->scopeNotes().size();
     }
     if (data->hasResumeOffsets()) {
       nresumeoffsets = data->resumeOffsets().size();
     }
   }
 
-  MOZ_TRY(xdr->codeUint32(&nscopes));
-  MOZ_TRY(xdr->codeUint32(&nbigints));
-  MOZ_TRY(xdr->codeUint32(&nobjects));
+  MOZ_TRY(xdr->codeUint32(&ngcthings));
   MOZ_TRY(xdr->codeUint32(&ntrynotes));
   MOZ_TRY(xdr->codeUint32(&nscopenotes));
   MOZ_TRY(xdr->codeUint32(&nresumeoffsets));
 
   if (mode == XDR_DECODE) {
-    if (!JSScript::createPrivateScriptData(cx, script, nscopes, nbigints,
-                                           nobjects, ntrynotes, nscopenotes,
-                                           nresumeoffsets)) {
+    if (!JSScript::createPrivateScriptData(cx, script, ngcthings, ntrynotes,
+                                           nscopenotes, nresumeoffsets)) {
       return xdr->fail(JS::TranscodeResult_Throw);
     }
 
     data = script->data_;
   }
 
-  if (nbigints > 0) {
-    RootedBigInt bi(cx);
-    for (GCPtrBigInt& elem : data->bigints()) {
-      if (mode == XDR_ENCODE) {
-        bi = elem.get();
-      }
-      MOZ_TRY(XDRBigInt(xdr, &bi));
-      if (mode == XDR_DECODE) {
-        elem.init(bi);
-      }
-    }
-  }
-
-  {
-    MOZ_ASSERT(nscopes > 0);
-    GCPtrScope* vector = data->scopes().data();
-    for (uint32_t i = 0; i < nscopes; ++i) {
-      RootedScope scope(cx);
-      if (mode == XDR_ENCODE) {
-        scope = vector[i];
-      }
-      MOZ_TRY(XDRScope(xdr, data, scriptEnclosingScope, fun, i, &scope));
-      if (mode == XDR_DECODE) {
-        vector[i].init(scope);
-      }
-    }
-
-    // Verify marker to detect data corruption after decoding scope data. A
-    // mismatch here indicates we will almost certainly crash in release.
-    MOZ_TRY(xdr->codeMarker(0x48922BAB));
-  }
-
-  /*
-   * Here looping from 0-to-length to xdr objects is essential to ensure that
-   * all references to enclosing blocks (via FindScopeIndex below) happen
-   * after the enclosing block has been XDR'd.
-   */
-  if (nobjects) {
-    for (GCPtrObject& elem : data->objects()) {
-      RootedObject inner(cx);
-      if (mode == XDR_ENCODE) {
-        inner = elem;
-      }
-      MOZ_TRY(XDRInnerObject(xdr, data, sourceObject, &inner));
-      if (mode == XDR_DECODE) {
-        elem.init(inner);
-      }
-    }
-  }
-
-  // Verify marker to detect data corruption after decoding object data. A
+  bool isFirstScope = true;
+  for (JS::GCCellPtr& gcThing : data->gcthings()) {
+    MOZ_TRY(XDRScriptGCThing(xdr, data, sourceObject, scriptEnclosingScope, fun,
+                             &isFirstScope, &gcThing));
+  }
+
+  // Verify marker to detect data corruption after decoding GC things. A
   // mismatch here indicates we will almost certainly crash in release.
   MOZ_TRY(xdr->codeMarker(0xF83B989A));
 
   if (ntrynotes) {
     for (JSTryNote& elem : data->tryNotes()) {
       MOZ_TRY(elem.XDR(xdr));
     }
   }
@@ -3411,46 +3427,33 @@ void js::FreeScriptData(JSRuntime* rt) {
             numLive);
   }
 #endif
 
   table.clear();
 }
 
 /* static */
-size_t PrivateScriptData::AllocationSize(uint32_t nscopes, uint32_t nbigints,
-                                         uint32_t nobjects, uint32_t ntrynotes,
+size_t PrivateScriptData::AllocationSize(uint32_t ngcthings, uint32_t ntrynotes,
                                          uint32_t nscopenotes,
                                          uint32_t nresumeoffsets) {
   size_t size = sizeof(PrivateScriptData);
 
-  if (nbigints) {
-    size += sizeof(PackedSpan);
-  }
-  if (nobjects) {
-    size += sizeof(PackedSpan);
-  }
   if (ntrynotes) {
     size += sizeof(PackedSpan);
   }
   if (nscopenotes) {
     size += sizeof(PackedSpan);
   }
   if (nresumeoffsets) {
     size += sizeof(PackedSpan);
   }
 
-  size += nscopes * sizeof(GCPtrScope);
-
-  if (nbigints) {
-    size += nbigints * sizeof(GCPtrBigInt);
-  }
-  if (nobjects) {
-    size += nobjects * sizeof(GCPtrObject);
-  }
+  size += ngcthings * sizeof(JS::GCCellPtr);
+
   if (ntrynotes) {
     size += ntrynotes * sizeof(JSTryNote);
   }
   if (nscopenotes) {
     size += nscopenotes * sizeof(ScopeNote);
   }
   if (nresumeoffsets) {
     size += nresumeoffsets * sizeof(uint32_t);
@@ -3483,21 +3486,20 @@ void PrivateScriptData::initSpan(size_t*
   // Placement-new the elements
   initElements<T>(*cursor, length);
 
   // Advance cursor
   (*cursor) += length * sizeof(T);
 }
 
 // Initialize PackedSpans and placement-new the trailing arrays.
-PrivateScriptData::PrivateScriptData(uint32_t nscopes_, uint32_t nbigints,
-                                     uint32_t nobjects, uint32_t ntrynotes,
+PrivateScriptData::PrivateScriptData(uint32_t ngcthings, uint32_t ntrynotes,
                                      uint32_t nscopenotes,
                                      uint32_t nresumeoffsets)
-    : nscopes(nscopes_) {
+    : ngcthings(ngcthings) {
   // Convert cursor possition to a packed offset.
   auto ToPackedOffset = [](size_t cursor) {
     MOZ_ASSERT(cursor % PackedOffsets::SCALE == 0);
     return cursor / PackedOffsets::SCALE;
   };
 
   // Helper to allocate a PackedSpan from the variable length data.
   auto TakeSpan = [=](size_t* cursor) {
@@ -3511,150 +3513,119 @@ PrivateScriptData::PrivateScriptData(uin
   // Variable-length data begins immediately after PrivateScriptData itself.
   // NOTE: Alignment is computed using cursor/offset so the alignment of
   // PrivateScriptData must be stricter than any trailing array type.
   size_t cursor = sizeof(*this);
 
   // Layout PackedSpan structures and initialize packedOffsets fields.
   static_assert(alignof(PrivateScriptData) >= alignof(PackedSpan),
                 "Incompatible alignment");
-  if (nbigints) {
-    packedOffsets.bigintsSpanOffset = TakeSpan(&cursor);
-  }
-  if (nobjects) {
-    packedOffsets.objectsSpanOffset = TakeSpan(&cursor);
-  }
   if (ntrynotes) {
     packedOffsets.tryNotesSpanOffset = TakeSpan(&cursor);
   }
   if (nscopenotes) {
     packedOffsets.scopeNotesSpanOffset = TakeSpan(&cursor);
   }
   if (nresumeoffsets) {
     packedOffsets.resumeOffsetsSpanOffset = TakeSpan(&cursor);
   }
 
-  // Layout and initialize the scopes array.
+  // Layout and initialize the gcthings array.
   {
-    MOZ_ASSERT(nscopes > 0);
-
-    static_assert(alignof(PackedSpan) >= alignof(GCPtrScope),
+    MOZ_ASSERT(ngcthings > 0);
+
+    static_assert(alignof(PackedSpan) >= alignof(JS::GCCellPtr),
                   "Incompatible alignment");
-    initElements<GCPtrScope>(cursor, nscopes);
-    packedOffsets.scopesOffset = ToPackedOffset(cursor);
-
-    cursor += nscopes * sizeof(GCPtrScope);
+    initElements<JS::GCCellPtr>(cursor, ngcthings);
+    packedOffsets.gcthingsOffset = ToPackedOffset(cursor);
+
+    cursor += ngcthings * sizeof(JS::GCCellPtr);
   }
 
   // Layout arrays, initialize PackedSpans and placement-new the elements.
-  static_assert(alignof(PrivateScriptData) >= alignof(GCPtrBigInt),
-                "Incompatible alignment");
-  static_assert(alignof(GCPtrScope) >= alignof(GCPtrBigInt),
-                "Incompatible alignment");
-  initSpan<GCPtrBigInt>(&cursor, packedOffsets.bigintsSpanOffset, nbigints);
-  static_assert(alignof(GCPtrBigInt) >= alignof(GCPtrObject),
-                "Incompatible alignment");
-  static_assert(alignof(GCPtrScope) >= alignof(GCPtrObject),
-                "Incompatible alignment");
-  initSpan<GCPtrObject>(&cursor, packedOffsets.objectsSpanOffset, nobjects);
-  static_assert(alignof(GCPtrObject) >= alignof(JSTryNote),
+  static_assert(alignof(JS::GCCellPtr) >= alignof(JSTryNote),
                 "Incompatible alignment");
   initSpan<JSTryNote>(&cursor, packedOffsets.tryNotesSpanOffset, ntrynotes);
   static_assert(alignof(JSTryNote) >= alignof(ScopeNote),
                 "Incompatible alignment");
   initSpan<ScopeNote>(&cursor, packedOffsets.scopeNotesSpanOffset, nscopenotes);
   static_assert(alignof(ScopeNote) >= alignof(uint32_t),
                 "Incompatible alignment");
   initSpan<uint32_t>(&cursor, packedOffsets.resumeOffsetsSpanOffset,
                      nresumeoffsets);
 
   // Sanity check
-  MOZ_ASSERT(AllocationSize(nscopes_, nbigints, nobjects, ntrynotes,
-                            nscopenotes, nresumeoffsets) == cursor);
+  MOZ_ASSERT(AllocationSize(ngcthings, ntrynotes, nscopenotes,
+                            nresumeoffsets) == cursor);
 }
 
 /* static */
-PrivateScriptData* PrivateScriptData::new_(JSContext* cx, uint32_t nscopes,
-                                           uint32_t nbigints, uint32_t nobjects,
+PrivateScriptData* PrivateScriptData::new_(JSContext* cx, uint32_t ngcthings,
                                            uint32_t ntrynotes,
                                            uint32_t nscopenotes,
                                            uint32_t nresumeoffsets,
                                            uint32_t* dataSize) {
   // Compute size including trailing arrays
-  size_t size = AllocationSize(nscopes, nbigints, nobjects, ntrynotes,
-                               nscopenotes, nresumeoffsets);
+  size_t size =
+      AllocationSize(ngcthings, ntrynotes, nscopenotes, nresumeoffsets);
 
   // Allocate contiguous raw buffer
   void* raw = cx->pod_malloc<uint8_t>(size);
   MOZ_ASSERT(uintptr_t(raw) % alignof(PrivateScriptData) == 0);
   if (!raw) {
     return nullptr;
   }
 
   if (dataSize) {
     *dataSize = size;
   }
 
   // Constuct the PrivateScriptData. Trailing arrays are uninitialized but
   // GCPtrs are put into a safe state.
-  return new (raw) PrivateScriptData(nscopes, nbigints, nobjects, ntrynotes,
-                                     nscopenotes, nresumeoffsets);
+  return new (raw)
+      PrivateScriptData(ngcthings, ntrynotes, nscopenotes, nresumeoffsets);
 }
 
 /* static */ bool PrivateScriptData::InitFromEmitter(
     JSContext* cx, js::HandleScript script, frontend::BytecodeEmitter* bce) {
-  uint32_t nscopes = bce->perScriptData().scopeList().length();
-  uint32_t nbigints = bce->perScriptData().bigIntList().length();
-  uint32_t nobjects = bce->perScriptData().objectList().length;
+  uint32_t ngcthings = bce->perScriptData().gcThingList().length();
   uint32_t ntrynotes = bce->bytecodeSection().tryNoteList().length();
   uint32_t nscopenotes = bce->bytecodeSection().scopeNoteList().length();
   uint32_t nresumeoffsets = bce->bytecodeSection().resumeOffsetList().length();
 
   // Create and initialize PrivateScriptData
-  if (!JSScript::createPrivateScriptData(cx, script, nscopes, nbigints,
-                                         nobjects, ntrynotes, nscopenotes,
-                                         nresumeoffsets)) {
+  if (!JSScript::createPrivateScriptData(cx, script, ngcthings, ntrynotes,
+                                         nscopenotes, nresumeoffsets)) {
     return false;
   }
 
   js::PrivateScriptData* data = script->data_;
-  if (nscopes) {
-    bce->perScriptData().scopeList().finish(data->scopes());
-  }
-  if (nbigints) {
-    bce->perScriptData().bigIntList().finish(data->bigints());
-  }
-  if (nobjects) {
-    bce->perScriptData().objectList().finish(data->objects());
+  if (ngcthings) {
+    bce->perScriptData().gcThingList().finish(data->gcthings());
   }
   if (ntrynotes) {
     bce->bytecodeSection().tryNoteList().finish(data->tryNotes());
   }
   if (nscopenotes) {
     bce->bytecodeSection().scopeNoteList().finish(data->scopeNotes());
   }
   if (nresumeoffsets) {
     bce->bytecodeSection().resumeOffsetList().finish(data->resumeOffsets());
   }
 
   return true;
 }
 
 void PrivateScriptData::trace(JSTracer* trc) {
-  auto scopearray = scopes();
-  TraceRange(trc, scopearray.size(), scopearray.data(), "scopes");
-
-  if (hasBigInts()) {
-    auto bigintarray = bigints();
-    TraceRange(trc, bigintarray.size(), bigintarray.data(), "bigints");
-  }
-
-  if (hasObjects()) {
-    auto objarray = objects();
-    TraceRange(trc, objarray.size(), objarray.data(), "objects");
+  for (JS::GCCellPtr& elem : gcthings()) {
+    gc::Cell* thing = elem.asCell();
+    TraceManuallyBarrieredGenericPointerEdge(trc, &thing, "script-gcthing");
+    if (thing != elem.asCell()) {
+      elem = JS::GCCellPtr(thing, elem.kind());
+    }
   }
 }
 
 JSScript::JSScript(JS::Realm* realm, uint8_t* stubEntry,
                    HandleScriptSourceObject sourceObject, uint32_t sourceStart,
                    uint32_t sourceEnd, uint32_t toStringStart,
                    uint32_t toStringEnd)
     :
@@ -3789,62 +3760,58 @@ bool JSScript::initScriptName(JSContext*
     return false;
   }
 
   return true;
 }
 
 /* static */
 bool JSScript::createPrivateScriptData(JSContext* cx, HandleScript script,
-                                       uint32_t nscopes, uint32_t nbigints,
-                                       uint32_t nobjects, uint32_t ntrynotes,
+                                       uint32_t ngcthings, uint32_t ntrynotes,
                                        uint32_t nscopenotes,
                                        uint32_t nresumeoffsets) {
   cx->check(script);
   MOZ_ASSERT(!script->data_);
 
   uint32_t dataSize;
 
-  PrivateScriptData* data =
-      PrivateScriptData::new_(cx, nscopes, nbigints, nobjects, ntrynotes,
-                              nscopenotes, nresumeoffsets, &dataSize);
+  PrivateScriptData* data = PrivateScriptData::new_(
+      cx, ngcthings, ntrynotes, nscopenotes, nresumeoffsets, &dataSize);
   if (!data) {
     return false;
   }
 
   script->data_ = data;
   script->dataSize_ = dataSize;
   AddCellMemory(script, dataSize, MemoryUse::ScriptPrivateData);
 
   return true;
 }
 
 /* static */
 bool JSScript::initFunctionPrototype(JSContext* cx, HandleScript script,
                                      HandleFunction functionProto) {
-  uint32_t numScopes = 1;
-  uint32_t numBigInts = 0;
-  uint32_t numObjects = 0;
+  uint32_t numGCThings = 1;
   uint32_t numTryNotes = 0;
   uint32_t numScopeNotes = 0;
   uint32_t nresumeoffsets = 0;
-  if (!createPrivateScriptData(cx, script, numScopes, numBigInts, numObjects,
-                               numTryNotes, numScopeNotes, nresumeoffsets)) {
+  if (!createPrivateScriptData(cx, script, numGCThings, numTryNotes,
+                               numScopeNotes, nresumeoffsets)) {
     return false;
   }
 
   RootedScope enclosing(cx, &cx->global()->emptyGlobalScope());
   Scope* functionProtoScope = FunctionScope::create(cx, nullptr, false, false,
                                                     functionProto, enclosing);
   if (!functionProtoScope) {
     return false;
   }
 
-  mozilla::Span<GCPtrScope> scopes = script->data_->scopes();
-  scopes[0].init(functionProtoScope);
+  mozilla::Span<JS::GCCellPtr> gcthings = script->data_->gcthings();
+  gcthings[0] = JS::GCCellPtr(functionProtoScope);
 
   uint32_t codeLength = 1;
   uint32_t noteLength = 1;
   uint32_t numAtoms = 0;
   if (!script->createSharedScriptData(cx, codeLength, noteLength, numAtoms)) {
     return false;
   }
 
@@ -3922,17 +3889,17 @@ bool JSScript::fullyInitFromEmitter(JSCo
   // If initialization fails, we must call JSScript::freeScriptData in order to
   // neuter the script. Various things that iterate raw scripts in a GC arena
   // use the presense of this data to detect if initialization is complete.
   auto scriptDataGuard =
       mozilla::MakeScopeExit([&] { script->freeScriptData(); });
 
   /* The counts of indexed things must be checked during code generation. */
   MOZ_ASSERT(bce->perScriptData().atomIndices()->count() <= INDEX_LIMIT);
-  MOZ_ASSERT(bce->perScriptData().objectList().length <= INDEX_LIMIT);
+  MOZ_ASSERT(bce->perScriptData().gcThingList().length() <= INDEX_LIMIT);
 
   uint64_t nslots =
       bce->maxFixedSlots +
       static_cast<uint64_t>(bce->bytecodeSection().maxStackDepth());
   if (nslots > UINT32_MAX) {
     bce->reportError(nullptr, JSMSG_NEED_DIET, js_script_str);
     return false;
   }
@@ -3981,17 +3948,17 @@ bool JSScript::fullyInitFromEmitter(JSCo
     } else {
       fun->setScript(script);
     }
   }
 
   // Part of the parse result – the scope containing each inner function – must
   // be stored in the inner function itself. Do this now that compilation is
   // complete and can no longer fail.
-  bce->perScriptData().objectList().finishInnerFunctions();
+  bce->perScriptData().gcThingList().finishInnerFunctions();
 
 #ifdef JS_STRUCTURED_SPEW
   // We want this to happen after line number initialization to allow filtering
   // to work.
   script->setSpewEnabled(cx->spewer().enabled(script));
 #endif
 
 #ifdef DEBUG
@@ -4400,142 +4367,125 @@ static JSObject* CloneInnerInterpretedFu
 
   if (!JSFunction::setTypeForScriptedFunction(cx, clone)) {
     return nullptr;
   }
 
   return clone;
 }
 
+static JSObject* CloneScriptObject(JSContext* cx, PrivateScriptData* srcData,
+                                   HandleObject obj,
+                                   Handle<ScriptSourceObject*> sourceObject,
+                                   JS::HandleVector<StackGCCellPtr> gcThings) {
+  if (obj->is<RegExpObject>()) {
+    return CloneScriptRegExpObject(cx, obj->as<RegExpObject>());
+  }
+
+  if (obj->is<JSFunction>()) {
+    HandleFunction innerFun = obj.as<JSFunction>();
+    if (innerFun->isNative()) {
+      if (cx->realm() != innerFun->realm()) {
+        MOZ_ASSERT(innerFun->isAsmJSNative());
+        JS_ReportErrorASCII(cx, "AsmJS modules do not yet support cloning.");
+        return nullptr;
+      }
+      return innerFun;
+    }
+
+    if (innerFun->isInterpretedLazy()) {
+      AutoRealm ar(cx, innerFun);
+      if (!JSFunction::getOrCreateScript(cx, innerFun)) {
+        return nullptr;
+      }
+    }
+
+    Scope* enclosing = innerFun->nonLazyScript()->enclosingScope();
+    uint32_t scopeIndex = FindScopeIndex(srcData->gcthings(), *enclosing);
+    RootedScope enclosingClone(cx,
+                               &gcThings[scopeIndex].get().get().as<Scope>());
+    return CloneInnerInterpretedFunction(cx, enclosingClone, innerFun,
+                                         sourceObject);
+  }
+
+  return DeepCloneObjectLiteral(cx, obj, TenuredObject);
+}
+
 /* static */
 bool PrivateScriptData::Clone(JSContext* cx, HandleScript src, HandleScript dst,
                               MutableHandle<GCVector<Scope*>> scopes) {
   PrivateScriptData* srcData = src->data_;
-  uint32_t nscopes = srcData->scopes().size();
-  uint32_t nbigints = srcData->hasBigInts() ? srcData->bigints().size() : 0;
-  uint32_t nobjects = srcData->hasObjects() ? srcData->objects().size() : 0;
+  uint32_t ngcthings = srcData->gcthings().size();
   uint32_t ntrynotes = srcData->hasTryNotes() ? srcData->tryNotes().size() : 0;
   uint32_t nscopenotes =
       srcData->hasScopeNotes() ? srcData->scopeNotes().size() : 0;
   uint32_t nresumeoffsets =
       srcData->hasResumeOffsets() ? srcData->resumeOffsets().size() : 0;
 
-  /* Scopes */
-
-  // The passed in scopes vector contains body scopes that needed to be
-  // cloned especially, depending on whether the script is a function or
-  // global scope. Starting at scopes.length() means we only deal with
-  // intra-body scopes.
-  {
-    MOZ_ASSERT(nscopes != 0);
-    MOZ_ASSERT(src->bodyScopeIndex() + 1 == scopes.length());
-    RootedScope original(cx);
-    RootedScope clone(cx);
-    for (const GCPtrScope& elem : srcData->scopes().From(scopes.length())) {
-      original = elem.get();
-      uint32_t scopeIndex =
-          FindScopeIndex(srcData->scopes(), *original->enclosing());
-      clone = Scope::clone(cx, original, scopes[scopeIndex]);
-      if (!clone || !scopes.append(clone)) {
+  // Clone GC things.
+  JS::RootedVector<StackGCCellPtr> gcThings(cx);
+  size_t scopeIndex = 0;
+  Rooted<ScriptSourceObject*> sourceObject(cx, dst->sourceObject());
+  RootedObject obj(cx);
+  RootedScope scope(cx);
+  RootedScope enclosingScope(cx);
+  RootedBigInt bigint(cx);
+  for (JS::GCCellPtr gcThing : srcData->gcthings()) {
+    if (gcThing.is<JSObject>()) {
+      obj = &gcThing.as<JSObject>();
+      JSObject* clone =
+          CloneScriptObject(cx, srcData, obj, sourceObject, gcThings);
+      if (!clone || !gcThings.append(JS::GCCellPtr(clone))) {
         return false;
       }
-    }
-  }
-
-  /* BigInts */
-
-  Rooted<BigIntVector> bigints(cx);
-  if (nbigints != 0) {
-    RootedBigInt clone(cx);
-    for (const GCPtrBigInt& elem : srcData->bigints()) {
-      if (cx->zone() == elem->zone()) {
-        clone = elem;
+    } else if (gcThing.is<Scope>()) {
+      // The passed in scopes vector contains body scopes that needed to be
+      // cloned especially, depending on whether the script is a function or
+      // global scope. Clone all other scopes.
+      if (scopeIndex < scopes.length()) {
+        if (!gcThings.append(JS::GCCellPtr(scopes[scopeIndex].get()))) {
+          return false;
+        }
       } else {
-        RootedBigInt b(cx, elem);
-        clone = BigInt::copy(cx, b);
+        scope = &gcThing.as<Scope>();
+        uint32_t enclosingScopeIndex =
+            FindScopeIndex(srcData->gcthings(), *scope->enclosing());
+        enclosingScope = &gcThings[enclosingScopeIndex].get().get().as<Scope>();
+        Scope* clone = Scope::clone(cx, scope, enclosingScope);
+        if (!clone || !gcThings.append(JS::GCCellPtr(clone))) {
+          return false;
+        }
+      }
+      scopeIndex++;
+    } else {
+      bigint = &gcThing.as<BigInt>();
+      BigInt* clone = bigint;
+      if (cx->zone() != bigint->zone()) {
+        clone = BigInt::copy(cx, bigint);
         if (!clone) {
           return false;
         }
       }
-      if (!bigints.append(clone)) {
-        return false;
-      }
-    }
-  }
-
-  /* Objects */
-
-  RootedObjectVector objects(cx);
-  if (nobjects != 0) {
-    RootedObject obj(cx);
-    RootedObject clone(cx);
-    Rooted<ScriptSourceObject*> sourceObject(cx, dst->sourceObject());
-    for (const GCPtrObject& elem : srcData->objects()) {
-      obj = elem.get();
-      clone = nullptr;
-      if (obj->is<RegExpObject>()) {
-        clone = CloneScriptRegExpObject(cx, obj->as<RegExpObject>());
-      } else if (obj->is<JSFunction>()) {
-        RootedFunction innerFun(cx, &obj->as<JSFunction>());
-        if (innerFun->isNative()) {
-          if (cx->realm() != innerFun->realm()) {
-            MOZ_ASSERT(innerFun->isAsmJSNative());
-            JS_ReportErrorASCII(cx,
-                                "AsmJS modules do not yet support cloning.");
-            return false;
-          }
-          clone = innerFun;
-        } else {
-          if (innerFun->isInterpretedLazy()) {
-            AutoRealm ar(cx, innerFun);
-            if (!JSFunction::getOrCreateScript(cx, innerFun)) {
-              return false;
-            }
-          }
-
-          Scope* enclosing = innerFun->nonLazyScript()->enclosingScope();
-          uint32_t scopeIndex = FindScopeIndex(srcData->scopes(), *enclosing);
-          RootedScope enclosingClone(cx, scopes[scopeIndex]);
-          clone = CloneInnerInterpretedFunction(cx, enclosingClone, innerFun,
-                                                sourceObject);
-        }
-      } else {
-        clone = DeepCloneObjectLiteral(cx, obj, TenuredObject);
-      }
-
-      if (!clone || !objects.append(clone)) {
+      if (!gcThings.append(JS::GCCellPtr(clone))) {
         return false;
       }
     }
   }
 
   // Create the new PrivateScriptData on |dst| and fill it in.
-  if (!JSScript::createPrivateScriptData(cx, dst, nscopes, nbigints, nobjects,
-                                         ntrynotes, nscopenotes,
-                                         nresumeoffsets)) {
+  if (!JSScript::createPrivateScriptData(cx, dst, ngcthings, ntrynotes,
+                                         nscopenotes, nresumeoffsets)) {
     return false;
   }
 
   PrivateScriptData* dstData = dst->data_;
   {
-    auto array = dstData->scopes();
-    for (uint32_t i = 0; i < nscopes; ++i) {
-      array[i].init(scopes[i]);
-    }
-  }
-  if (nbigints) {
-    auto array = dstData->bigints();
-    for (unsigned i = 0; i < nbigints; ++i) {
-      array[i].init(bigints[i]);
-    }
-  }
-  if (nobjects) {
-    auto array = dstData->objects();
-    for (unsigned i = 0; i < nobjects; ++i) {
-      array[i].init(objects[i]);
+    auto array = dstData->gcthings();
+    for (uint32_t i = 0; i < ngcthings; ++i) {
+      array[i] = gcThings[i].get().get();
     }
   }
   if (ntrynotes) {
     std::copy_n(srcData->tryNotes().begin(), ntrynotes,
                 dstData->tryNotes().begin());
   }
   if (nscopenotes) {
     std::copy_n(srcData->scopeNotes().begin(), nscopenotes,
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -30,16 +30,17 @@
 #include "gc/Barrier.h"
 #include "gc/Rooting.h"
 #include "jit/IonCode.h"
 #include "js/CompileOptions.h"
 #include "js/UbiNode.h"
 #include "js/UniquePtr.h"
 #include "js/Utility.h"
 #include "util/StructuredSpewer.h"
+#include "vm/BigIntType.h"
 #include "vm/BytecodeIterator.h"
 #include "vm/BytecodeLocation.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/JSAtom.h"
 #include "vm/NativeObject.h"
 #include "vm/Scope.h"
 #include "vm/Shape.h"
 #include "vm/SharedImmutableStringsCache.h"
@@ -1428,76 +1429,65 @@ XDRResult XDRLazyScript(XDRState<mode>* 
 template <XDRMode mode>
 XDRResult XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp);
 
 // [SMDOC] - JSScript data layout (unshared)
 //
 // PrivateScriptData stores variable-length data associated with a script.
 // Abstractly a PrivateScriptData consists of all these arrays:
 //
-//   * A non-empty array of GCPtrScope in scopes()
-//   * A possibly-empty array of GCPtrBigInt in bigints()
-//   * A possibly-empty array of JSObject* in objects()
+//   * A non-empty array of GCCellPtr in gcthings()
 //   * A possibly-empty array of JSTryNote in tryNotes()
 //   * A possibly-empty array of ScopeNote in scopeNotes()
 //   * A possibly-empty array of uint32_t in resumeOffsets()
 //
 // Accessing any of these arrays just requires calling the appropriate public
 // Span-computing function.
 //
 // Under the hood, PrivateScriptData is a small class followed by a memory
 // layout that compactly encodes all these arrays, in this manner (only
 // explicit padding, "--" separators for readability only):
 //
 //   <PrivateScriptData itself>
 //   --
-//   (OPTIONAL) PackedSpan for bigints()
-//   (OPTIONAL) PackedSpan for objects()
-//   (OPTIONAL) PackedSpan for tryNotes()
-//   (OPTIONAL) PackedSpan for scopeNotes()
-//   (OPTIONAL) PackedSpan for resumeOffsets()
+//   (OPTIONAL) PackedSpan for gcthings()
 //   --
-//   (REQUIRED) All the GCPtrScopes that constitute scopes()
-//   --
-//   (OPTIONAL) All the GCPtrBigInts that constitute bigints()
-//   --
-//   (OPTIONAL) All the GCPtrObjects that constitute objects()
+//   (REQUIRED) All the GCCellPtrs that constitute gcthings()
 //   --
 //   (OPTIONAL) All the JSTryNotes that constitute tryNotes()
 //   --
 //   (OPTIONAL) All the ScopeNotes that constitute scopeNotes()
 //   --
 //   (OPTIONAL) All the uint32_t's that constitute resumeOffsets()
 //
 // The contents of PrivateScriptData indicate which optional items are present.
 // PrivateScriptData::packedOffsets contains bit-fields, one per array.
 // Multiply each packed offset by sizeof(uint32_t) to compute a *real* offset.
 //
-// PrivateScriptData::scopesOffset indicates where scopes() begins. The bound
-// of five PackedSpans ensures we can encode this offset compactly.
-// PrivateScriptData::nscopes indicates the number of GCPtrScopes in scopes().
+// PrivateScriptData::gcthingsOffset indicates where gcthings() begins. The
+// bound of five PackedSpans ensures we can encode this offset compactly.
+// PrivateScriptData::ngcthings indicates the number of GCCellPtrs in
+// gcthings().
 //
 // The other PackedScriptData::*Offset fields indicate where a potential
 // corresponding PackedSpan resides. If the packed offset is 0, there is no
 // PackedSpan, and the array is empty. Otherwise the PackedSpan's uint32_t
 // offset and length fields store: 1) a *non-packed* offset (a literal count of
 // bytes offset from the *start* of PrivateScriptData struct) to the
 // corresponding array, and 2) the number of elements in the array,
 // respectively.
 class alignas(uintptr_t) PrivateScriptData final {
   struct PackedOffsets {
     static constexpr size_t SCALE = sizeof(uint32_t);
     static constexpr size_t MAX_OFFSET = 0b1111;
 
-    // (Scaled) offset to Scopes
-    uint32_t scopesOffset : 8;
+    // (Scaled) offset to GC things.
+    uint32_t gcthingsOffset : 8;
 
     // (Scaled) offset to Spans. These are set to 0 if they don't exist.
-    uint32_t bigintsSpanOffset : 4;
-    uint32_t objectsSpanOffset : 4;
     uint32_t tryNotesSpanOffset : 4;
     uint32_t scopeNotesSpanOffset : 4;
     uint32_t resumeOffsetsSpanOffset : 4;
   };
 
   // Detect accidental size regressions.
   static_assert(sizeof(PackedOffsets) == sizeof(uint32_t),
                 "unexpected bit-field packing");
@@ -1506,17 +1496,17 @@ class alignas(uintptr_t) PrivateScriptDa
   // the private data.
   struct alignas(uintptr_t) PackedSpan {
     uint32_t offset;
     uint32_t length;
   };
 
   // Concrete Fields
   PackedOffsets packedOffsets = {};  // zeroes
-  uint32_t nscopes = 0;
+  uint32_t ngcthings = 0;
 
   js::FieldInitializers fieldInitializers_ = js::FieldInitializers::Invalid();
 
   // Translate an offset into a concrete pointer.
   template <typename T>
   T* offsetToPointer(size_t offset) {
     uintptr_t base = reinterpret_cast<uintptr_t>(this);
     uintptr_t elem = base + offset;
@@ -1541,65 +1531,54 @@ class alignas(uintptr_t) PrivateScriptDa
   // Helpers for creating initializing trailing data
   template <typename T>
   void initSpan(size_t* cursor, uint32_t scaledSpanOffset, size_t length);
 
   template <typename T>
   void initElements(size_t offset, size_t length);
 
   // Size to allocate
-  static size_t AllocationSize(uint32_t nscopes, uint32_t nbigints,
-                               uint32_t nobjects, uint32_t ntrynotes,
+  static size_t AllocationSize(uint32_t ngcthings, uint32_t ntrynotes,
                                uint32_t nscopenotes, uint32_t nresumeoffsets);
 
   // Initialize header and PackedSpans
-  PrivateScriptData(uint32_t nscopes_, uint32_t nbigints, uint32_t nobjects,
-                    uint32_t ntrynotes, uint32_t nscopenotes,
-                    uint32_t nresumeoffsets);
+  PrivateScriptData(uint32_t ngcthings, uint32_t ntrynotes,
+                    uint32_t nscopenotes, uint32_t nresumeoffsets);
 
  public:
   // Accessors for typed array spans.
-  mozilla::Span<GCPtrScope> scopes() {
-    GCPtrScope* base =
-        packedOffsetToPointer<GCPtrScope>(packedOffsets.scopesOffset);
-    return mozilla::MakeSpan(base, nscopes);
-  }
-  mozilla::Span<GCPtrBigInt> bigints() {
-    return packedOffsetToSpan<GCPtrBigInt>(packedOffsets.bigintsSpanOffset);
-  }
-  mozilla::Span<GCPtrObject> objects() {
-    return packedOffsetToSpan<GCPtrObject>(packedOffsets.objectsSpanOffset);
+  mozilla::Span<JS::GCCellPtr> gcthings() {
+    JS::GCCellPtr* base =
+        packedOffsetToPointer<JS::GCCellPtr>(packedOffsets.gcthingsOffset);
+    return mozilla::MakeSpan(base, ngcthings);
   }
   mozilla::Span<JSTryNote> tryNotes() {
     return packedOffsetToSpan<JSTryNote>(packedOffsets.tryNotesSpanOffset);
   }
   mozilla::Span<ScopeNote> scopeNotes() {
     return packedOffsetToSpan<ScopeNote>(packedOffsets.scopeNotesSpanOffset);
   }
   mozilla::Span<uint32_t> resumeOffsets() {
     return packedOffsetToSpan<uint32_t>(packedOffsets.resumeOffsetsSpanOffset);
   }
 
   // Fast tests for if array exists
-  bool hasBigInts() const { return packedOffsets.bigintsSpanOffset != 0; }
-  bool hasObjects() const { return packedOffsets.objectsSpanOffset != 0; }
   bool hasTryNotes() const { return packedOffsets.tryNotesSpanOffset != 0; }
   bool hasScopeNotes() const { return packedOffsets.scopeNotesSpanOffset != 0; }
   bool hasResumeOffsets() const {
     return packedOffsets.resumeOffsetsSpanOffset != 0;
   }
   void setFieldInitializers(FieldInitializers fieldInitializers) {
     fieldInitializers_ = fieldInitializers;
   }
   const FieldInitializers& getFieldInitializers() { return fieldInitializers_; }
 
   // Allocate a new PrivateScriptData. Headers and GCPtrs are initialized.
   // The size of allocation is returned as an out parameter.
-  static PrivateScriptData* new_(JSContext* cx, uint32_t nscopes,
-                                 uint32_t nbigints, uint32_t nobjects,
+  static PrivateScriptData* new_(JSContext* cx, uint32_t ngcthings,
                                  uint32_t ntrynotes, uint32_t nscopenotes,
                                  uint32_t nresumeoffsets, uint32_t* dataSize);
 
   template <XDRMode mode>
   static MOZ_MUST_USE XDRResult XDR(js::XDRState<mode>* xdr,
                                     js::HandleScript script,
                                     js::HandleScriptSourceObject sourceObject,
                                     js::HandleScope scriptEnclosingScope,
@@ -1638,17 +1617,17 @@ class alignas(uintptr_t) SharedScriptDat
   uint32_t mainOffset = 0;
 
   // Fixed frame slots.
   uint32_t nfixed = 0;
 
   // Slots plus maximum stack depth.
   uint32_t nslots = 0;
 
-  // Index into the scopes array of the body scope.
+  // Index into the gcthings array of the body scope.
   uint32_t bodyScopeIndex = 0;
 
   // Number of IC entries to allocate in JitScript for Baseline ICs.
   uint32_t numICEntries = 0;
 
   // ES6 function length.
   uint16_t funLength = 0;
 
@@ -2104,18 +2083,17 @@ class JSScript : public js::gc::TenuredC
                           uint32_t sourceStart, uint32_t sourceEnd,
                           uint32_t toStringStart, uint32_t toStringEnd);
 
   // NOTE: If you use createPrivateScriptData directly instead of via
   // fullyInitFromEmitter, you are responsible for notifying the debugger
   // after successfully creating the script.
   static bool createPrivateScriptData(JSContext* cx,
                                       JS::Handle<JSScript*> script,
-                                      uint32_t nscopes, uint32_t nbigints,
-                                      uint32_t nobjects, uint32_t ntrynotes,
+                                      uint32_t ngcthings, uint32_t ntrynotes,
                                       uint32_t nscopenotes,
                                       uint32_t nresumeoffsets);
 
  private:
   void initFromFunctionBox(js::frontend::FunctionBox* funbox);
 
  public:
   static bool fullyInitFromEmitter(JSContext* cx, js::HandleScript script,
@@ -2706,26 +2684,34 @@ class JSScript : public js::gc::TenuredC
   bool functionHasExtraBodyVarScope() const {
     bool res = hasFlag(ImmutableFlags::FunctionHasExtraBodyVarScope);
     MOZ_ASSERT_IF(res, functionHasParameterExprs());
     return res;
   }
 
   js::VarScope* functionExtraBodyVarScope() const {
     MOZ_ASSERT(functionHasExtraBodyVarScope());
-    for (js::Scope* scope : scopes()) {
+    for (JS::GCCellPtr gcThing : gcthings()) {
+      if (!gcThing.is<js::Scope>()) {
+        continue;
+      }
+      js::Scope* scope = &gcThing.as<js::Scope>();
       if (scope->kind() == js::ScopeKind::FunctionBodyVar) {
         return &scope->as<js::VarScope>();
       }
     }
     MOZ_CRASH("Function extra body var scope not found");
   }
 
   bool needsBodyEnvironment() const {
-    for (js::Scope* scope : scopes()) {
+    for (JS::GCCellPtr gcThing : gcthings()) {
+      if (!gcThing.is<js::Scope>()) {
+        continue;
+      }
+      js::Scope* scope = &gcThing.as<js::Scope>();
       if (ScopeKindIsInBody(scope->kind()) && scope->hasEnvironment()) {
         return true;
       }
     }
     return false;
   }
 
   inline js::LexicalScope* maybeNamedLambdaScope() const;
@@ -2812,32 +2798,22 @@ class JSScript : public js::gc::TenuredC
   size_t sizeOfData(mozilla::MallocSizeOf mallocSizeOf) const;
 
   void addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf,
                           size_t* sizeOfJitScript,
                           size_t* sizeOfBaselineFallbackStubs) const;
 
   size_t dataSize() const { return dataSize_; }
 
-  bool hasBigInts() const { return data_->hasBigInts(); }
-  bool hasObjects() const { return data_->hasObjects(); }
   bool hasTrynotes() const { return data_->hasTryNotes(); }
   bool hasScopeNotes() const { return data_->hasScopeNotes(); }
   bool hasResumeOffsets() const { return data_->hasResumeOffsets(); }
 
-  mozilla::Span<const js::GCPtrScope> scopes() const { return data_->scopes(); }
-
-  mozilla::Span<const js::GCPtrBigInt> bigints() const {
-    MOZ_ASSERT(hasBigInts());
-    return data_->bigints();
-  }
-
-  mozilla::Span<const js::GCPtrObject> objects() const {
-    MOZ_ASSERT(hasObjects());
-    return data_->objects();
+  mozilla::Span<const JS::GCCellPtr> gcthings() const {
+    return data_->gcthings();
   }
 
   mozilla::Span<const JSTryNote> trynotes() const {
     MOZ_ASSERT(hasTrynotes());
     return data_->tryNotes();
   }
 
   mozilla::Span<const js::ScopeNote> scopeNotes() const {
@@ -2895,26 +2871,28 @@ class JSScript : public js::gc::TenuredC
     return getAtom(index)->asPropertyName();
   }
 
   js::PropertyName* getName(jsbytecode* pc) const {
     return getAtom(pc)->asPropertyName();
   }
 
   JSObject* getObject(size_t index) {
-    MOZ_ASSERT(objects()[index]->isTenured());
-    return objects()[index];
+    MOZ_ASSERT(gcthings()[index].asCell()->isTenured());
+    return &gcthings()[index].as<JSObject>();
   }
 
   JSObject* getObject(jsbytecode* pc) {
     MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t)));
     return getObject(GET_UINT32_INDEX(pc));
   }
 
-  js::Scope* getScope(size_t index) const { return scopes()[index]; }
+  js::Scope* getScope(size_t index) const {
+    return &gcthings()[index].as<js::Scope>();
+  }
 
   js::Scope* getScope(jsbytecode* pc) const {
     // This method is used to get a scope directly using a JSOp with an
     // index. To search through ScopeNotes to look for a Scope using pc,
     // use lookupScope.
     MOZ_ASSERT(containsPC(pc) && containsPC(pc + sizeof(uint32_t)));
     MOZ_ASSERT(js::JOF_OPTYPE(JSOp(*pc)) == JOF_SCOPE,
                "Did you mean to use lookupScope(pc)?");
@@ -2929,17 +2907,19 @@ class JSScript : public js::gc::TenuredC
       return functionNonDelazifying();
     }
     return nullptr;
   }
 
   inline js::RegExpObject* getRegExp(size_t index);
   inline js::RegExpObject* getRegExp(jsbytecode* pc);
 
-  js::BigInt* getBigInt(size_t index) { return bigints()[index]; }
+  js::BigInt* getBigInt(size_t index) {
+    return &gcthings()[index].as<js::BigInt>();
+  }
 
   js::BigInt* getBigInt(jsbytecode* pc) {
     MOZ_ASSERT(containsPC(pc));
     MOZ_ASSERT(js::JOF_OPTYPE(JSOp(*pc)) == JOF_BIGINT);
     return getBigInt(GET_UINT32_INDEX(pc));
   }
 
   // The following 3 functions find the static scope just before the
--- a/js/src/vm/Realm.cpp
+++ b/js/src/vm/Realm.cpp
@@ -655,20 +655,22 @@ void Realm::setNewObjectMetadata(JSConte
     if (!objects_.objectMetadataTable->add(cx, obj, metadata)) {
       oomUnsafe.crash("setNewObjectMetadata");
     }
   }
 }
 
 static bool AddInnerLazyFunctionsFromScript(
     JSScript* script, MutableHandleObjectVector lazyFunctions) {
-  if (!script->hasObjects()) {
-    return true;
-  }
-  for (JSObject* obj : script->objects()) {
+  for (JS::GCCellPtr gcThing : script->gcthings()) {
+    if (!gcThing.is<JSObject>()) {
+      continue;
+    }
+
+    JSObject* obj = &gcThing.as<JSObject>();
     if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpretedLazy()) {
       if (!lazyFunctions.append(obj)) {
         return false;
       }
     }
   }
   return true;
 }
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -910,50 +910,85 @@ bool js::intrinsic_NewRegExpStringIterat
   if (!obj) {
     return false;
   }
 
   args.rval().setObject(*obj);
   return true;
 }
 
-JSAtom* js::GetSelfHostedFunctionName(JSFunction* fun) {
+static JSAtom* GetUnclonedSelfHostedFunctionName(JSFunction* fun) {
+  if (!fun->isExtended()) {
+    return nullptr;
+  }
+  Value name = fun->getExtendedSlot(ORIGINAL_FUNCTION_NAME_SLOT);
+  if (!name.isString()) {
+    return nullptr;
+  }
+  return &name.toString()->asAtom();
+}
+
+JSAtom* js::GetClonedSelfHostedFunctionName(JSFunction* fun) {
+  if (!fun->isExtended()) {
+    return nullptr;
+  }
   Value name = fun->getExtendedSlot(LAZY_FUNCTION_NAME_SLOT);
   if (!name.isString()) {
     return nullptr;
   }
   return &name.toString()->asAtom();
 }
 
-static void SetSelfHostedFunctionName(JSFunction* fun, JSAtom* name) {
+JSAtom* js::GetClonedSelfHostedFunctionNameOffMainThread(JSFunction* fun) {
+  Value name = fun->getExtendedSlotOffMainThread(LAZY_FUNCTION_NAME_SLOT);
+  if (!name.isString()) {
+    return nullptr;
+  }
+  return &name.toString()->asAtom();
+}
+
+bool js::IsExtendedUnclonedSelfHostedFunctionName(JSAtom* name) {
+  if (name->length() < 2) {
+    return false;
+  }
+  return name->latin1OrTwoByteChar(0) == '$';
+}
+
+static void SetUnclonedSelfHostedFunctionName(JSFunction* fun, JSAtom* name) {
+  fun->setExtendedSlot(ORIGINAL_FUNCTION_NAME_SLOT, StringValue(name));
+}
+
+static void SetClonedSelfHostedFunctionName(JSFunction* fun, JSAtom* name) {
   fun->setExtendedSlot(LAZY_FUNCTION_NAME_SLOT, StringValue(name));
 }
 
 static bool intrinsic_SetCanonicalName(JSContext* cx, unsigned argc,
                                        Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   MOZ_ASSERT(args.length() == 2);
 
   RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
   MOZ_ASSERT(fun->isSelfHostedBuiltin());
+  MOZ_ASSERT(fun->isExtended());
+  MOZ_ASSERT(IsExtendedUnclonedSelfHostedFunctionName(fun->explicitName()));
   JSAtom* atom = AtomizeString(cx, args[1].toString());
   if (!atom) {
     return false;
   }
 
   // _SetCanonicalName can only be called on top-level function declarations.
   MOZ_ASSERT(fun->kind() == JSFunction::NormalFunction);
   MOZ_ASSERT(!fun->isLambda());
 
   // It's an error to call _SetCanonicalName multiple times.
-  MOZ_ASSERT(!GetSelfHostedFunctionName(fun));
+  MOZ_ASSERT(!GetUnclonedSelfHostedFunctionName(fun));
 
   // Set the lazy function name so we can later retrieve the script from the
   // self-hosting global.
-  SetSelfHostedFunctionName(fun, fun->explicitName());
+  SetUnclonedSelfHostedFunctionName(fun, fun->explicitName());
   fun->setAtom(atom);
 
   args.rval().setUndefined();
   return true;
 }
 
 static bool intrinsic_GeneratorObjectIsClosed(JSContext* cx, unsigned argc,
                                               Value* vp) {
@@ -3289,21 +3324,24 @@ static JSObject* CloneObject(JSContext* 
     }
   }
 #endif
 
   RootedObject clone(cx);
   if (selfHostedObject->is<JSFunction>()) {
     RootedFunction selfHostedFunction(cx, &selfHostedObject->as<JSFunction>());
     if (selfHostedFunction->isInterpreted()) {
+      bool hasName = selfHostedFunction->explicitName() != nullptr;
+
       // Arrow functions use the first extended slot for their lexical |this|
       // value. And methods use the first extended slot for their home-object.
       // We only expect to see normal functions here.
       MOZ_ASSERT(selfHostedFunction->kind() == JSFunction::NormalFunction);
-      js::gc::AllocKind kind = selfHostedFunction->getAllocKind();
+      js::gc::AllocKind kind = hasName ? gc::AllocKind::FUNCTION_EXTENDED
+                                       : selfHostedFunction->getAllocKind();
 
       Handle<GlobalObject*> global = cx->global();
       Rooted<LexicalEnvironmentObject*> globalLexical(
           cx, &global->lexicalEnvironment());
       RootedScope emptyGlobalScope(cx, &global->emptyGlobalScope());
       Rooted<ScriptSourceObject*> sourceObject(
           cx, SelfHostingScriptSourceObject(cx));
       if (!sourceObject) {
@@ -3314,21 +3352,21 @@ static JSObject* CloneObject(JSContext* 
       clone = CloneFunctionAndScript(cx, selfHostedFunction, globalLexical,
                                      emptyGlobalScope, sourceObject, kind);
       // To be able to re-lazify the cloned function, its name in the
       // self-hosting compartment has to be stored on the clone. Re-lazification
       // is only possible if this isn't a function expression.
       if (clone && !selfHostedFunction->isLambda()) {
         // If |_SetCanonicalName| was called on the function, the self-hosted
         // name is stored in the extended slot.
-        JSAtom* name = GetSelfHostedFunctionName(selfHostedFunction);
+        JSAtom* name = GetUnclonedSelfHostedFunctionName(selfHostedFunction);
         if (!name) {
           name = selfHostedFunction->explicitName();
         }
-        SetSelfHostedFunctionName(&clone->as<JSFunction>(), name);
+        SetClonedSelfHostedFunctionName(&clone->as<JSFunction>(), name);
       }
     } else {
       clone = CloneSelfHostingIntrinsic(cx, selfHostedFunction);
     }
   } else if (selfHostedObject->is<RegExpObject>()) {
     RegExpObject& reobj = selfHostedObject->as<RegExpObject>();
     RootedAtom source(cx, reobj.getSource());
     MOZ_ASSERT(source->isPermanentAtom());
@@ -3416,28 +3454,29 @@ bool JSRuntime::createLazySelfHostedFunc
   JSFunction* selfHostedFun = getUnclonedSelfHostedFunction(cx, selfHostedName);
   if (!selfHostedFun) {
     return false;
   }
 
   if (!selfHostedFun->isClassConstructor() &&
       !selfHostedFun->hasGuessedAtom() &&
       selfHostedFun->explicitName() != selfHostedName) {
-    MOZ_ASSERT(GetSelfHostedFunctionName(selfHostedFun) == selfHostedName);
+    MOZ_ASSERT(GetUnclonedSelfHostedFunctionName(selfHostedFun) ==
+               selfHostedName);
     funName = selfHostedFun->explicitName();
   }
 
   fun.set(NewScriptedFunction(cx, nargs, JSFunction::INTERPRETED_LAZY, funName,
                               proto, gc::AllocKind::FUNCTION_EXTENDED,
                               newKind));
   if (!fun) {
     return false;
   }
   fun->setIsSelfHostedBuiltin();
-  SetSelfHostedFunctionName(fun, selfHostedName);
+  SetClonedSelfHostedFunctionName(fun, selfHostedName);
   return true;
 }
 
 bool JSRuntime::cloneSelfHostedFunctionScript(JSContext* cx,
                                               HandlePropertyName name,
                                               HandleFunction targetFun) {
   RootedFunction sourceFun(cx, getUnclonedSelfHostedFunction(cx, name));
   if (!sourceFun) {
@@ -3523,23 +3562,23 @@ bool JSRuntime::cloneSelfHostedValue(JSC
   return CloneValue(cx, selfHostedValue, vp);
 }
 
 void JSRuntime::assertSelfHostedFunctionHasCanonicalName(
     JSContext* cx, HandlePropertyName name) {
 #ifdef DEBUG
   JSFunction* selfHostedFun = getUnclonedSelfHostedFunction(cx, name);
   MOZ_ASSERT(selfHostedFun);
-  MOZ_ASSERT(GetSelfHostedFunctionName(selfHostedFun) == name);
+  MOZ_ASSERT(GetUnclonedSelfHostedFunctionName(selfHostedFun) == name);
 #endif
 }
 
 bool js::IsSelfHostedFunctionWithName(JSFunction* fun, JSAtom* name) {
   return fun->isSelfHostedBuiltin() && fun->isExtended() &&
-         GetSelfHostedFunctionName(fun) == name;
+         GetClonedSelfHostedFunctionName(fun) == name;
 }
 
 static_assert(
     JSString::MAX_LENGTH <= INT32_MAX,
     "StringIteratorNext in builtin/String.js assumes the stored index "
     "into the string is an Int32Value");
 
 static_assert(JSString::MAX_LENGTH == MAX_STRING_LENGTH,
--- a/js/src/vm/SelfHosting.h
+++ b/js/src/vm/SelfHosting.h
@@ -16,29 +16,40 @@ namespace js {
 
 /*
  * Check whether the given JSFunction is a self-hosted function whose
  * self-hosted name is the given name.
  */
 bool IsSelfHostedFunctionWithName(JSFunction* fun, JSAtom* name);
 
 /*
- * Returns the name of the function's binding in the self-hosted global.
+ * Returns the name of the cloned function's binding in the self-hosted global.
+ *
+ * This returns a non-null value only when this is a top level function
+ * declaration in the self-hosted global.
+ */
+JSAtom* GetClonedSelfHostedFunctionName(JSFunction* fun);
+
+/*
+ * Same as GetClonedSelfHostedFunctionName, but `fun` is guaranteed to be an
+ * extended function.
  *
- * This returns a non-null value only when:
- *   * This is a top level function declaration in the self-hosted global.
- *   * And either:
- *     * This function is not cloned and `_SetCanonicalName` has been called to
- *       set a different function name.
- *     * This function is cloned.
+ * This function is supposed to be used off-thread, especially the JIT
+ * compilation thread, that cannot access JSFunction.flags_, because of
+ * a race condition.
  *
- * For functions not cloned and not `_SetCanonicalName`ed, use
- * `fun->explicitName()` instead.
+ * See Also: WrappedFunction.isExtended_
  */
-JSAtom* GetSelfHostedFunctionName(JSFunction* fun);
+JSAtom* GetClonedSelfHostedFunctionNameOffMainThread(JSFunction* fun);
+
+/*
+ * Uncloned self-hosted functions with `$` prefix are allocated as
+ * extended function, to store the original name in `_SetCanonicalName`.
+ */
+bool IsExtendedUnclonedSelfHostedFunctionName(JSAtom* name);
 
 bool IsCallSelfHostedNonGenericMethod(NativeImpl impl);
 
 bool ReportIncompatibleSelfHostedMethod(JSContext* cx, const CallArgs& args);
 
 /* Get the compile options used when compiling self hosted code. */
 void FillSelfHostingCompileOptions(JS::CompileOptions& options);
 
--- a/js/src/vm/SharedArrayObject.cpp
+++ b/js/src/vm/SharedArrayObject.cpp
@@ -355,17 +355,17 @@ static const ClassOps SharedArrayBufferO
     nullptr, /* hasInstance */
     nullptr, /* construct */
     nullptr, /* trace */
 };
 
 static const JSFunctionSpec sharedarrray_functions[] = {JS_FS_END};
 
 static const JSPropertySpec sharedarrray_properties[] = {
-    JS_SELF_HOSTED_SYM_GET(species, "SharedArrayBufferSpecies", 0), JS_PS_END};
+    JS_SELF_HOSTED_SYM_GET(species, "$SharedArrayBufferSpecies", 0), JS_PS_END};
 
 static const JSFunctionSpec sharedarray_proto_functions[] = {
     JS_SELF_HOSTED_FN("slice", "SharedArrayBufferSlice", 2, 0), JS_FS_END};
 
 static const JSPropertySpec sharedarray_proto_properties[] = {
     JS_PSG("byteLength", SharedArrayBufferObject::byteLengthGetter, 0),
     JS_STRING_SYM_PS(toStringTag, "SharedArrayBuffer", JSPROP_READONLY),
     JS_PS_END};
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -1893,29 +1893,29 @@ bool TypedArrayObject::set(JSContext* cx
     JS_SELF_HOSTED_FN("reduce", "TypedArrayReduce", 1, 0),
     JS_SELF_HOSTED_FN("reduceRight", "TypedArrayReduceRight", 1, 0),
     JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0),
     JS_SELF_HOSTED_FN("slice", "TypedArraySlice", 2, 0),
     JS_SELF_HOSTED_FN("some", "TypedArraySome", 1, 0),
     JS_SELF_HOSTED_FN("sort", "TypedArraySort", 1, 0),
     JS_SELF_HOSTED_FN("entries", "TypedArrayEntries", 0, 0),
     JS_SELF_HOSTED_FN("keys", "TypedArrayKeys", 0, 0),
-    JS_SELF_HOSTED_FN("values", "TypedArrayValues", 0, 0),
-    JS_SELF_HOSTED_SYM_FN(iterator, "TypedArrayValues", 0, 0),
+    JS_SELF_HOSTED_FN("values", "$TypedArrayValues", 0, 0),
+    JS_SELF_HOSTED_SYM_FN(iterator, "$TypedArrayValues", 0, 0),
     JS_SELF_HOSTED_FN("includes", "TypedArrayIncludes", 2, 0),
     JS_SELF_HOSTED_FN("toString", "ArrayToString", 0, 0),
     JS_SELF_HOSTED_FN("toLocaleString", "TypedArrayToLocaleString", 2, 0),
     JS_FS_END};
 
 /* static */ const JSFunctionSpec TypedArrayObject::staticFunctions[] = {
     JS_SELF_HOSTED_FN("from", "TypedArrayStaticFrom", 3, 0),
     JS_SELF_HOSTED_FN("of", "TypedArrayStaticOf", 0, 0), JS_FS_END};
 
 /* static */ const JSPropertySpec TypedArrayObject::staticProperties[] = {
-    JS_SELF_HOSTED_SYM_GET(species, "TypedArraySpecies", 0), JS_PS_END};
+    JS_SELF_HOSTED_SYM_GET(species, "$TypedArraySpecies", 0), JS_PS_END};
 
 static JSObject* CreateSharedTypedArrayPrototype(JSContext* cx,
                                                  JSProtoKey key) {
   return GlobalObject::createBlankPrototype(
       cx, cx->global(), &TypedArrayObject::sharedTypedArrayPrototypeClass);
 }
 
 static const ClassSpec TypedArrayObjectSharedTypedArrayPrototypeClassSpec = {
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -10693,16 +10693,20 @@ nsContainerFrame* nsCSSFrameConstructor:
   //
   // ColumnSetWrapper (original style)
   //   ColumnSet (-moz-column-set)
   //     Block (-moz-column-content)
   //
   nsBlockFrame* columnSetWrapper = NS_NewColumnSetWrapperFrame(
       mPresShell, aComputedStyle, nsFrameState(NS_FRAME_OWNS_ANON_BOXES));
   InitAndRestoreFrame(aState, aContent, aParentFrame, columnSetWrapper);
+  if (aParentFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) &&
+      !ShouldSuppressColumnSpanDescendants(aParentFrame)) {
+    columnSetWrapper->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
+  }
 
   RefPtr<ComputedStyle> columnSetStyle =
       mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
           PseudoStyleType::columnSet, aComputedStyle);
   nsContainerFrame* columnSet = NS_NewColumnSetFrame(
       mPresShell, columnSetStyle, nsFrameState(NS_FRAME_OWNS_ANON_BOXES));
   InitAndRestoreFrame(aState, aContent, columnSetWrapper, columnSet);
   columnSet->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3787,18 +3787,21 @@ nsresult nsLayoutUtils::PaintFrame(gfxCo
     doc->GetIsActive(&isActive);
     builder->SetInActiveDocShell(isActive);
   }
 
   nsRect rootVisualOverflow = aFrame->GetVisualOverflowRectRelativeToSelf();
 
   // If we are in a remote browser, then apply clipping from ancestor browsers
   if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) {
+    LayoutDeviceIntRect unscaledVisibleRect = browserChild->GetVisibleRect();
+    CSSRect visibleRect =
+        unscaledVisibleRect / presContext->CSSToDevPixelScale();
     rootVisualOverflow.IntersectRect(rootVisualOverflow,
-                                     browserChild->GetVisibleRect());
+                                     CSSPixel::ToAppUnits(visibleRect));
   }
 
   nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
   if (rootScrollFrame && !aFrame->GetParent()) {
     nsIScrollableFrame* rootScrollableFrame =
         presShell->GetRootScrollFrameAsScrollable();
     MOZ_ASSERT(rootScrollableFrame);
     nsRect displayPortBase = rootVisualOverflow;
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -3599,16 +3599,17 @@ void nsBlockFrame::ReflowBlockFrame(Bloc
     availSpace.BStart(wm) -= bStartMargin;
     if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) {
       availSpace.BSize(wm) += bStartMargin;
     }
 
     // Construct the reflow input for the block.
     Maybe<ReflowInput> blockReflowInput;
     Maybe<LogicalSize> cbSize;
+    LogicalSize availSize = availSpace.Size(wm);
     if (Style()->GetPseudoType() == PseudoStyleType::columnContent) {
       // Calculate the multicol containing block's block size so that the
       // children with percentage block size get correct percentage basis.
       const ReflowInput* cbReflowInput =
           StaticPrefs::layout_css_column_span_enabled()
               ? aState.mReflowInput.mParentReflowInput->mCBReflowInput
               : aState.mReflowInput.mCBReflowInput;
       MOZ_ASSERT(cbReflowInput->mFrame->StyleColumn()->IsColumnContainerStyle(),
@@ -3623,21 +3624,49 @@ void nsBlockFrame::ReflowBlockFrame(Bloc
       // If a ColumnSetWrapper is in a balancing column content, it may be
       // pushed or pulled back and forth between column contents. Always add
       // NS_FRAME_HAS_DIRTY_CHILDREN bit to it so that its ColumnSet children
       // can have a chance to reflow under current block size constraint.
       if (aState.mReflowInput.mFlags.mIsColumnBalancing &&
           frame->IsColumnSetWrapperFrame()) {
         frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
       }
-    }
-
-    blockReflowInput.emplace(
-        aState.mPresContext, aState.mReflowInput, frame,
-        availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm), cbSize);
+    } else if (IsColumnSetWrapperFrame()) {
+      // If we are reflowing our ColumnSet children, we want to apply our block
+      // size constraint to the available block size when constructing reflow
+      // input for ColumnSet so that ColumnSet can use it to compute its max
+      // column block size.
+      if (frame->IsColumnSetFrame()) {
+        if (availSize.BSize(wm) != NS_UNCONSTRAINEDSIZE) {
+          // If the available size is constrained, we need to subtract
+          // ColumnSetWrapper's block-end border and padding.
+          availSize.BSize(wm) -= aState.BorderPadding().BEnd(wm);
+        }
+
+        nscoord contentBSize = aState.mReflowInput.ComputedBSize();
+        if (aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
+          contentBSize =
+              std::min(contentBSize, aState.mReflowInput.ComputedMaxBSize());
+        }
+        if (contentBSize != NS_UNCONSTRAINEDSIZE) {
+          // ColumnSet is not the outermost frame in the column container, so it
+          // cannot have any margin. We don't need to consider any margin that
+          // can be generated by "box-decoration-break: clone" as we do in
+          // BlockReflowInput::ComputeBlockAvailSpace().
+          const nscoord availContentBSize = std::max(
+              0, contentBSize - (aState.mBCoord - aState.ContentBStart()));
+          availSize.BSize(wm) =
+              std::min(availSize.BSize(wm), availContentBSize);
+        }
+      }
+    }
+
+    blockReflowInput.emplace(aState.mPresContext, aState.mReflowInput, frame,
+                             availSize.ConvertTo(frame->GetWritingMode(), wm),
+                             cbSize);
 
     nsFloatManager::SavedState floatManagerState;
     nsReflowStatus frameReflowStatus;
     do {
       if (floatAvailableSpace.HasFloats()) {
         // Set if floatAvailableSpace.HasFloats() is true for any
         // iteration of the loop.
         aLine->SetLineIsImpactedByFloat(true);
--- a/layout/generic/nsColumnSetFrame.cpp
+++ b/layout/generic/nsColumnSetFrame.cpp
@@ -306,21 +306,16 @@ nsColumnSetFrame::ReflowConfig nsColumnS
   nscoord computedBSize =
       GetEffectiveComputedBSize(aReflowInput, consumedBSize);
   nscoord colBSize = GetAvailableContentBSize(aReflowInput);
 
   if (aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) {
     colBSize = aReflowInput.ComputedBSize();
   } else if (aReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
     colBSize = std::min(colBSize, aReflowInput.ComputedMaxBSize());
-  } else if (StaticPrefs::layout_css_column_span_enabled() &&
-             aReflowInput.mCBReflowInput->ComputedMaxBSize() !=
-                 NS_UNCONSTRAINEDSIZE) {
-    colBSize =
-        std::min(colBSize, aReflowInput.mCBReflowInput->ComputedMaxBSize());
   }
 
   nscoord colGap =
       ColumnUtils::GetColumnGap(this, aReflowInput.ComputedISize());
   int32_t numColumns = colStyle->mColumnCount;
 
   // If column-fill is set to 'balance', then we want to balance the columns.
   const bool isBalancing =
@@ -1059,16 +1054,18 @@ void nsColumnSetFrame::FindBestBalanceBS
                    aConfig.mKnownFeasibleBSize);
 
     if (aConfig.mKnownInfeasibleBSize >= aConfig.mKnownFeasibleBSize - 1) {
       // aConfig.mKnownFeasibleBSize is where we want to be
       break;
     }
 
     if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) {
+      // There's no feasible block-size to fit our contents. We may need to
+      // reflow one more time after this loop.
       break;
     }
 
     if (lastKnownFeasibleBSize - aConfig.mKnownFeasibleBSize == 1) {
       // We decreased the feasible block-size by one twip only. This could
       // indicate that there is a continuously breakable child frame
       // that we are crawling through.
       maybeContinuousBreakingDetected = true;
@@ -1119,17 +1116,39 @@ void nsColumnSetFrame::FindBestBalanceBS
   if (aConfig.mIsBalancing && !aColData.mFeasible &&
       !aPresContext->HasPendingInterrupt()) {
     // We may need to reflow one more time at the feasible block-size to
     // get a valid layout.
     bool skip = false;
     if (aConfig.mKnownInfeasibleBSize >= availableContentBSize) {
       aConfig.mColMaxBSize = availableContentBSize;
       if (mLastBalanceBSize == availableContentBSize) {
-        skip = true;
+        if (StaticPrefs::layout_css_column_span_enabled() &&
+            aUnboundedLastColumn &&
+            !aReflowInput.mCBReflowInput->mFrame->HasAnyStateBits(
+                NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
+          // If we are end up here, we have a constrained available content
+          // block size and cannot fit all the content during the very first
+          // balancing iteration. We may need to give up balancing and reflow
+          // again.
+          //
+          // Note that we do this only for the top-level column container
+          // because we don't want a nested column container to create overflow
+          // columns immediately if its content doesn't fit, and change its
+          // completeness from incomplete to complete. That is because 1) the
+          // top-level one might do column balancing, and it can enlarge the
+          // available block-size so that the nested one could fit its content
+          // in next balancing iteration; or 2) the top-level container is
+          // filling columns sequentially, and may have more inline-size to
+          // create more column boxes for the nested column containers'
+          // next-in-flow frames.
+          aConfig = ChooseColumnStrategy(aReflowInput, true);
+        } else {
+          skip = true;
+        }
       }
     } else {
       aConfig.mColMaxBSize = aConfig.mKnownFeasibleBSize;
     }
     if (!skip) {
       // If our block-size is unconstrained, make sure that the last column is
       // allowed to have arbitrary block-size here, even though we were
       // balancing. Otherwise we'd have to split, and it's not clear what we'd
--- a/layout/reftests/columns/column-balancing-nested-000-ref.html
+++ b/layout/reftests/columns/column-balancing-nested-000-ref.html
@@ -12,17 +12,16 @@
             color:black; background-color:white; font-size:16px; padding:0; margin:0;
         }
 
   .colset {
     -moz-column-count: 2;
     -moz-column-gap: 1px;
     -moz-column-rule: 1px solid black;
     border: solid silver;
-    margin-bottom:1em;
     width:30ch;
   }
   p { margin: 0; }
 
   /* balancing column sets deeper than level 2 should only use 1 column */
   .colset .colset .colset,
   .colset .colset .colset .colset {
     -moz-column-count: 1;
@@ -31,25 +30,27 @@
 </head>
 <body>
 <div class="colset">
   <p>one one one one one</p>
   <div class="colset">
     <p>two two two two two</p>
   </div>
 </div>
+<br>
 <div class="colset">
   <p>one one one one one</p>
   <div class="colset">
     <p>two two two two two</p>
     <div class="colset">
       <p>three three three three three</p>
     </div>
   </div>
 </div>
+<br>
 <div class="colset">
   <p>one one one one one</p>
   <div class="colset">
     <p>two two two two two</p>
     <div class="colset">
       <p>three three</p>
       <div class="colset">
         <p>four four four four four</p>
--- a/layout/reftests/columns/column-balancing-nested-000.html
+++ b/layout/reftests/columns/column-balancing-nested-000.html
@@ -12,38 +12,39 @@
             color:black; background-color:white; font-size:16px; padding:0; margin:0;
         }
 
   .colset {
     -moz-column-count: 2;
     -moz-column-gap: 1px;
     -moz-column-rule: 1px solid black;
     border: solid silver;
-    margin-bottom:1em;
     width:30ch;
   }
   p { margin: 0; }
 </style>
 </head>
 <body>
 <div class="colset">
   <p>one one one one one</p>
   <div class="colset">
     <p>two two two two two</p>
   </div>
 </div>
+<br>
 <div class="colset">
   <p>one one one one one</p>
   <div class="colset">
     <p>two two two two two</p>
     <div class="colset">
       <p>three three three three three</p>
     </div>
   </div>
 </div>
+<br>
 <div class="colset">
   <p>one one one one one</p>
   <div class="colset">
     <p>two two two two two</p>
     <div class="colset">
       <p>three three</p>
       <div class="colset">
         <p>four four four four four</p>
--- a/modules/libmar/moz.build
+++ b/modules/libmar/moz.build
@@ -5,16 +5,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'Application Update')
 
 DIRS += ['src']
 
 if CONFIG['MOZ_ENABLE_SIGNMAR']:
-    DIRS += ['sign', 'verify']
+    DIRS += ['sign']
     TEST_DIRS += ['tests']
-elif CONFIG['MOZ_VERIFY_MAR_SIGNATURE']:
-    DIRS += ['verify']
+    if CONFIG['MOZ_VERIFY_MAR_SIGNATURE']:
+        DIRS += ['verify']
 
 # If we are building ./sign and ./verify then ./tool must come after it
 DIRS += ['tool']
 
--- a/modules/libmar/tool/moz.build
+++ b/modules/libmar/tool/moz.build
@@ -18,19 +18,23 @@ HOST_USE_LIBS += [
 if CONFIG['MOZ_ENABLE_SIGNMAR']:
     Program('signmar')
 
     SOURCES += HOST_SOURCES
 
     USE_LIBS += [
         'mar',
         'signmar',
-        'verifymar',
     ]
 
+    if CONFIG['MOZ_VERIFY_MAR_SIGNATURE']:
+        USE_LIBS += [
+            'verifymar',
+        ]
+
 for var in ('MAR_CHANNEL_ID', 'MOZ_APP_VERSION'):
     DEFINES[var] = '"%s"' % CONFIG[var]
     HOST_DEFINES[var] = DEFINES[var]
 
 if CONFIG['MOZ_ENABLE_SIGNMAR']:
     USE_LIBS += [
         'nspr',
         'nss',
--- a/modules/libmar/verify/moz.build
+++ b/modules/libmar/verify/moz.build
@@ -20,12 +20,24 @@ elif CONFIG['OS_ARCH'] == 'Darwin':
       'MacVerifyCrypto.cpp',
     ]
     OS_LIBS += [
       '-framework Security',
     ]
 else:
     DEFINES['MAR_NSS'] = True
     LOCAL_INCLUDES += ['../sign']
+    USE_LIBS += [
+        'nspr',
+        'nss',
+        'signmar',
+    ]
+    # Ideally, this would be '-Wl,-rpath=$ORIGIN', but the build system
+    # doesn't do the right escaping yet. Even more ideally, this would
+    # be LDFLAGS, but the build system doesn't propagate those like USE_LIBS
+    # and OS_LIBS. Bug #1041943.
+    OS_LIBS += [
+        '-Wl,-rpath=\\$$ORIGIN',
+    ]
 
 LOCAL_INCLUDES += [
     '../src',
 ]
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -2510,17 +2510,17 @@ pref("font.blacklist.underline_offset", 
 
 pref("security.directory",              "");
 
 // security-sensitive dialogs should delay button enabling. In milliseconds.
 pref("security.dialog_enable_delay", 1000);
 pref("security.notification_enable_delay", 500);
 
 #if defined(DEBUG) && !defined(ANDROID)
-pref("csp.about_uris_without_csp", "blank,printpreview,srcdoc,addons,cache-entry,config,debugging,devtools,downloads,home,newtab,performance,plugins,profiles,preferences,restartrequired,serviceworkers,sessionrestore,support,sync-log,telemetry,url-classifier,welcomeback");
+pref("csp.about_uris_without_csp", "blank,printpreview,srcdoc,addons,cache-entry,config,debugging,devtools,downloads,home,newtab,plugins,profiles,preferences,restartrequired,serviceworkers,sessionrestore,support,sync-log,telemetry,url-classifier,welcomeback");
 // the following prefs are for testing purposes only.
 pref("csp.overrule_about_uris_without_csp_whitelist", false);
 pref("csp.skip_about_page_has_csp_assert", false);
 // assertion flag will be set to false after fixing Bug 1473549
 pref("security.allow_eval_with_system_principal", false);
 pref("security.uris_using_eval_with_system_principal", "autocomplete.xml,redux.js,react-redux.js,content-task.js,preferencesbindings.js,lodash.js,jszip.js,sinon-7.2.7.js,ajv-4.1.1.js,jsol.js");
 #endif
 
--- a/modules/libpref/parser/src/lib.rs
+++ b/modules/libpref/parser/src/lib.rs
@@ -624,19 +624,19 @@ impl<'t> Parser<'t> {
     }
 
     fn match_hex_digits(&mut self, ndigits: i32) -> Option<u16> {
         debug_assert!(ndigits == 2 || ndigits == 4);
         let mut value: u16 = 0;
         for _ in 0..ndigits {
             value = value << 4;
             match self.get_char() {
-                c @ b'0'...b'9' => value += (c - b'0') as u16,
-                c @ b'A'...b'F' => value += (c - b'A') as u16 + 10,
-                c @ b'a'...b'f' => value += (c - b'a') as u16 + 10,
+                c @ b'0' ..= b'9' => value += (c - b'0') as u16,
+                c @ b'A' ..= b'F' => value += (c - b'A') as u16 + 10,
+                c @ b'a' ..= b'f' => value += (c - b'a') as u16 + 10,
                 _ => {
                     self.unget_char();
                     return None;
                 }
             }
         }
         Some(value)
     }
--- a/mozglue/baseprofiler/core/platform-linux-android.cpp
+++ b/mozglue/baseprofiler/core/platform-linux-android.cpp
@@ -71,17 +71,17 @@ using namespace mozilla;
 
 namespace mozilla {
 namespace baseprofiler {
 
 int profiler_current_process_id() { return getpid(); }
 
 int profiler_current_thread_id() {
   // glibc doesn't provide a wrapper for gettid().
-#if defined(__GLIBC__)
+#if defined(__linux__) || !defined(__BIONIC__)
   return static_cast<int>(static_cast<pid_t>(syscall(SYS_gettid)));
 #else
   return static_cast<int>(gettid());
 #endif
 }
 
 static int64_t MicrosecondsSince1970() {
   struct timeval tv;
--- a/netwerk/protocol/http/Http2Push.cpp
+++ b/netwerk/protocol/http/Http2Push.cpp
@@ -24,39 +24,116 @@ namespace mozilla {
 namespace net {
 
 class CallChannelOnPush final : public Runnable {
  public:
   CallChannelOnPush(nsIHttpChannelInternal* associatedChannel,
                     const nsACString& pushedURI, Http2PushedStream* pushStream)
       : Runnable("net::CallChannelOnPush"),
         mAssociatedChannel(associatedChannel),
-        mPushedURI(pushedURI),
-        mPushedStream(pushStream) {}
+        mPushedURI(pushedURI) {
+    mPushedStreamWrapper = new Http2PushedStreamWrapper(pushStream);
+  }
 
   NS_IMETHOD Run() override {
     MOZ_ASSERT(NS_IsMainThread());
     RefPtr<nsHttpChannel> channel;
     CallQueryInterface(mAssociatedChannel, channel.StartAssignment());
     MOZ_ASSERT(channel);
-    if (channel && NS_SUCCEEDED(channel->OnPush(mPushedURI, mPushedStream))) {
+    if (channel &&
+        NS_SUCCEEDED(channel->OnPush(mPushedURI, mPushedStreamWrapper))) {
       return NS_OK;
     }
 
     LOG3(("Http2PushedStream Orphan %p failed OnPush\n", this));
-    mPushedStream->OnPushFailed();
+    mPushedStreamWrapper->OnPushFailed();
     return NS_OK;
   }
 
  private:
   nsCOMPtr<nsIHttpChannelInternal> mAssociatedChannel;
   const nsCString mPushedURI;
-  Http2PushedStream* mPushedStream;
+  RefPtr<Http2PushedStreamWrapper> mPushedStreamWrapper;
 };
 
+// Because WeakPtr isn't thread-safe we must ensure that the object is destroyed
+// on the socket thread, so any Release() called on a different thread is
+// dispatched to the socket thread.
+bool Http2PushedStreamWrapper::DispatchRelease() {
+  if (OnSocketThread()) {
+    return false;
+  }
+
+  gSocketTransportService->Dispatch(
+      NewNonOwningRunnableMethod("net::Http2PushedStreamWrapper::Release", this,
+                                 &Http2PushedStreamWrapper::Release),
+      NS_DISPATCH_NORMAL);
+
+  return true;
+}
+
+NS_IMPL_ADDREF(Http2PushedStreamWrapper)
+NS_IMETHODIMP_(MozExternalRefCountType)
+Http2PushedStreamWrapper::Release() {
+  nsrefcnt count = mRefCnt - 1;
+  if (DispatchRelease()) {
+    // Redispatched to the socket thread.
+    return count;
+  }
+
+  MOZ_ASSERT(0 != mRefCnt, "dup release");
+  count = --mRefCnt;
+  NS_LOG_RELEASE(this, count, "Http2PushedStreamWrapper");
+
+  if (0 == count) {
+    mRefCnt = 1;
+    delete (this);
+    return 0;
+  }
+
+  return count;
+}
+
+NS_INTERFACE_MAP_BEGIN(Http2PushedStreamWrapper)
+NS_INTERFACE_MAP_END
+
+Http2PushedStreamWrapper::Http2PushedStreamWrapper(
+    Http2PushedStream* aPushStream) {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  mStream = aPushStream;
+  mRequestString = aPushStream->GetRequestString();
+}
+
+Http2PushedStreamWrapper::~Http2PushedStreamWrapper() {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+}
+
+Http2PushedStream* Http2PushedStreamWrapper::GetStream() {
+  MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+  if (mStream) {
+    Http2Stream* stream = mStream;
+    return static_cast<Http2PushedStream*>(stream);
+  }
+  return nullptr;
+}
+
+void Http2PushedStreamWrapper::OnPushFailed() {
+  if (OnSocketThread()) {
+    if (mStream) {
+      Http2Stream* stream = mStream;
+      static_cast<Http2PushedStream*>(stream)->OnPushFailed();
+    }
+  } else {
+    gSocketTransportService->Dispatch(
+        NewRunnableMethod("net::Http2PushedStreamWrapper::OnPushFailed", this,
+                          &Http2PushedStreamWrapper::OnPushFailed),
+        NS_DISPATCH_NORMAL);
+  }
+}
+
 //////////////////////////////////////////
 // Http2PushedStream
 //////////////////////////////////////////
 
 Http2PushedStream::Http2PushedStream(
     Http2PushTransactionBuffer* aTransaction, Http2Session* aSession,
     Http2Stream* aAssociatedStream, uint32_t aID,
     uint64_t aCurrentForegroundTabOuterContentWindowId)
--- a/netwerk/protocol/http/Http2Push.h
+++ b/netwerk/protocol/http/Http2Push.h
@@ -127,12 +127,30 @@ class Http2PushTransactionBuffer final :
   bool mIsDone;
 
   UniquePtr<char[]> mBufferedHTTP1;
   uint32_t mBufferedHTTP1Size;
   uint32_t mBufferedHTTP1Used;
   uint32_t mBufferedHTTP1Consumed;
 };
 
+class Http2PushedStreamWrapper : public nsISupports {
+ public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  bool DispatchRelease();
+
+  explicit Http2PushedStreamWrapper(Http2PushedStream* aPushStream);
+
+  nsCString& GetRequestString() { return mRequestString; }
+  Http2PushedStream* GetStream();
+  void OnPushFailed();
+
+ private:
+  virtual ~Http2PushedStreamWrapper();
+
+  nsCString mRequestString;
+  WeakPtr<Http2Stream> mStream;
+};
+
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // mozilla_net_Http2Push_Internal_h
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -443,30 +443,36 @@ bool Http2Session::AddStream(nsAHttpTran
   if (!mFirstHttpTransaction && !mTlsHandshakeFinished) {
     mFirstHttpTransaction = aHttpTransaction->QueryHttpTransaction();
     LOG3(("Http2Session::AddStream first session=%p trans=%p ", this,
           mFirstHttpTransaction.get()));
   }
 
   if (mClosed || mShouldGoAway) {
     nsHttpTransaction* trans = aHttpTransaction->QueryHttpTransaction();
-    if (trans && !trans->GetPushedStream()) {
-      LOG3(
-          ("Http2Session::AddStream %p atrans=%p trans=%p session unusable - "
-           "resched.\n",
-           this, aHttpTransaction, trans));
-      aHttpTransaction->SetConnection(nullptr);
-      nsresult rv = gHttpHandler->InitiateTransaction(trans, trans->Priority());
-      if (NS_FAILED(rv)) {
+    if (trans) {
+      RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper;
+      pushedStreamWrapper = trans->GetPushedStream();
+      if (!pushedStreamWrapper || !pushedStreamWrapper->GetStream()) {
         LOG3(
-            ("Http2Session::AddStream %p atrans=%p trans=%p failed to initiate "
-             "transaction (%08x).\n",
-             this, aHttpTransaction, trans, static_cast<uint32_t>(rv)));
+            ("Http2Session::AddStream %p atrans=%p trans=%p session unusable - "
+             "resched.\n",
+             this, aHttpTransaction, trans));
+        aHttpTransaction->SetConnection(nullptr);
+        nsresult rv =
+            gHttpHandler->InitiateTransaction(trans, trans->Priority());
+        if (NS_FAILED(rv)) {
+          LOG3(
+              ("Http2Session::AddStream %p atrans=%p trans=%p failed to "
+               "initiate "
+               "transaction (%08x).\n",
+               this, aHttpTransaction, trans, static_cast<uint32_t>(rv)));
+        }
+        return true;
       }
-      return true;
     }
   }
 
   aHttpTransaction->SetConnection(this);
   aHttpTransaction->OnActivated();
 
   if (aIsWebsocket) {
     MOZ_ASSERT(!aUseTunnel, "Websocket on tunnel?!");
--- a/netwerk/protocol/http/Http2Stream.cpp
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -444,23 +444,25 @@ nsresult Http2Stream::ParseHttpRequestHe
   if (head->IsGet()) {
     // from :scheme, :authority, :path
     nsIRequestContext* requestContext = mTransaction->RequestContext();
     SpdyPushCache* cache = nullptr;
     if (requestContext) {
       cache = requestContext->GetSpdyPushCache();
     }
 
+    RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper;
     Http2PushedStream* pushedStream = nullptr;
 
     // If a push stream is attached to the transaction via onPush, match only
     // with that one. This occurs when a push was made with in conjunction with
     // a nsIHttpPushListener
     nsHttpTransaction* trans = mTransaction->QueryHttpTransaction();
-    if (trans && (pushedStream = trans->TakePushedStream())) {
+    if (trans && (pushedStreamWrapper = trans->TakePushedStream()) &&
+        (pushedStream = pushedStreamWrapper->GetStream())) {
       if (pushedStream->mSession == mSession) {
         LOG3(("Pushed Stream match based on OnPush correlation %p",
               pushedStream));
       } else {
         LOG3(("Pushed Stream match failed due to stream mismatch %p %" PRId64
               " %" PRId64 "\n",
               pushedStream, pushedStream->mSession->Serial(),
               mSession->Serial()));
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -9367,24 +9367,24 @@ nsHttpChannel::SetNotificationCallbacks(
   }
   return rv;
 }
 
 bool nsHttpChannel::AwaitingCacheCallbacks() {
   return mCacheEntriesToWaitFor != 0;
 }
 
-void nsHttpChannel::SetPushedStream(Http2PushedStream* stream) {
+void nsHttpChannel::SetPushedStream(Http2PushedStreamWrapper* stream) {
   MOZ_ASSERT(stream);
   MOZ_ASSERT(!mPushedStream);
   mPushedStream = stream;
 }
 
 nsresult nsHttpChannel::OnPush(const nsACString& url,
-                               Http2PushedStream* pushedStream) {
+                               Http2PushedStreamWrapper* pushedStream) {
   MOZ_ASSERT(NS_IsMainThread());
   LOG(("nsHttpChannel::OnPush [this=%p]\n", this));
 
   MOZ_ASSERT(mCaps & NS_HTTP_ONPUSH_LISTENER);
   nsCOMPtr<nsIHttpPushListener> pushListener;
   NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
                                 NS_GET_IID(nsIHttpPushListener),
                                 getter_AddRefs(pushListener));
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -139,17 +139,17 @@ class nsHttpChannel final : public HttpB
   nsHttpChannel();
 
   virtual MOZ_MUST_USE nsresult
   Init(nsIURI* aURI, uint32_t aCaps, nsProxyInfo* aProxyInfo,
        uint32_t aProxyResolveFlags, nsIURI* aProxyURI, uint64_t aChannelId,
        nsContentPolicyType aContentPolicyType) override;
 
   MOZ_MUST_USE nsresult OnPush(const nsACString& uri,
-                               Http2PushedStream* pushedStream);
+                               Http2PushedStreamWrapper* pushedStream);
 
   static bool IsRedirectStatus(uint32_t status);
   static bool WillRedirect(nsHttpResponseHead* response);
 
   // Methods HttpBaseChannel didn't implement for us or that we override.
   //
   // nsIRequest
   NS_IMETHOD Cancel(nsresult status) override;
@@ -536,17 +536,17 @@ class nsHttpChannel final : public HttpB
                              bool ignoreMissingPartialLen = false);
   MOZ_MUST_USE nsresult SetupByteRangeRequest(int64_t partialLen);
   void UntieByteRangeRequest();
   void UntieValidationRequest();
   MOZ_MUST_USE nsresult OpenCacheInputStream(nsICacheEntry* cacheEntry,
                                              bool startBuffering,
                                              bool checkingAppCacheEntry);
 
-  void SetPushedStream(Http2PushedStream* stream);
+  void SetPushedStream(Http2PushedStreamWrapper* stream);
 
   void MaybeWarnAboutAppCache();
 
   void SetLoadGroupUserAgentOverride();
 
   void SetOriginHeader();
   void SetDoNotTrack();
 
@@ -745,17 +745,17 @@ class nsHttpChannel final : public HttpB
   // true.
   nsCString mTopWindowOrigin;
 
   nsTArray<nsContinueRedirectionFunc> mRedirectFuncStack;
 
   // Needed for accurate DNS timing
   RefPtr<nsDNSPrefetch> mDNSPrefetch;
 
-  Http2PushedStream* mPushedStream;
+  RefPtr<Http2PushedStreamWrapper> mPushedStream;
   // True if the channel's principal was found on a phishing, malware, or
   // tracking (if tracking protection is enabled) blocklist
   bool mLocalBlocklist;
 
   MOZ_MUST_USE nsresult WaitForRedirectCallback();
   void PushRedirectAsyncFunc(nsContinueRedirectionFunc func);
   void PopRedirectAsyncFunc(nsContinueRedirectionFunc func);
 
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -1887,24 +1887,28 @@ nsresult nsHttpConnectionMgr::ProcessNew
   // been canceled (see bug 190001).
   if (NS_FAILED(trans->Status())) {
     LOG(("  transaction was canceled... dropping event!\n"));
     return NS_OK;
   }
 
   trans->SetPendingTime();
 
-  Http2PushedStream* pushedStream = trans->GetPushedStream();
-  if (pushedStream) {
-    LOG(("  ProcessNewTransaction %p tied to h2 session push %p\n", trans,
-         pushedStream->Session()));
-    return pushedStream->Session()->AddStream(trans, trans->Priority(), false,
-                                              false, nullptr)
-               ? NS_OK
-               : NS_ERROR_UNEXPECTED;
+  RefPtr<Http2PushedStreamWrapper> pushedStreamWrapper =
+      trans->GetPushedStream();
+  if (pushedStreamWrapper) {
+    Http2PushedStream* pushedStream = pushedStreamWrapper->GetStream();
+    if (pushedStream) {
+      LOG(("  ProcessNewTransaction %p tied to h2 session push %p\n", trans,
+           pushedStream->Session()));
+      return pushedStream->Session()->AddStream(trans, trans->Priority(), false,
+                                                false, nullptr)
+                 ? NS_OK
+                 : NS_ERROR_UNEXPECTED;
+    }
   }
 
   nsresult rv = NS_OK;
   nsHttpConnectionInfo* ci = trans->ConnectionInfo();
   MOZ_ASSERT(ci);
 
   nsConnectionEntry* ent =
       GetOrCreateConnectionEntry(ci, !!trans->TunnelProvider());
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -935,18 +935,18 @@ void nsHttpHandler::BuildUserAgent() {
     mUserAgent += ' ';
     mUserAgent += mAppName;
     mUserAgent += '/';
     mUserAgent += mAppVersion;
   }
 }
 
 #ifdef XP_WIN
-#  define WNT_BASE "Windows NT %ld.%ld"
-#  define W64_PREFIX "; Win64"
+#  define OSCPU_WINDOWS "Windows NT %ld.%ld"
+#  define OSCPU_WIN64 OSCPU_WINDOWS "; Win64; x64"
 #endif
 
 void nsHttpHandler::InitUserAgentComponents() {
 #ifndef MOZ_UA_OS_AGNOSTIC
   // Gather platform.
   mPlatform.AssignLiteral(
 #  if defined(ANDROID)
       "Android"
@@ -1012,28 +1012,28 @@ void nsHttpHandler::InitUserAgentCompone
 #ifndef MOZ_UA_OS_AGNOSTIC
   // Gather OS/CPU.
 #  if defined(XP_WIN)
   OSVERSIONINFO info = {sizeof(OSVERSIONINFO)};
 #    pragma warning(push)
 #    pragma warning(disable : 4996)
   if (GetVersionEx(&info)) {
 #    pragma warning(pop)
+
     const char* format;
-#    if defined _M_IA64
-    format = WNT_BASE W64_PREFIX "; IA64";
-#    elif defined _M_X64 || defined _M_AMD64
-    format = WNT_BASE W64_PREFIX "; x64";
+#    if defined _M_X64 || defined _M_AMD64
+    format = OSCPU_WIN64;
 #    else
     BOOL isWow64 = FALSE;
     if (!IsWow64Process(GetCurrentProcess(), &isWow64)) {
       isWow64 = FALSE;
     }
-    format = isWow64 ? WNT_BASE "; WOW64" : WNT_BASE;
+    format = isWow64 ? OSCPU_WIN64 : OSCPU_WINDOWS;
 #    endif
+
     SmprintfPointer buf =
         mozilla::Smprintf(format, info.dwMajorVersion, info.dwMinorVersion);
     if (buf) {
       mOscpu = buf.get();
     }
   }
 #  elif defined(XP_MACOSX)
 #    if defined(__ppc__)
@@ -1042,45 +1042,32 @@ void nsHttpHandler::InitUserAgentCompone
   mOscpu.AssignLiteral("Intel Mac OS X");
 #    endif
   SInt32 majorVersion = nsCocoaFeatures::OSXVersionMajor();
   SInt32 minorVersion = nsCocoaFeatures::OSXVersionMinor();
   mOscpu += nsPrintfCString(" %d.%d", static_cast<int>(majorVersion),
                             static_cast<int>(minorVersion));
 #  elif defined(XP_UNIX)
   struct utsname name;
-
   int ret = uname(&name);
   if (ret >= 0) {
     nsAutoCString buf;
     buf = (char*)name.sysname;
-
-    if (strcmp(name.machine, "x86_64") == 0 &&
-        sizeof(void*) == sizeof(int32_t)) {
-      // We're running 32-bit code on x86_64. Make this browser
-      // look like it's running on i686 hardware, but append "
-      // (x86_64)" to the end of the oscpu identifier to be able
-      // to differentiate this from someone running 64-bit code
-      // on x86_64..
-
-      buf += " i686 on x86_64";
-    } else {
-      buf += ' ';
+    buf += ' ';
 
 #    ifdef AIX
-      // AIX uname returns machine specific info in the uname.machine
-      // field and does not return the cpu type like other platforms.
-      // We use the AIX version and release numbers instead.
-      buf += (char*)name.version;
-      buf += '.';
-      buf += (char*)name.release;
+    // AIX uname returns machine specific info in the uname.machine
+    // field and does not return the cpu type like other platforms.
+    // We use the AIX version and release numbers instead.
+    buf += (char*)name.version;
+    buf += '.';
+    buf += (char*)name.release;
 #    else
-      buf += (char*)name.machine;
+    buf += (char*)name.machine;
 #    endif
-    }
 
     mOscpu.Assign(buf);
   }
 #  endif
 #endif
 
   mUserAgentIsDirty = true;
 }
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -138,23 +138,24 @@ class nsHttpTransaction final : public n
   void SetRequestContext(nsIRequestContext* aRequestContext);
   void DispatchedAsBlocking();
   void RemoveDispatchedAsBlocking();
 
   void DisableSpdy() override;
 
   nsHttpTransaction* QueryHttpTransaction() override { return this; }
 
-  Http2PushedStream* GetPushedStream() { return mPushedStream; }
-  Http2PushedStream* TakePushedStream() {
-    Http2PushedStream* r = mPushedStream;
-    mPushedStream = nullptr;
-    return r;
+  already_AddRefed<Http2PushedStreamWrapper> GetPushedStream() {
+    return do_AddRef(mPushedStream);
   }
-  void SetPushedStream(Http2PushedStream* push) { mPushedStream = push; }
+  already_AddRefed<Http2PushedStreamWrapper> TakePushedStream() {
+    return mPushedStream.forget();
+  }
+
+  void SetPushedStream(Http2PushedStreamWrapper* push) { mPushedStream = push; }
   uint32_t InitialRwin() const { return mInitialRwin; };
   bool ChannelPipeFull() { return mWaitingOnPipeOut; }
 
   // Locked methods to get and set timing info
   const TimingStruct Timings();
   void BootstrapTimings(TimingStruct times);
   void SetDomainLookupStart(mozilla::TimeStamp timeStamp,
                             bool onlyIfNull = false);
@@ -298,17 +299,17 @@ class nsHttpTransaction final : public n
 
   // After a 304/204 or other "no-content" style response we will skip over
   // up to MAX_INVALID_RESPONSE_BODY_SZ bytes when looking for the next
   // response header to deal with servers that actually sent a response
   // body where they should not have. This member tracks how many bytes have
   // so far been skipped.
   uint32_t mInvalidResponseBytesRead;
 
-  Http2PushedStream* mPushedStream;
+  RefPtr<Http2PushedStreamWrapper> mPushedStream;
   uint32_t mInitialRwin;
 
   nsHttpChunkedDecoder* mChunkedDecoder;
 
   TimingStruct mTimings;
 
   nsresult mStatus;
 
--- a/netwerk/sctp/datachannel/DataChannel.cpp
+++ b/netwerk/sctp/datachannel/DataChannel.cpp
@@ -562,21 +562,16 @@ bool DataChannelConnection::Init(const u
     event.se_type = event_type;
     if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_EVENT, &event,
                            sizeof(event)) < 0) {
       LOG(("*** failed setsockopt SCTP_EVENT errno %d", errno));
       goto error_cleanup;
     }
   }
 
-  // Update number of streams
-  mStreams.AppendElements(aNumStreams);
-  for (uint32_t i = 0; i < aNumStreams; ++i) {
-    mStreams[i] = nullptr;
-  }
   memset(&initmsg, 0, sizeof(initmsg));
   len = sizeof(initmsg);
   if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg,
                          &len) < 0) {
     LOG(("*** failed getsockopt SCTP_INITMSG"));
     goto error_cleanup;
   }
   LOG(("Setting number of SCTP streams to %u, was %u/%u", aNumStreams,
@@ -662,30 +657,37 @@ bool DataChannelConnection::ConnectToTra
              "SCTP wasn't initialized before ConnectToTransport!");
   if (NS_WARN_IF(aTransportId.empty())) {
     return false;
   }
 
   mLocalPort = localport;
   mRemotePort = remoteport;
   mState = CONNECTING;
-
-  RUN_ON_THREAD(
-      mSTS,
-      WrapRunnable(RefPtr<DataChannelConnection>(this),
-                   &DataChannelConnection::SetSignals, aTransportId, aClient),
-      NS_DISPATCH_NORMAL);
+  mAllocateEven = Some(aClient);
+
+  // Could be faster. Probably doesn't matter.
+  while (auto channel = mChannels.Get(INVALID_STREAM)) {
+    mChannels.Remove(channel);
+    channel->mStream = FindFreeStream();
+    if (channel->mStream != INVALID_STREAM) {
+      mChannels.Insert(channel);
+    }
+  }
+
+  RUN_ON_THREAD(mSTS,
+                WrapRunnable(RefPtr<DataChannelConnection>(this),
+                             &DataChannelConnection::SetSignals, aTransportId),
+                NS_DISPATCH_NORMAL);
   return true;
 }
 
-void DataChannelConnection::SetSignals(const std::string& aTransportId,
-                                       bool aClient) {
+void DataChannelConnection::SetSignals(const std::string& aTransportId) {
   ASSERT_WEBRTC(IsSTSThread());
   mTransportId = aTransportId;
-  mAllocateEven = aClient;
   mTransportHandler->SignalPacketReceived.connect(
       this, &DataChannelConnection::SctpDtlsInput);
   // SignalStateChange() doesn't call you with the initial state
   if (mTransportHandler->GetState(mTransportId, false) ==
       TransportLayer::TS_OPEN) {
     LOG(("Setting transport signals, dtls already open"));
     CompleteConnect();
   } else {
@@ -1020,69 +1022,68 @@ bool DataChannelConnection::Connect(cons
   Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
       DataChannelOnMessageAvailable::ON_CONNECTION, this,
       (DataChannel*)nullptr)));
   return true;
 }
 #endif
 
 DataChannel* DataChannelConnection::FindChannelByStream(uint16_t stream) {
-  return mStreams.SafeElementAt(stream);
+  return mChannels.Get(stream).get();
 }
 
 uint16_t DataChannelConnection::FindFreeStream() {
-  uint32_t i, j, limit;
-
-  limit = mStreams.Length();
-  if (limit > MAX_NUM_STREAMS) limit = MAX_NUM_STREAMS;
-
-  for (i = (mAllocateEven ? 0 : 1); i < limit; i += 2) {
-    if (!mStreams[i]) {
-      // Verify it's not still in the process of closing
-      for (j = 0; j < mStreamsResetting.Length(); ++j) {
-        if (mStreamsResetting[j] == i) {
-          break;
-        }
+  ASSERT_WEBRTC(NS_IsMainThread());
+  uint16_t i, limit;
+
+  limit = MAX_NUM_STREAMS;
+
+  MOZ_ASSERT(mAllocateEven.isSome());
+  for (i = (*mAllocateEven ? 0 : 1); i < limit; i += 2) {
+    if (mChannels.Get(i)) {
+      continue;
+    }
+
+    // Verify it's not still in the process of closing
+    size_t j;
+    for (j = 0; j < mStreamsResetting.Length(); ++j) {
+      if (mStreamsResetting[j] == i) {
+        break;
       }
-      if (j == mStreamsResetting.Length()) break;
+    }
+
+    if (j == mStreamsResetting.Length()) {
+      return i;
     }
   }
-  if (i >= limit) {
-    return INVALID_STREAM;
-  }
-  return i;
+  return INVALID_STREAM;
 }
 
 uint32_t DataChannelConnection::UpdateCurrentStreamIndex() {
-  if (mCurrentStream == mStreams.Length() - 1) {
+  RefPtr<DataChannel> channel = mChannels.GetNextChannel(mCurrentStream);
+  if (!channel) {
     mCurrentStream = 0;
   } else {
-    ++mCurrentStream;
+    mCurrentStream = channel->mStream;
   }
-
   return mCurrentStream;
 }
 
 uint32_t DataChannelConnection::GetCurrentStreamIndex() {
-  // Fix current stream index (in case #streams decreased)
-  if (mCurrentStream >= mStreams.Length()) {
-    mCurrentStream = 0;
-  }
-
   return mCurrentStream;
 }
 
 bool DataChannelConnection::RequestMoreStreams(int32_t aNeeded) {
   struct sctp_status status;
   struct sctp_add_streams sas;
   uint32_t outStreamsNeeded;
   socklen_t len;
 
-  if (aNeeded + mStreams.Length() > MAX_NUM_STREAMS) {
-    aNeeded = MAX_NUM_STREAMS - mStreams.Length();
+  if (aNeeded + mNegotiatedIdLimit > MAX_NUM_STREAMS) {
+    aNeeded = MAX_NUM_STREAMS - mNegotiatedIdLimit;
   }
   if (aNeeded <= 0) {
     return false;
   }
 
   len = (socklen_t)sizeof(struct sctp_status);
   if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_STATUS, &status,
                          &len) < 0) {
@@ -1103,18 +1104,18 @@ bool DataChannelConnection::RequestMoreS
       LOG(("Already have %u output streams", outStreamsNeeded));
       return true;
     }
 
     LOG(("***failed: setsockopt ADD errno=%d", errno));
     return false;
   }
   LOG(("Requested %u more streams", outStreamsNeeded));
-  // We add to mStreams when we get a SCTP_STREAM_CHANGE_EVENT and the
-  // values are larger than mStreams.Length()
+  // We add to mNegotiatedIdLimit when we get a SCTP_STREAM_CHANGE_EVENT and the
+  // values are larger than mNegotiatedIdLimit
   return true;
 }
 
 // Returns a POSIX error code.
 int DataChannelConnection::SendControlMessage(const uint8_t* data, uint32_t len,
                                               uint16_t stream) {
   struct sctp_sendv_spa info = {0};
 
@@ -1212,16 +1213,17 @@ int DataChannelConnection::SendOpenReque
 // filling the SCTP's buffers.
 
 // returns if we're still blocked (true)
 bool DataChannelConnection::SendDeferredMessages() {
   RefPtr<DataChannel> channel;  // we may null out the refs to this
 
   // This may block while something is modifying channels, but should not block
   // for IO
+  ASSERT_WEBRTC(!NS_IsMainThread());
   mLock.AssertCurrentThreadOwns();
 
   LOG(("SendDeferredMessages called, pending type: %d", mPendingType));
   if (!mPendingType) {
     return false;
   }
 
   // Send pending control messages
@@ -1237,17 +1239,17 @@ bool DataChannelConnection::SendDeferred
     // Note: There may or may not be pending data messages
     mPendingType = PENDING_DATA;
   }
 
   bool blocked = false;
   uint32_t i = GetCurrentStreamIndex();
   uint32_t end = i;
   do {
-    channel = mStreams[i];
+    channel = mChannels.Get(i);
     // Should already be cleared if closing/closed
     if (!channel || channel->mBufferedData.IsEmpty()) {
       i = UpdateCurrentStreamIndex();
       continue;
     }
 
     // Send buffered data messages
     // Warning: This will fail in case ndata is inactive and a previously
@@ -1314,16 +1316,17 @@ bool DataChannelConnection::SendBuffered
 // Caller must ensure that length <= SIZE_MAX
 void DataChannelConnection::HandleOpenRequestMessage(
     const struct rtcweb_datachannel_open_request* req, uint32_t length,
     uint16_t stream) {
   RefPtr<DataChannel> channel;
   uint32_t prValue;
   uint16_t prPolicy;
 
+  ASSERT_WEBRTC(!NS_IsMainThread());
   mLock.AssertCurrentThreadOwns();
 
   const size_t requiredLength = (sizeof(*req) - 1) + ntohs(req->label_length) +
                                 ntohs(req->protocol_length);
   if (((size_t)length) != requiredLength) {
     LOG(("%s: Inconsistent length: %u, should be %zu", __FUNCTION__, length,
          requiredLength));
     if (((size_t)length) < requiredLength) return;
@@ -1349,17 +1352,17 @@ void DataChannelConnection::HandleOpenRe
       LOG(("Unknown channel type %d", req->channel_type));
       /* XXX error handling */
       return;
   }
   prValue = ntohl(req->reliability_param);
   bool ordered = !(req->channel_type & 0x80);
 
   if ((channel = FindChannelByStream(stream))) {
-    if (!(channel->mFlags & DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED)) {
+    if (!channel->mNegotiated) {
       LOG(
           ("ERROR: HandleOpenRequestMessage: channel for pre-existing stream "
            "%u that was not externally negotiated. JS is lying to us, or "
            "there's an id collision.",
            stream));
       /* XXX: some error handling */
     } else {
       LOG(("Open for externally negotiated channel %u", stream));
@@ -1370,31 +1373,31 @@ void DataChannelConnection::HandleOpenRe
             ("WARNING: external negotiation mismatch with OpenRequest:"
              "channel %u, policy %u/%u, value %u/%u, ordered %d/%d",
              stream, prPolicy, channel->mPrPolicy, prValue, channel->mPrValue,
              static_cast<int>(ordered), static_cast<int>(channel->mOrdered)));
       }
     }
     return;
   }
-  if (stream >= mStreams.Length()) {
+  if (stream >= mNegotiatedIdLimit) {
     LOG(("%s: stream %u out of bounds (%zu)", __FUNCTION__, stream,
-         mStreams.Length()));
+         mNegotiatedIdLimit));
     return;
   }
 
   nsCString label(
       nsDependentCSubstring(&req->label[0], ntohs(req->label_length)));
   nsCString protocol(nsDependentCSubstring(
       &req->label[ntohs(req->label_length)], ntohs(req->protocol_length)));
 
   channel =
       new DataChannel(this, stream, DataChannel::OPEN, label, protocol,
                       prPolicy, prValue, ordered, false, nullptr, nullptr);
-  mStreams[stream] = channel;
+  mChannels.Insert(channel);
 
   LOG(("%s: sending ON_CHANNEL_CREATED for %s/%s: %u", __FUNCTION__,
        channel->mLabel.get(), channel->mProtocol.get(), stream));
   Dispatch(do_AddRef(new DataChannelOnMessageAvailable(
       DataChannelOnMessageAvailable::ON_CHANNEL_CREATED, this, channel)));
 
   LOG(("%s: deferring sending ON_CHANNEL_OPEN for %p", __FUNCTION__,
        channel.get()));
@@ -1556,18 +1559,17 @@ void DataChannelConnection::HandleDataMe
   // Remaining chunks of previously truncated message (due to the buffer being
   // full)?
   if (channel->mFlags & DATA_CHANNEL_FLAGS_CLOSING_TOO_LARGE) {
     LOG(
         ("DataChannel: Ignoring partial message of length %u, buffer full and "
          "closing",
          data_length));
     // Only unblock if unordered
-    if ((channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED) &&
-        (flags & MSG_EOR)) {
+    if (!channel->mOrdered && (flags & MSG_EOR)) {
       channel->mFlags &= ~DATA_CHANNEL_FLAGS_CLOSING_TOO_LARGE;
     }
   }
 
   // Buffer message until complete
   bufferFlags =
       BufferMessage(channel->mRecvBuffer, buffer, data_length, ppid, flags);
   if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_TOO_LARGE) {
@@ -1984,17 +1986,19 @@ void DataChannelConnection::ClearResets(
   }
 
   for (uint32_t i = 0; i < mStreamsResetting.Length(); ++i) {
     RefPtr<DataChannel> channel;
     channel = FindChannelByStream(mStreamsResetting[i]);
     if (channel) {
       LOG(("Forgetting channel %u (%p) with pending reset", channel->mStream,
            channel.get()));
-      mStreams[channel->mStream] = nullptr;
+      // TODO: Do we _really_ want to remove this? Are we allowed to reuse the
+      // id?
+      mChannels.Remove(channel);
     }
   }
   mStreamsResetting.Clear();
 }
 
 void DataChannelConnection::ResetOutgoingStream(uint16_t stream) {
   uint32_t i;
 
@@ -2066,21 +2070,20 @@ void DataChannelConnection::HandleStream
           // 2. We sent our own reset (CLOSING); either they crossed on the
           //    wire, or this is a response to our Reset.
           //    Go to CLOSED
           // 3. We've sent a open but haven't gotten a response yet (CONNECTING)
           //    I believe this is impossible, as we don't have an input stream
           //    yet.
 
           LOG(("Incoming: Channel %u  closed", channel->mStream));
-          if (mStreams[channel->mStream]) {
+          if (mChannels.Remove(channel)) {
             // Mark the stream for reset (the reset is sent below)
             ResetOutgoingStream(channel->mStream);
           }
-          mStreams[channel->mStream] = nullptr;
 
           LOG(("Disconnected DataChannel %p from connection %p",
                (void*)channel.get(), (void*)channel->mConnection.get()));
           channel->StreamClosedLocked();
         } else {
           LOG(("Can't find incoming channel %d", i));
         }
       }
@@ -2091,104 +2094,72 @@ void DataChannelConnection::HandleStream
   if (!mStreamsResetting.IsEmpty()) {
     LOG(("Sending %zu pending resets", mStreamsResetting.Length()));
     SendOutgoingStreamReset();
   }
 }
 
 void DataChannelConnection::HandleStreamChangeEvent(
     const struct sctp_stream_change_event* strchg) {
-  uint16_t stream;
-  RefPtr<DataChannel> channel;
-
+  ASSERT_WEBRTC(!NS_IsMainThread());
   if (strchg->strchange_flags == SCTP_STREAM_CHANGE_DENIED) {
     LOG(("*** Failed increasing number of streams from %zu (%u/%u)",
-         mStreams.Length(), strchg->strchange_instrms,
+         mNegotiatedIdLimit, strchg->strchange_instrms,
          strchg->strchange_outstrms));
     // XXX FIX! notify pending opens of failure
     return;
   }
-  if (strchg->strchange_instrms > mStreams.Length()) {
-    LOG(("Other side increased streams from %zu to %u", mStreams.Length(),
+  if (strchg->strchange_instrms > mNegotiatedIdLimit) {
+    LOG(("Other side increased streams from %zu to %u", mNegotiatedIdLimit,
          strchg->strchange_instrms));
   }
-  if (strchg->strchange_outstrms > mStreams.Length() ||
-      strchg->strchange_instrms > mStreams.Length()) {
-    uint16_t old_len = mStreams.Length();
-    uint16_t new_len =
-        std::max(strchg->strchange_outstrms, strchg->strchange_instrms);
+  uint16_t old_limit = mNegotiatedIdLimit;
+  uint16_t new_limit =
+      std::max(strchg->strchange_outstrms, strchg->strchange_instrms);
+  if (new_limit > mNegotiatedIdLimit) {
     LOG(("Increasing number of streams from %u to %u - adding %u (in: %u)",
-         old_len, new_len, new_len - old_len, strchg->strchange_instrms));
+         old_limit, new_limit, new_limit - old_limit,
+         strchg->strchange_instrms));
     // make sure both are the same length
-    mStreams.AppendElements(new_len - old_len);
-    LOG(("New length = %zu (was %d)", mStreams.Length(), old_len));
-    for (size_t i = old_len; i < mStreams.Length(); ++i) {
-      mStreams[i] = nullptr;
-    }
+    mNegotiatedIdLimit = new_limit;
+    LOG(("New length = %zu (was %d)", mNegotiatedIdLimit, old_limit));
     // Re-process any channels waiting for streams.
     // Linear search, but we don't increase channels often and
     // the array would only get long in case of an app error normally
 
     // Make sure we request enough streams if there's a big jump in streams
     // Could make a more complex API for OpenXxxFinish() and avoid this loop
-    size_t num_needed = mPending.GetSize();
-    LOG(("%zu of %d new streams already needed", num_needed,
-         new_len - old_len));
-    num_needed -= (new_len - old_len);  // number we added
-    if (num_needed > 0) {
-      if (num_needed < 16) num_needed = 16;
-      LOG(("Not enough new streams, asking for %zu more", num_needed));
+    auto channels = mChannels.GetAll();
+    size_t num_needed =
+        channels.Length() ? (channels.LastElement()->mStream + 1) : 0;
+    MOZ_ASSERT(num_needed != INVALID_STREAM);
+    if (num_needed > new_limit) {
+      int32_t more_needed = num_needed - ((int32_t)mNegotiatedIdLimit) + 16;
+      LOG(("Not enough new streams, asking for %d more", more_needed));
       // TODO: parameter is an int32_t but we pass size_t
-      RequestMoreStreams(num_needed);
+      RequestMoreStreams(more_needed);
     } else if (strchg->strchange_outstrms < strchg->strchange_instrms) {
       LOG(("Requesting %d output streams to match partner",
            strchg->strchange_instrms - strchg->strchange_outstrms));
       RequestMoreStreams(strchg->strchange_instrms -
                          strchg->strchange_outstrms);
     }
 
     ProcessQueuedOpens();
   }
   // else probably not a change in # of streams
 
-  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-    channel = mStreams[i];
-    if (!channel) continue;
-
-    if (channel->mStream == INVALID_STREAM) {
-      if ((strchg->strchange_flags & SCTP_STREAM_CHANGE_DENIED) ||
-          (strchg->strchange_flags & SCTP_STREAM_CHANGE_FAILED)) {
+  if ((strchg->strchange_flags & SCTP_STREAM_CHANGE_DENIED) ||
+      (strchg->strchange_flags & SCTP_STREAM_CHANGE_FAILED)) {
+    // Other side denied our request. Need to AnnounceClosed some stuff.
+    for (auto& channel : mChannels.GetAll()) {
+      if (channel->mStream >= mNegotiatedIdLimit) {
         /* XXX: Signal to the other end. */
         channel->AnnounceClosed();
         // maybe fire onError (bug 843625)
-      } else {
-        stream = FindFreeStream();
-        if (stream != INVALID_STREAM) {
-          channel->mStream = stream;
-          mStreams[stream] = channel;
-
-          // Send open request
-          int error = SendOpenRequestMessage(
-              channel->mLabel, channel->mProtocol, channel->mStream,
-              !!(channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED),
-              channel->mPrPolicy, channel->mPrValue);
-          if (error) {
-            LOG(("SendOpenRequest failed, error = %d", error));
-            // Close the channel, inform the user
-            mStreams[channel->mStream] = nullptr;
-            channel->AnnounceClosed();
-            // Don't need to reset; we didn't open it
-          } else {
-            channel->mFlags |= DATA_CHANNEL_FLAGS_READY;
-            channel->AnnounceOpen();
-          }
-        } else {
-          /* We will not find more ... */
-          break;
-        }
       }
     }
   }
 }
 
 // Called with mLock locked!
 void DataChannelConnection::HandleNotification(
     const union sctp_notification* notif, size_t n) {
@@ -2274,19 +2245,28 @@ int DataChannelConnection::ReceiveCallba
   // usrsctp defines the callback as returning an int, but doesn't use it
   return 1;
 }
 
 already_AddRefed<DataChannel> DataChannelConnection::Open(
     const nsACString& label, const nsACString& protocol, Type type,
     bool inOrder, uint32_t prValue, DataChannelListener* aListener,
     nsISupports* aContext, bool aExternalNegotiated, uint16_t aStream) {
+  ASSERT_WEBRTC(NS_IsMainThread());
   if (!aExternalNegotiated) {
-    // aStream == INVALID_STREAM to have the protocol allocate
-    aStream = INVALID_STREAM;
+    if (mAllocateEven.isSome()) {
+      aStream = FindFreeStream();
+      if (aStream == INVALID_STREAM) {
+        return nullptr;
+      }
+    } else {
+      // We do not yet know whether we are client or server, and an id has not
+      // been chosen for us. We will need to choose later.
+      aStream = INVALID_STREAM;
+    }
   }
   uint16_t prPolicy = SCTP_PR_SCTP_NONE;
 
   LOG(
       ("DC Open: label %s/%s, type %u, inorder %d, prValue %u, listener %p, "
        "context %p, external: %s, stream %u",
        PromiseFlatCString(label).get(), PromiseFlatCString(protocol).get(),
        type, inOrder, prValue, aListener, aContext,
@@ -2305,40 +2285,39 @@ already_AddRefed<DataChannel> DataChanne
       LOG(("ERROR: unsupported channel type: %u", type));
       MOZ_ASSERT(false);
       return nullptr;
   }
   if ((prPolicy == SCTP_PR_SCTP_NONE) && (prValue != 0)) {
     return nullptr;
   }
 
-  // Don't look past currently-negotiated streams
-  if (aStream != INVALID_STREAM && aStream < mStreams.Length() &&
-      mStreams[aStream]) {
+  if (aStream != INVALID_STREAM && mChannels.Get(aStream)) {
     LOG(("ERROR: external negotiation of already-open channel %u", aStream));
     // XXX How do we indicate this up to the application?  Probably the
     // caller's job, but we may need to return an error code.
     return nullptr;
   }
 
   RefPtr<DataChannel> channel(new DataChannel(
       this, aStream, DataChannel::CONNECTING, label, protocol, prPolicy,
       prValue, inOrder, aExternalNegotiated, aListener, aContext));
+  mChannels.Insert(channel);
 
   MutexAutoLock lock(mLock);  // OpenFinish assumes this
   return OpenFinish(channel.forget());
 }
 
 // Separate routine so we can also call it to finish up from pending opens
 already_AddRefed<DataChannel> DataChannelConnection::OpenFinish(
     already_AddRefed<DataChannel>&& aChannel) {
   RefPtr<DataChannel> channel(aChannel);  // takes the reference passed in
   // Normally 1 reference if called from ::Open(), or 2 if called from
   // ProcessQueuedOpens() unless the DOMDataChannel was gc'd
-  uint16_t stream = channel->mStream;
+  const uint16_t stream = channel->mStream;
   bool queue = false;
 
   mLock.AssertCurrentThreadOwns();
 
   // Cases we care about:
   // Pre-negotiated:
   //    Not Open:
   //      Doesn't fit:
@@ -2358,62 +2337,53 @@ already_AddRefed<DataChannel> DataChanne
   //         -> RequestMoreStreams && queue
   //      Does fit:
   //         -> open
   // So the Open cases are basically the same
   // Not Open cases are simply queue for non-negotiated, and
   // either change the initial ask or possibly renegotiate after open.
 
   if (mState == OPEN) {
-    if (stream == INVALID_STREAM) {
-      stream = FindFreeStream();  // may be INVALID_STREAM if we need more
-    }
-    if (stream == INVALID_STREAM || stream >= mStreams.Length()) {
+    MOZ_ASSERT(stream != INVALID_STREAM);
+    if (stream >= mNegotiatedIdLimit) {
       // RequestMoreStreams() limits to MAX_NUM_STREAMS -- allocate extra
       // streams to avoid going back immediately for more if the ask to N, N+1,
       // etc
-      int32_t more_needed = (stream == INVALID_STREAM)
-                                ? 16
-                                : (stream - ((int32_t)mStreams.Length())) + 16;
+      int32_t more_needed = stream - ((int32_t)mNegotiatedIdLimit) + 16;
       if (!RequestMoreStreams(more_needed)) {
         // Something bad happened... we're done
         goto request_error_cleanup;
       }
       queue = true;
     }
   } else {
     // not OPEN
-    if (stream != INVALID_STREAM && stream >= mStreams.Length() &&
+    if (stream != INVALID_STREAM && stream >= mNegotiatedIdLimit &&
         mState == CLOSED) {
       // Update number of streams for init message
       struct sctp_initmsg initmsg;
       socklen_t len = sizeof(initmsg);
-      int32_t total_needed = stream + 16;
+      uint16_t total_needed =
+          (stream < UINT16_MAX - 16) ? stream + 16 : UINT16_MAX;
 
       memset(&initmsg, 0, sizeof(initmsg));
       if (usrsctp_getsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INITMSG,
                              &initmsg, &len) < 0) {
         LOG(("*** failed getsockopt SCTP_INITMSG"));
         goto request_error_cleanup;
       }
       LOG(("Setting number of SCTP streams to %u, was %u/%u", total_needed,
            initmsg.sinit_num_ostreams, initmsg.sinit_max_instreams));
       initmsg.sinit_num_ostreams = total_needed;
       initmsg.sinit_max_instreams = MAX_NUM_STREAMS;
       if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_INITMSG,
                              &initmsg, (socklen_t)sizeof(initmsg)) < 0) {
         LOG(("*** failed setsockopt SCTP_INITMSG, errno %d", errno));
         goto request_error_cleanup;
       }
-
-      int32_t old_len = mStreams.Length();
-      mStreams.AppendElements(total_needed - old_len);
-      for (int32_t i = old_len; i < total_needed; ++i) {
-        mStreams[i] = nullptr;
-      }
     }
     // else if state is CONNECTING, we'll just re-negotiate when OpenFinish
     // is called, if needed
     queue = true;
   }
   if (queue) {
     LOG(("Queuing channel %p (%u) to finish open", channel.get(), stream));
     // Also serves to mark we told the app
@@ -2421,57 +2391,51 @@ already_AddRefed<DataChannel> DataChanne
     // we need a ref for the nsDeQue and one to return
     DataChannel* rawChannel = channel;
     rawChannel->AddRef();
     mPending.Push(rawChannel);
     return channel.forget();
   }
 
   MOZ_ASSERT(stream != INVALID_STREAM);
-  // just allocated (& OPEN), or externally negotiated
-  mStreams[stream] = channel;  // holds a reference
-  channel->mStream = stream;
+  MOZ_ASSERT(stream < mNegotiatedIdLimit);
 
 #ifdef TEST_QUEUED_DATA
   // It's painful to write a test for this...
   channel->AnnounceOpen();
-  channel->mFlags |= DATA_CHANNEL_FLAGS_READY;
   SendDataMsgInternalOrBuffer(channel, "Help me!", 8,
                               DATA_CHANNEL_PPID_DOMSTRING);
 #endif
 
-  if (channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED) {
+  if (!channel->mOrdered) {
     // Don't send unordered until this gets cleared
     channel->mFlags |= DATA_CHANNEL_FLAGS_WAITING_ACK;
   }
 
-  if (!(channel->mFlags & DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED)) {
-    int error = SendOpenRequestMessage(
-        channel->mLabel, channel->mProtocol, stream,
-        !!(channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED),
-        channel->mPrPolicy, channel->mPrValue);
+  if (!channel->mNegotiated) {
+    int error = SendOpenRequestMessage(channel->mLabel, channel->mProtocol,
+                                       stream, !channel->mOrdered,
+                                       channel->mPrPolicy, channel->mPrValue);
     if (error) {
       LOG(("SendOpenRequest failed, error = %d", error));
       if (channel->mFlags & DATA_CHANNEL_FLAGS_FINISH_OPEN) {
         // We already returned the channel to the app.
         NS_ERROR("Failed to send open request");
         channel->AnnounceClosed();
       }
       // If we haven't returned the channel yet, it will get destroyed when we
       // exit this function.
-      mStreams[stream] = nullptr;
-      channel->mStream = INVALID_STREAM;
+      mChannels.Remove(channel);
       // we'll be destroying the channel
       return nullptr;
       /* NOTREACHED */
     }
   }
 
   // Either externally negotiated or we sent Open
-  channel->mFlags |= DATA_CHANNEL_FLAGS_READY;
   // FIX?  Move into DOMDataChannel?  I don't think we can send it yet here
   channel->AnnounceOpen();
 
   return channel.forget();
 
 request_error_cleanup:
   if (channel->mFlags & DATA_CHANNEL_FLAGS_FINISH_OPEN) {
     // We already returned the channel to the app.
@@ -2645,35 +2609,34 @@ int DataChannelConnection::SendDataMsgIn
   // General flags
   info.sendv_flags = SCTP_SEND_SNDINFO_VALID;
 
   // Set stream identifier, protocol identifier and flags
   info.sendv_sndinfo.snd_sid = channel.mStream;
   info.sendv_sndinfo.snd_flags = SCTP_EOR;
   info.sendv_sndinfo.snd_ppid = htonl(ppid);
 
+  MutexAutoLock lock(mLock);  // Need to protect mFlags... :(
   // Unordered?
   // To avoid problems where an in-order OPEN is lost and an
   // out-of-order data message "beats" it, require data to be in-order
   // until we get an ACK.
-  if ((channel.mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED) &&
-      !(channel.mFlags & DATA_CHANNEL_FLAGS_WAITING_ACK)) {
+  if (!channel.mOrdered && !(channel.mFlags & DATA_CHANNEL_FLAGS_WAITING_ACK)) {
     info.sendv_sndinfo.snd_flags |= SCTP_UNORDERED;
   }
 
   // Partial reliability policy
   if (channel.mPrPolicy != SCTP_PR_SCTP_NONE) {
     info.sendv_prinfo.pr_policy = channel.mPrPolicy;
     info.sendv_prinfo.pr_value = channel.mPrValue;
     info.sendv_flags |= SCTP_SEND_PRINFO_VALID;
   }
 
   // Create message instance and send
   OutgoingMsg msg(info, data, len);
-  MutexAutoLock lock(mLock);
   bool buffered;
   size_t written = 0;
   mDeferSend = true;
   int error =
       SendMsgInternalOrBuffer(channel.mBufferedData, msg, buffered, &written);
   mDeferSend = false;
   if (written) {
     channel.DecrementBufferedAmount(written);
@@ -2738,17 +2701,17 @@ class ReadBlobRunnable : public Runnable
   RefPtr<DataChannelConnection> mConnection;
   uint16_t mStream;
   // Use RefCount for preventing the object is deleted when SendBlob returns.
   RefPtr<nsIInputStream> mBlob;
 };
 
 // Returns a POSIX error code.
 int DataChannelConnection::SendBlob(uint16_t stream, nsIInputStream* aBlob) {
-  DataChannel* channel = mStreams[stream];
+  RefPtr<DataChannel> channel = mChannels.Get(stream);
   if (NS_WARN_IF(!channel)) {
     return EINVAL;  // TODO: Find a better error code
   }
 
   // Spawn a thread to send the data
   if (!mInternalIOThread) {
     nsresult rv =
         NS_NewNamedThread("DataChannel IO", getter_AddRefs(mInternalIOThread));
@@ -2824,47 +2787,37 @@ void DataChannelConnection::ReadBlob(
     NS_ReleaseOnMainThreadSystemGroup("DataChannelBlobSendRunnable",
                                       runnable.forget());
     return;
   }
   aBlob->Close();
   Dispatch(runnable.forget());
 }
 
-void DataChannelConnection::GetStreamIds(std::vector<uint16_t>* aStreamList) {
-  ASSERT_WEBRTC(NS_IsMainThread());
-  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-    if (mStreams[i]) {
-      aStreamList->push_back(mStreams[i]->mStream);
-    }
-  }
-}
-
 // Returns a POSIX error code.
 int DataChannelConnection::SendDataMsgCommon(uint16_t stream,
                                              const nsACString& aMsg,
                                              bool isBinary) {
   ASSERT_WEBRTC(NS_IsMainThread());
   // We really could allow this from other threads, so long as we deal with
   // asynchronosity issues with channels closing, in particular access to
-  // mStreams, and issues with the association closing (access to mSocket).
+  // mChannels, and issues with the association closing (access to mSocket).
 
   const uint8_t* data = (const uint8_t*)aMsg.BeginReading();
   uint32_t len = aMsg.Length();
 #if (UINT32_MAX > SIZE_MAX)
   if (len > SIZE_MAX) {
     return EMSGSIZE;
   }
 #endif
-  DataChannel* channelPtr;
 
   LOG(("Sending %sto stream %u: %u bytes", isBinary ? "binary " : "", stream,
        len));
   // XXX if we want more efficiency, translate flags once at open time
-  channelPtr = mStreams[stream];
+  RefPtr<DataChannel> channelPtr = mChannels.Get(stream);
   if (NS_WARN_IF(!channelPtr)) {
     return EINVAL;  // TODO: Find a better error code
   }
 
   auto& channel = *channelPtr;
 
   if (isBinary) {
     return SendDataMsg(channel, data, len, DATA_CHANNEL_PPID_BINARY_PARTIAL,
@@ -2889,34 +2842,34 @@ void DataChannelConnection::Close(DataCh
 // Called from someone who holds a ref via ::Close(), or from ~DataChannel
 void DataChannelConnection::CloseInt(DataChannel* aChannel) {
   MOZ_ASSERT(aChannel);
   RefPtr<DataChannel> channel(aChannel);  // make sure it doesn't go away on us
 
   mLock.AssertCurrentThreadOwns();
   LOG(("Connection %p/Channel %p: Closing stream %u",
        channel->mConnection.get(), channel.get(), channel->mStream));
+
+  aChannel->mBufferedData.Clear();
+  if (mState == CLOSED) {
+    // If we're CLOSING, we might leave this in place until we can send a
+    // reset.
+    mChannels.Remove(channel);
+  }
+
   // re-test since it may have closed before the lock was grabbed
   if (aChannel->mReadyState == CLOSED || aChannel->mReadyState == CLOSING) {
     LOG(("Channel already closing/closed (%u)", aChannel->mReadyState));
-    if (mState == CLOSED && channel->mStream != INVALID_STREAM) {
-      // called from CloseAll()
-      // we're not going to hang around waiting any more
-      mStreams[channel->mStream] = nullptr;
-    }
     return;
   }
-  aChannel->mBufferedData.Clear();
+
   if (channel->mStream != INVALID_STREAM) {
     ResetOutgoingStream(channel->mStream);
-    if (mState == CLOSED) {  // called from CloseAll()
-      // Let resets accumulate then send all at once in CloseAll()
-      // we're not going to hang around waiting
-      mStreams[channel->mStream] = nullptr;
-    } else {
+    if (mState != CLOSED) {
+      // Individual channel is being closed, send reset now.
       SendOutgoingStreamReset();
     }
   }
   aChannel->mReadyState = CLOSING;
   if (mState == CLOSED) {
     // we're not going to hang around waiting
     channel->StreamClosedLocked();
   }
@@ -2932,39 +2885,99 @@ void DataChannelConnection::CloseAll() {
   {
     MutexAutoLock lock(mLock);
     mState = CLOSED;
   }
 
   // Close current channels
   // If there are runnables, they hold a strong ref and keep the channel
   // and/or connection alive (even if in a CLOSED state)
-  bool closed_some = false;
-  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-    if (mStreams[i]) {
-      mStreams[i]->Close();
-      closed_some = true;
-    }
+  for (auto& channel : mChannels.GetAll()) {
+    channel->Close();
   }
 
   // Clean up any pending opens for channels
   RefPtr<DataChannel> channel;
   while (nullptr != (channel = dont_AddRef(
                          static_cast<DataChannel*>(mPending.PopFront())))) {
     LOG(("closing pending channel %p, stream %u", channel.get(),
          channel->mStream));
     channel->Close();  // also releases the ref on each iteration
-    closed_some = true;
   }
   // It's more efficient to let the Resets queue in shutdown and then
   // SendOutgoingStreamReset() here.
-  if (closed_some) {
-    MutexAutoLock lock(mLock);
-    SendOutgoingStreamReset();
+  MutexAutoLock lock(mLock);
+  SendOutgoingStreamReset();
+}
+
+bool DataChannelConnection::Channels::IdComparator::Equals(
+    const RefPtr<DataChannel>& aChannel, uint16_t aId) const {
+  return aChannel->mStream == aId;
+}
+
+bool DataChannelConnection::Channels::IdComparator::LessThan(
+    const RefPtr<DataChannel>& aChannel, uint16_t aId) const {
+  return aChannel->mStream < aId;
+}
+
+bool DataChannelConnection::Channels::IdComparator::Equals(
+    const RefPtr<DataChannel>& a1, const RefPtr<DataChannel>& a2) const {
+  return Equals(a1, a2->mStream);
+}
+
+bool DataChannelConnection::Channels::IdComparator::LessThan(
+    const RefPtr<DataChannel>& a1, const RefPtr<DataChannel>& a2) const {
+  return LessThan(a1, a2->mStream);
+}
+
+void DataChannelConnection::Channels::Insert(
+    const RefPtr<DataChannel>& aChannel) {
+  LOG(("Inserting channel %u : %p", aChannel->mStream, aChannel.get()));
+  MutexAutoLock lock(mMutex);
+  if (aChannel->mStream != INVALID_STREAM) {
+    MOZ_ASSERT(!mChannels.ContainsSorted(aChannel, IdComparator()));
   }
+
+  MOZ_ASSERT(!mChannels.Contains(aChannel));
+
+  mChannels.InsertElementSorted(aChannel, IdComparator());
+}
+
+bool DataChannelConnection::Channels::Remove(
+    const RefPtr<DataChannel>& aChannel) {
+  LOG(("Removing channel %u : %p", aChannel->mStream, aChannel.get()));
+  MutexAutoLock lock(mMutex);
+  if (aChannel->mStream == INVALID_STREAM) {
+    return mChannels.RemoveElement(aChannel);
+  }
+
+  return mChannels.RemoveElementSorted(aChannel, IdComparator());
+}
+
+RefPtr<DataChannel> DataChannelConnection::Channels::Get(uint16_t aId) const {
+  MutexAutoLock lock(mMutex);
+  auto index = mChannels.BinaryIndexOf(aId, IdComparator());
+  if (index == ChannelArray::NoIndex) {
+    return nullptr;
+  }
+  return mChannels[index];
+}
+
+RefPtr<DataChannel> DataChannelConnection::Channels::GetNextChannel(
+    uint16_t aCurrentId) const {
+  MutexAutoLock lock(mMutex);
+  if (mChannels.IsEmpty()) {
+    return nullptr;
+  }
+
+  auto index = mChannels.IndexOfFirstElementGt(aCurrentId, IdComparator());
+  if (index == mChannels.Length()) {
+    index = 0;
+  }
+  return mChannels[index];
 }
 
 DataChannel::~DataChannel() {
   // NS_ASSERTION since this is more "I think I caught all the cases that
   // can cause this" than a true kill-the-program assertion.  If this is
   // wrong, nothing bad happens.  A worst it's a leak.
   NS_ASSERTION(mReadyState == CLOSED || mReadyState == CLOSING,
                "unexpected state in ~DataChannel");
@@ -2981,18 +2994,16 @@ void DataChannel::Close() {
 // Used when disconnecting from the DataChannelConnection
 void DataChannel::StreamClosedLocked() {
   mConnection->mLock.AssertCurrentThreadOwns();
   ENSURE_DATACONNECTION;
 
   LOG(("Destroying Data channel %u", mStream));
   MOZ_ASSERT_IF(mStream != INVALID_STREAM,
                 !mConnection->FindChannelByStream(mStream));
-  // Spec doesn't say to mess with the stream id...
-  mStream = INVALID_STREAM;
   AnnounceClosed();
   // We leave mConnection live until the DOM releases us, to avoid races
 }
 
 void DataChannel::ReleaseConnection() {
   ASSERT_WEBRTC(NS_IsMainThread());
   mConnection = nullptr;
 }
--- a/netwerk/sctp/datachannel/DataChannel.h
+++ b/netwerk/sctp/datachannel/DataChannel.h
@@ -159,17 +159,17 @@ class DataChannelConnection final : publ
 #endif
 
 #ifdef SCTP_DTLS_SUPPORTED
   bool ConnectToTransport(const std::string& aTransportId, bool aClient,
                           uint16_t localport, uint16_t remoteport);
   void TransportStateChange(const std::string& aTransportId,
                             TransportLayer::State aState);
   void CompleteConnect();
-  void SetSignals(const std::string& aTransportId, bool aClient);
+  void SetSignals(const std::string& aTransportId);
 #endif
 
   typedef enum {
     RELIABLE = 0,
     PARTIAL_RELIABLE_REXMIT = 1,
     PARTIAL_RELIABLE_TIMED = 2
   } Type;
 
@@ -211,18 +211,16 @@ class DataChannelConnection final : publ
   }
 
   friend class DataChannel;
   Mutex mLock;
 
   void ReadBlob(already_AddRefed<DataChannelConnection> aThis, uint16_t aStream,
                 nsIInputStream* aBlob);
 
-  void GetStreamIds(std::vector<uint16_t>* aStreamList);
-
   bool SendDeferredMessages();
 
  protected:
   friend class DataChannelOnMessageAvailable;
   // Avoid cycles with PeerConnectionImpl
   // Use from main thread only as WeakPtr is not threadsafe
   WeakPtr<DataConnectionListener> mListener;
 
@@ -305,35 +303,66 @@ class DataChannelConnection final : publ
     bool on = false;
     if (mSTS) {
       mSTS->IsOnCurrentThread(&on);
     }
     return on;
   }
 #endif
 
+  class Channels {
+   public:
+    Channels() : mMutex("DataChannelConnection::Channels::mMutex") {}
+    void Insert(const RefPtr<DataChannel>& aChannel);
+    bool Remove(const RefPtr<DataChannel>& aChannel);
+    RefPtr<DataChannel> Get(uint16_t aId) const;
+    typedef AutoTArray<RefPtr<DataChannel>, 16> ChannelArray;
+    ChannelArray GetAll() const {
+      MutexAutoLock lock(mMutex);
+      return mChannels;
+    }
+    RefPtr<DataChannel> GetNextChannel(uint16_t aCurrentId) const;
+
+   private:
+    struct IdComparator {
+      bool Equals(const RefPtr<DataChannel>& aChannel, uint16_t aId) const;
+      bool LessThan(const RefPtr<DataChannel>& aChannel, uint16_t aId) const;
+      bool Equals(const RefPtr<DataChannel>& a1,
+                  const RefPtr<DataChannel>& a2) const;
+      bool LessThan(const RefPtr<DataChannel>& a1,
+                    const RefPtr<DataChannel>& a2) const;
+    };
+    mutable Mutex mMutex;
+    ChannelArray mChannels;
+  };
+
   bool mSendInterleaved = false;
   bool mMaxMessageSizeSet = false;
   uint64_t mMaxMessageSize = 0;
-  bool mAllocateEven = false;
+  // Main thread only
+  Maybe<bool> mAllocateEven;
   // Data:
-  // NOTE: while this array will auto-expand, increases in the number of
+  // NOTE: while this container will auto-expand, increases in the number of
   // channels available from the stack must be negotiated!
-  AutoTArray<RefPtr<DataChannel>, 16> mStreams;
+  // Accessed from both main and sts, API is threadsafe
+  Channels mChannels;
+  // STS only
   uint32_t mCurrentStream = 0;
   nsDeque mPending;  // Holds addref'ed DataChannel's -- careful!
+  // STS and main
+  size_t mNegotiatedIdLimit = 0;  // GUARDED_BY(mConnection->mLock)
   uint8_t mPendingType = PENDING_NONE;
   // holds data that's come in before a channel is open
   nsTArray<nsAutoPtr<QueuedDataMessage>> mQueuedData;
   // holds outgoing control messages
   nsTArray<nsAutoPtr<BufferedOutgoingMsg>>
       mBufferedControl;  // GUARDED_BY(mConnection->mLock)
 
-  // Streams pending reset
-  AutoTArray<uint16_t, 4> mStreamsResetting;
+  // Streams pending reset. Accessed from main and STS.
+  AutoTArray<uint16_t, 4> mStreamsResetting;  // GUARDED_BY(mConnection->mLock)
   // accessed from STS thread
   struct socket* mMasterSocket = nullptr;
   // cloned from mMasterSocket on successful Connect on STS thread
   struct socket* mSocket = nullptr;
   uint16_t mState = CLOSED;  // Protected with mLock
 
 #ifdef SCTP_DTLS_SUPPORTED
   std::string mTransportId;
@@ -380,27 +409,20 @@ class DataChannel {
         mProtocol(protocol),
         mReadyState(state),
         mStream(stream),
         mPrPolicy(policy),
         mPrValue(value),
         mNegotiated(negotiated),
         mOrdered(ordered),
         mFlags(0),
-        mId(0),
         mIsRecvBinary(false),
         mBufferedThreshold(0),  // default from spec
         mBufferedAmount(0),
         mMainThreadEventTarget(connection->GetNeckoTarget()) {
-    if (!ordered) {
-      mFlags |= DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED;
-    }
-    if (negotiated) {
-      mFlags |= DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED;
-    }
     NS_ASSERTION(mConnection, "NULL connection");
   }
 
  private:
   ~DataChannel();
 
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataChannel)
@@ -488,20 +510,20 @@ class DataChannel {
   RefPtr<DataChannelConnection> mConnection;
   nsCString mLabel;
   nsCString mProtocol;
   // This is mainthread only
   uint16_t mReadyState;
   uint16_t mStream;
   uint16_t mPrPolicy;
   uint32_t mPrValue;
+  // Accessed on main and STS
   const bool mNegotiated;
   const bool mOrdered;
   uint32_t mFlags;
-  uint32_t mId;
   bool mIsRecvBinary;
   size_t mBufferedThreshold;
   // Read/written on main only. Decremented via message-passing, because the
   // spec requires us to queue a task for this.
   size_t mBufferedAmount;
   nsCString mRecvBuffer;
   nsTArray<nsAutoPtr<BufferedOutgoingMsg>>
       mBufferedData;  // GUARDED_BY(mConnection->mLock)
--- a/netwerk/sctp/datachannel/DataChannelProtocol.h
+++ b/netwerk/sctp/datachannel/DataChannelProtocol.h
@@ -31,20 +31,17 @@
 #define DATA_CHANNEL_PPID_CONTROL 50
 #define DATA_CHANNEL_PPID_BINARY_PARTIAL 52
 #define DATA_CHANNEL_PPID_BINARY 53
 #define DATA_CHANNEL_PPID_DOMSTRING_PARTIAL 54
 #define DATA_CHANNEL_PPID_DOMSTRING 51
 
 #define DATA_CHANNEL_MAX_BINARY_FRAGMENT 0x4000
 
-#define DATA_CHANNEL_FLAGS_READY 0x00000001
-#define DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED 0x00000002
 #define DATA_CHANNEL_FLAGS_FINISH_OPEN 0x00000004
-#define DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED 0x00000008
 #define DATA_CHANNEL_FLAGS_WAITING_ACK 0x00000010
 #define DATA_CHANNEL_FLAGS_CLOSING_TOO_LARGE 0x00000020
 
 #define DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_TOO_LARGE 0x01
 #define DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED 0x02
 #define DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_COMPLETE 0x04
 
 #define INVALID_STREAM (0xFFFF)
--- a/taskcluster/ci/test/web-platform.yml
+++ b/taskcluster/ci/test/web-platform.yml
@@ -53,17 +53,16 @@ web-platform-tests:
             default: 12
     max-run-time:
         by-test-platform:
             .*-ccov/debug: 10800
             default: 7200
     e10s: true
     run-on-projects:
         by-test-platform:
-            android.*: ['mozilla-central', 'try']
             windows10-aarch64/opt: ['try', 'mozilla-central']
             .*-qr/.*: ['release', 'try']  # skip on integration branches due to high load
             default: built-projects
     tier:
         by-test-platform:
             android.*: 2
             linux64-asan/opt: 2
             windows10-aarch64.*: 2
@@ -111,24 +110,21 @@ web-platform-tests-reftests:
             linux64(-shippable|-devedition)?/opt: 3
             macosx10(10|14)-64/debug: 6
             windows.*-(32|64)(-qr)?/debug: 5
             android.*: 6
             default: 4
     e10s: true
     run-on-projects:
         by-test-platform:
-            android.*: ['mozilla-central', 'try']
             windows10-aarch64/opt: ['try', 'mozilla-central']
             default: built-projects
     tier:
         by-test-platform:
-            android.*-x86_64/opt: 3
-            android.*-x86_64/debug: 3
-            android.*x86(?!x86_64)/opt: 2
+            android.*: 2
             linux64-asan/opt: 2
             windows10-aarch64.*: 2
             default: default
     mozharness:
         extra-options:
             - --test-type=reftest
 
 web-platform-tests-reftests-headless:
@@ -162,17 +158,16 @@ web-platform-tests-wdspec:
             .*-ccov/debug: 4
             default: 2
     mozharness:
         extra-options:
             - --test-type=wdspec
     run-on-projects:
         by-test-platform:
             windows10-aarch64/opt: ['try', 'mozilla-central']
-            android.*: ['mozilla-central', 'try']
             .*-qr/.*: ['release', 'try']
             default: built-projects
     tier:
         by-test-platform:
             android.*: 3
             linux64-asan/opt: 2
             .*-qr/.*: 2  # can't be tier-1 if it's not running on integration branches
             default: default
--- a/taskcluster/taskgraph/target_tasks.py
+++ b/taskcluster/taskgraph/target_tasks.py
@@ -302,16 +302,22 @@ def target_tasks_mozilla_esr68(full_task
 
     def filter(task):
         if not filter_release_tasks(task, parameters):
             return False
 
         if not standard_filter(task, parameters):
             return False
 
+        platform = task.attributes.get('test_platform')
+
+        # Don't run QuantumRender tests on esr68.
+        if platform and '-qr/' in platform:
+            return False
+
         # Unlike esr60, we do want all kinds of fennec builds on esr68.
 
         return True
 
     return [l for l, t in full_task_graph.tasks.iteritems() if filter(t)]
 
 
 @_target_task('promote_desktop')
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-backgrounds/background-image-first-line.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[background-image-first-line.html]
-  expected: FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-multicol/multicol-breaking-004.html.ini
@@ -0,0 +1,2 @@
+[multicol-breaking-004.html]
+  prefs: [layout.css.column-span.enabled:true]
--- a/testing/web-platform/meta/css/css-multicol/multicol-fill-auto-block-children-001.xht.ini
+++ b/testing/web-platform/meta/css/css-multicol/multicol-fill-auto-block-children-001.xht.ini
@@ -1,2 +1,2 @@
 [multicol-fill-auto-block-children-001.xht]
-  expected: FAIL
+  prefs: [layout.css.column-span.enabled:true]
--- a/testing/web-platform/meta/css/css-multicol/multicol-fill-auto-block-children-002.xht.ini
+++ b/testing/web-platform/meta/css/css-multicol/multicol-fill-auto-block-children-002.xht.ini
@@ -1,2 +1,2 @@
 [multicol-fill-auto-block-children-002.xht]
-  expected: FAIL
+  prefs: [layout.css.column-span.enabled:true]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-multicol/multicol-span-all-children-height-002.html.ini
@@ -0,0 +1,2 @@
+[multicol-span-all-children-height-002.html]
+  prefs: [layout.css.column-span.enabled:true]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/css-multicol/multicol-span-all-children-height-003.html.ini
@@ -0,0 +1,2 @@
+[multicol-span-all-children-height-003.html]
+  prefs: [layout.css.column-span.enabled:true]
--- a/testing/web-platform/meta/css/geometry/DOMMatrix-001.html.ini
+++ b/testing/web-platform/meta/css/geometry/DOMMatrix-001.html.ini
@@ -1,79 +1,10 @@
 [DOMMatrix-001.html]
-  [testConstructor0]
-    expected: FAIL
-
-  [testConstructor1]
-    expected: FAIL
-
-  [testConstructor2]
-    expected: FAIL
-
-  [testConstructor3]
-    expected: FAIL
-
-  [testConstructor4]
-    expected: FAIL
-
-  [testConstructor5]
-    expected: FAIL
-
-  [testConstructor6]
-    expected: FAIL
-
-  [testConstructor7]
-    expected: FAIL
-
-  [testConstructor8]
-    expected: FAIL
-
-  [testConstructor9]
-    expected: FAIL
-
-  [testConstructor10]
-    expected: FAIL
-
-  [testConstructor12]
-    expected: FAIL
-
-  [testConstructor13]
-    expected: FAIL
-
   [new DOMMatrix(undefined)]
     expected: FAIL
 
-  [new DOMMatrix(float32Array) 16 elements]
-    expected: FAIL
-
-  [new DOMMatrix(float64Array) 16 elements]
-    expected: FAIL
-
-  [new DOMMatrix(sequence) 16 elements]
-    expected: FAIL
-
-  [new DOMMatrix(sequence)]
-    expected: FAIL
-
-  [new DOMMatrix(matrix)]
-    expected: FAIL
-
-  [new DOMMatrixReadOnly(float32Array) 16 elements]
-    expected: FAIL
-
-  [new DOMMatrixReadOnly(float64Array) 16 elements]
-    expected: FAIL
-
-  [new DOMMatrixReadOnly(sequence) 16 elements]
-    expected: FAIL
-
-  [new DOMMatrixReadOnly(sequence)]
-    expected: FAIL
-
-  [new DOMMatrixReadOnly(matrix)]
-    expected: FAIL
-
   [new DOMMatrix("scale(2) translateX(5px) translateY(5px) rotate(5deg) rotate(-5deg)")]
     expected: FAIL