Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
authorBogdan Tara <btara@mozilla.com>
Fri, 08 Mar 2019 23:57:07 +0200
changeset 521238 0537da28038acb2e22e683f6b487c8eef1db400a
parent 521237 1972b883694851e31344e1eefff9ac5b95d2b88c (current diff)
parent 521152 67424fa758d40134fdca363ec9a7a992aa92403f (diff)
child 521239 d652c07b0c040250e7fe7dfcee4ed9d88a754bf7
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound. a=merge CLOSED TREE
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -2409,16 +2409,22 @@ Accessible* Accessible::CurrentItem() co
   // with the aria-activedescendant attribute.
   nsAutoString id;
   if (HasOwnContent() && mContent->IsElement() &&
       mContent->AsElement()->GetAttr(kNameSpaceID_None,
                                      nsGkAtoms::aria_activedescendant, id)) {
     dom::Document* DOMDoc = mContent->OwnerDoc();
     dom::Element* activeDescendantElm = DOMDoc->GetElementById(id);
     if (activeDescendantElm) {
+      if (nsContentUtils::ContentIsDescendantOf(mContent,
+                                                activeDescendantElm)) {
+        // Don't want a cyclical descendant relationship. That would be bad.
+        return nullptr;
+      }
+
       DocAccessible* document = Document();
       if (document) return document->GetAccessible(activeDescendantElm);
     }
   }
   return nullptr;
 }
 
 void Accessible::SetCurrentItem(const Accessible* aItem) {
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -30,16 +30,20 @@ file, You can obtain one at http://mozil
                          flex="1">
         <children/>
         <html:input anonid="scheme"
                     class="urlbar-scheme textbox-input"
                     required="required"
                     xbl:inherits="textoverflow,focused"/>
         <html:input anonid="input"
                     class="urlbar-input textbox-input"
+                    role="combobox"
+                    aria-owns="urlbarView-results"
+                    aria-controls="urlbarView-results"
+                    aria-autocomplete="both"
                     allowevents="true"
                     inputmode="mozAwesomebar"
                     xbl:inherits="value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,focused,textoverflow"/>
       </xul:moz-input-box>
       <xul:image anonid="urlbar-go-button"
                  class="urlbar-go-button urlbar-icon"
                  onclick="gURLBar.handleCommand(event);"
                  tooltiptext="&goEndCap.tooltip;"
--- a/browser/components/downloads/content/downloadsPanel.inc.xul
+++ b/browser/components/downloads/content/downloadsPanel.inc.xul
@@ -110,18 +110,17 @@
                      onmouseover="DownloadsView.onDownloadMouseOver(event);"
                      onmouseout="DownloadsView.onDownloadMouseOut(event);"
                      oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
                      ondragstart="DownloadsView.onDownloadDragStart(event);"/>
         <description id="emptyDownloads"
                      mousethrough="always"
                      value="&downloadsPanelEmpty.label;"/>
       </vbox>
-      <vbox id="downloadsFooter"
-            class="downloadsPanelFooter">
+      <vbox id="downloadsFooter">
         <stack>
           <hbox id="downloadsSummary"
                 align="center"
                 orient="horizontal"
                 onkeydown="DownloadsSummary.onKeyDown(event);"
                 onclick="DownloadsSummary.onClick(event);">
             <image class="downloadTypeIcon" />
             <vbox pack="center"
@@ -154,17 +153,17 @@
                class="PanelUI-subView"
                title="&downloadDetails.label;">
       <vbox class="panel-view-body-unscrollable">
         <description id="downloadsPanel-blockedSubview-title"/>
         <description id="downloadsPanel-blockedSubview-details1"/>
         <description id="downloadsPanel-blockedSubview-details2"/>
       </vbox>
       <hbox id="downloadsPanel-blockedSubview-buttons"
-            class="panel-footer downloadsPanelFooter"
+            class="panel-footer"
             align="stretch">
         <button id="downloadsPanel-blockedSubview-openButton"
                 class="downloadsPanelFooterButton"
                 command="downloadsCmd_unblockAndOpen"
                 flex="1"/>
         <button id="downloadsPanel-blockedSubview-deleteButton"
                 class="downloadsPanelFooterButton"
                 oncommand="DownloadsBlockedSubview.confirmBlock();"
--- a/browser/components/search/searchplugins/google-2018.xml
+++ b/browser/components/search/searchplugins/google-2018.xml
@@ -6,11 +6,12 @@
 <ShortName>Google</ShortName>
 <Description>Google Search</Description>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">resource://search-plugins/images/google.ico</Image>
 <Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=firefox&amp;q={searchTerms}"/>
 <Url type="text/html" method="GET" template="https://www.google.com/search" rel="searchform">
   <MozParam name="client" condition="purpose" purpose="keyword" value="firefox-b-1-ab"/>
   <MozParam name="client" condition="purpose" purpose="searchbar" value="firefox-b-1"/>
+  <MozParam name="channel" condition="pref" pref="google_channel_us"/>
   <Param name="q" value="{searchTerms}"/>
 </Url>
 </SearchPlugin>
--- a/browser/components/search/searchplugins/google-b-1-d.xml
+++ b/browser/components/search/searchplugins/google-b-1-d.xml
@@ -5,11 +5,12 @@
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Google</ShortName>
 <Description>Google Search</Description>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">resource://search-plugins/images/google.ico</Image>
 <Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=firefox&amp;q={searchTerms}"/>
 <Url type="text/html" method="GET" template="https://www.google.com/search" rel="searchform">
   <Param name="client" value="firefox-b-1-d"/>
+  <MozParam name="channel" condition="pref" pref="google_channel_us"/>
   <Param name="q" value="{searchTerms}"/>
 </Url>
 </SearchPlugin>
--- a/browser/components/search/searchplugins/google-b-1-e.xml
+++ b/browser/components/search/searchplugins/google-b-1-e.xml
@@ -5,11 +5,12 @@
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Google</ShortName>
 <Description>Google Search</Description>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">resource://search-plugins/images/google.ico</Image>
 <Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=firefox&amp;q={searchTerms}"/>
 <Url type="text/html" method="GET" template="https://www.google.com/search" rel="searchform">
   <Param name="client" value="firefox-b-1-e"/>
+  <MozParam name="channel" condition="pref" pref="google_channel_us"/>
   <Param name="q" value="{searchTerms}"/>
 </Url>
 </SearchPlugin>
--- a/browser/components/search/searchplugins/google-b-d.xml
+++ b/browser/components/search/searchplugins/google-b-d.xml
@@ -5,11 +5,12 @@
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Google</ShortName>
 <Description>Google Search</Description>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">resource://search-plugins/images/google.ico</Image>
 <Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=firefox&amp;q={searchTerms}"/>
 <Url type="text/html" method="GET" template="https://www.google.com/search" rel="searchform">
   <Param name="client" value="firefox-b-d"/>
+  <MozParam name="channel" condition="pref" pref="google_channel_row"/>
   <Param name="q" value="{searchTerms}"/>
 </Url>
 </SearchPlugin>
--- a/browser/components/search/searchplugins/google-b-e.xml
+++ b/browser/components/search/searchplugins/google-b-e.xml
@@ -5,11 +5,12 @@
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Google</ShortName>
 <Description>Google Search</Description>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">resource://search-plugins/images/google.ico</Image>
 <Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=firefox&amp;q={searchTerms}"/>
 <Url type="text/html" method="GET" template="https://www.google.com/search" rel="searchform">
   <Param name="client" value="firefox-b-e"/>
+  <MozParam name="channel" condition="pref" pref="google_channel_row"/>
   <Param name="q" value="{searchTerms}"/>
 </Url>
 </SearchPlugin>
--- a/browser/components/search/searchplugins/google.xml
+++ b/browser/components/search/searchplugins/google.xml
@@ -6,11 +6,12 @@
 <ShortName>Google</ShortName>
 <Description>Google Search</Description>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">resource://search-plugins/images/google.ico</Image>
 <Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=firefox&amp;q={searchTerms}"/>
 <Url type="text/html" method="GET" template="https://www.google.com/search" rel="searchform">
   <MozParam name="client" condition="purpose" purpose="keyword" value="firefox-b-ab"/>
   <MozParam name="client" condition="purpose" purpose="searchbar" value="firefox-b"/>
+  <MozParam name="channel" condition="pref" pref="google_channel_row"/>
   <Param name="q" value="{searchTerms}"/>
 </Url>
 </SearchPlugin>
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -52,26 +52,27 @@ class UrlbarInput {
 
     // Create the panel to contain results.
     // In the future this may be moved to the view, so it can customize
     // the container element.
     let MozXULElement = this.window.MozXULElement;
     this.document.getElementById("mainPopupSet").appendChild(
       MozXULElement.parseXULToFragment(`
         <panel id="urlbar-results"
-                role="group"
-                noautofocus="true"
-                hidden="true"
-                flip="none"
-                consumeoutsideclicks="never"
-                norolluponanchor="true"
-                level="parent">
+               role="group"
+               noautofocus="true"
+               hidden="true"
+               flip="none"
+               consumeoutsideclicks="never"
+               norolluponanchor="true"
+               level="parent">
           <html:div class="urlbarView-body-outer">
             <html:div class="urlbarView-body-inner">
-              <html:div class="urlbarView-results"/>
+              <html:div id="urlbarView-results"
+                        role="listbox"/>
             </html:div>
           </html:div>
           <hbox class="search-one-offs"
                 compact="true"
                 includecurrentengine="true"
                 disabletab="true"/>
         </panel>
       `));
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -29,17 +29,17 @@ class UrlbarView {
   constructor(input) {
     this.input = input;
     this.panel = input.panel;
     this.controller = input.controller;
     this.document = this.panel.ownerDocument;
     this.window = this.document.defaultView;
 
     this._mainContainer = this.panel.querySelector(".urlbarView-body-inner");
-    this._rows = this.panel.querySelector(".urlbarView-results");
+    this._rows = this.panel.querySelector("#urlbarView-results");
 
     this._rows.addEventListener("mouseup", this);
     this._rows.addEventListener("mousedown", this);
 
     // For the horizontal fade-out effect, set the overflow attribute on result
     // rows when they overflow.
     this._rows.addEventListener("overflow", this);
     this._rows.addEventListener("underflow", this);
@@ -381,18 +381,20 @@ class UrlbarView {
       this.panel.style.removeProperty("--item-padding-end");
     }
     this.panel.style.setProperty("--item-content-width", Math.round(contentWidth) + "px");
   }
 
   _createRow(resultIndex) {
     let result = this._queryContext.results[resultIndex];
     let item = this._createElement("div");
+    item.id = "urlbarView-row-" + resultIndex;
     item.className = "urlbarView-row";
     item.setAttribute("resultIndex", resultIndex);
+    item.setAttribute("role", "option");
 
     if (result.type == UrlbarUtils.RESULT_TYPE.SEARCH &&
         !result.payload.isKeywordOffer) {
       item.setAttribute("type", "search");
     } else if (result.type == UrlbarUtils.RESULT_TYPE.REMOTE_TAB) {
       item.setAttribute("type", "remotetab");
     } else if (result.type == UrlbarUtils.RESULT_TYPE.TAB_SWITCH) {
       item.setAttribute("type", "switchtab");
@@ -488,24 +490,28 @@ class UrlbarView {
     }
 
     return item;
   }
 
   _selectItem(item, updateInput = true) {
     if (this._selected) {
       this._selected.toggleAttribute("selected", false);
+      this._selected.toggleAttribute("aria-selected", false);
       this._selected = null;
     }
 
     if (!item) {
+      this._rows.removeAttribute("aria-activedescendant");
       return;
     }
     this._selected = item;
     item.toggleAttribute("selected", true);
+    item.toggleAttribute("aria-selected", true);
+    this._rows.setAttribute("aria-activedescendant", item.id);
 
     if (!updateInput) {
       return;
     }
     let resultIndex = item.getAttribute("resultIndex");
     let result = this._queryContext.results[resultIndex];
     if (result) {
       this.input.setValueFromResult(result);
--- a/browser/components/urlbar/tests/browser/browser.ini
+++ b/browser/components/urlbar/tests/browser/browser.ini
@@ -45,24 +45,23 @@ support-files =
 [browser_moz_action_link.js]
 [browser_new_tab_urlbar_reset.js]
 [browser_pasteAndGo.js]
 subsuite = clipboard
 [browser_populateAfterPushState.js]
 [browser_privateBrowsingWindowChange.js]
 skip-if = debug # Bug 1532034 - Leaks in debug mode.
 [browser_raceWithTabs.js]
-skip-if = os == "linux" # Bug 1382456
 [browser_redirect_error.js]
 support-files = redirect_error.sjs
 [browser_remotetab.js]
 [browser_removeUnsafeProtocolsFromURLBarPaste.js]
 subsuite = clipboard
 [browser_search_favicon.js]
-skip-if = true # Bug 1526222 - Doesn't currently work with QuantumBar
+skip-if = true # Bug 1526222 - Doesn't currently work with QuantumBar
 [browser_searchTelemetry.js]
 support-files =
   searchSuggestionEngine.xml
   searchSuggestionEngine.sjs
 [browser_stop_pending.js]
 support-files =
   slow-page.sjs
 [browser_switchTab_closesUrlbarPopup.js]
--- a/browser/components/urlbar/tests/browser/browser_raceWithTabs.js
+++ b/browser/components/urlbar/tests/browser/browser_raceWithTabs.js
@@ -36,37 +36,40 @@ add_task(async function hitEnterLoadInRi
     keyword: "urlbarkeyword",
   });
 
   info("Opening a tab");
   let oldTabOpenPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer,
                                                         "TabOpen");
   BrowserOpenTab();
   let oldTab = (await oldTabOpenPromise).target;
-  let oldTabLoadedPromise = BrowserTestUtils.browserLoaded(oldTab.linkedBrowser,
-                                                           false, TEST_URL);
-  oldTabLoadedPromise.then(() => info("Old tab loaded"));
+  let oldTabLoadedPromise =
+    BrowserTestUtils.browserLoaded(oldTab.linkedBrowser, false, TEST_URL)
+                    .then(() => info("Old tab loaded"));
 
   info("Filling URL bar, sending <return> and opening a tab");
   let tabOpenPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer,
                                                      "TabOpen");
   gURLBar.value = "urlbarkeyword";
   gURLBar.focus();
   gURLBar.select();
   EventUtils.sendKey("return");
 
   info("Immediately open a second tab");
   BrowserOpenTab();
   let newTab = (await tabOpenPromise).target;
 
-  info("Created new tab; waiting for either tab to load");
-  let newTabLoadedPromise = BrowserTestUtils.browserLoaded(newTab.linkedBrowser,
-                                                           false, TEST_URL);
-  newTabLoadedPromise.then(() => info("New tab loaded"));
-  await Promise.race([newTabLoadedPromise, oldTabLoadedPromise]);
+  info("Created new tab; waiting for tabs to load");
+  let newTabLoadedPromise =
+    BrowserTestUtils.browserLoaded(newTab.linkedBrowser, false, "about:newtab")
+                    .then(() => info("New tab loaded"));
+  // If one of the tabs loads the wrong page, this will timeout, and that
+  // indicates we regressed this bug fix.
+  await Promise.all([newTabLoadedPromise, oldTabLoadedPromise]);
+  // These are not particularly useful, but the test must contain some checks.
   is(newTab.linkedBrowser.currentURI.spec, "about:newtab",
-                                           "New tab still has about:newtab");
+     "New tab loaded about:newtab");
   is(oldTab.linkedBrowser.currentURI.spec, TEST_URL, "Old tab loaded URL");
 
   info("Closing tabs");
   BrowserTestUtils.removeTab(newTab);
   BrowserTestUtils.removeTab(oldTab);
 });
--- a/browser/components/urlbar/tests/legacy/browser.ini
+++ b/browser/components/urlbar/tests/legacy/browser.ini
@@ -68,17 +68,16 @@ skip-if = (os == "linux" || os == "mac")
 [../browser/browser_locationBarExternalLoad.js]
 [../browser/browser_moz_action_link.js]
 [../browser/browser_new_tab_urlbar_reset.js]
 [../browser/browser_pasteAndGo.js]
 subsuite = clipboard
 [../browser/browser_populateAfterPushState.js]
 [../browser/browser_privateBrowsingWindowChange.js]
 [../browser/browser_raceWithTabs.js]
-skip-if = os == "linux" # Bug 1382456
 [../browser/browser_redirect_error.js]
 support-files = ../browser/redirect_error.sjs
 [../browser/browser_remotetab.js]
 [../browser/browser_removeUnsafeProtocolsFromURLBarPaste.js]
 subsuite = clipboard
 [../browser/browser_search_favicon.js]
 [../browser/browser_searchTelemetry.js]
 support-files =
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -438,17 +438,17 @@ var ExtensionsUI = {
     let bundle = window.gNavigatorBundle;
 
     let message = bundle.getFormattedString("addonPostInstall.message1",
                                             ["<>", appName]);
     return new Promise(resolve => {
       // Show or hide private permission ui based on the pref.
       let checkbox = window.document.getElementById("addon-incognito-checkbox");
       checkbox.checked = false;
-      checkbox.hidden = allowPrivateBrowsingByDefault;
+      checkbox.hidden = allowPrivateBrowsingByDefault || addon.type !== "extension";
 
       async function actionResolve() {
         if (checkbox.checked) {
           let perms = {permissions: ["internal:privateBrowsingAllowed"], origins: []};
           await ExtensionPermissions.add(addon.id, perms);
           AMTelemetry.recordActionEvent({
             addon,
             object: "doorhanger",
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -362,17 +362,17 @@ notification[value="translation"] menuli
   --urlbar-popup-action-color: -moz-nativehyperlinktext;
 }
 
 #PopupAutoComplete > richlistbox > richlistitem[originaltype~="datalist-first"] {
   border-top: 1px solid ThreeDShadow;
 }
 
 .ac-title,
-.urlbarView-results {
+#urlbarView-results {
   font-size: 1.05em;
 }
 
 .ac-separator,
 .ac-url,
 .ac-action,
 .ac-tags {
   font-size: 0.9em;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -345,17 +345,17 @@ html|input.urlbar-input {
   margin-top: 1px;
 }
 
 #PopupAutoComplete > richlistbox > richlistitem[originaltype~="datalist-first"] {
   border-top: 1px solid #C7C7C7;
 }
 
 .ac-title,
-.urlbarView-results {
+#urlbarView-results {
   font-size: 14px;
 }
 
 .ac-separator,
 .ac-url,
 .ac-action,
 .ac-tags {
   font-size: 12px;
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -47,16 +47,18 @@
   outline: 1px solid var(--arrowpanel-dimmed-further);
 }
 
 @notKeyfocus@ .downloadsPanelFooterButton:-moz-focusring {
   outline: none;
 }
 
 #downloadsSummary {
+  background: var(--arrowpanel-dimmed);
+  border-top: 1px solid var(--panel-separator-color);
   /* Reserve the same space as the button and separator in download items. */
   padding-inline-end: 59px;
 }
 
 #downloadsHistory {
   padding-inline-start: 14px;
   padding-inline-end: 14px;
 }
--- a/browser/themes/shared/urlbar-autocomplete.inc.css
+++ b/browser/themes/shared/urlbar-autocomplete.inc.css
@@ -33,17 +33,17 @@
   color: var(--autocomplete-popup-color);
   border: 1px solid var(--autocomplete-popup-border-color);
 }
 
 .urlbarView-body-inner {
   box-sizing: border-box;
 }
 
-.urlbarView-results {
+#urlbarView-results {
   box-sizing: border-box;
   padding: @urlbarViewPadding@;
   white-space: nowrap;
 }
 
 .urlbarView-row {
   border-radius: 2px;
   fill: currentColor;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -634,17 +634,17 @@ html|*.urlbar-input:-moz-lwtheme::placeh
 
 %include ../shared/identity-block/identity-block.inc.css
 
 /* autocomplete */
 
 %include ../shared/autocomplete.inc.css
 %include ../shared/urlbar-autocomplete.inc.css
 
-.urlbarView-results {
+#urlbarView-results {
   font-size: 1.15em;
 }
 
 #PopupAutoComplete > richlistbox > richlistitem[originaltype~="datalist-first"] {
   border-top: 1px solid ThreeDShadow;
 }
 
 .ac-title {
--- a/devtools/client/debugger/new/src/client/firefox/commands.js
+++ b/devtools/client/debugger/new/src/client/firefox/commands.js
@@ -68,17 +68,17 @@ function releaseActor(actor: String) {
   if (!actor) {
     return;
   }
 
   return debuggerClient.release(actor);
 }
 
 function sendPacket(packet: Object) {
-  return debuggerClient.request(packet)
+  return debuggerClient.request(packet);
 }
 
 function lookupThreadClient(thread: string) {
   if (thread == threadClient.actor) {
     return threadClient;
   }
   if (!workerClients[thread]) {
     throw new Error(`Unknown thread client: ${thread}`);
--- a/devtools/client/debugger/new/src/components/Editor/Preview/index.js
+++ b/devtools/client/debugger/new/src/components/Editor/Preview/index.js
@@ -46,21 +46,20 @@ function inPopup(e) {
     relatedTarget.classList.contains("debug-expression");
 
   return pop;
 }
 
 function getElementFromPos(pos: DOMRect) {
   // We need to use element*s*AtPoint because the tooltip overlays
   // the token and thus an undesirable element may be returned
-  // $FlowIgnore
-  const elementsAtPoint = [...document.elementsFromPoint(
-    pos.x + pos.width / 2,
-    pos.y + pos.height / 2
-  )];
+  const elementsAtPoint = [
+    // $FlowIgnore
+    ...document.elementsFromPoint(pos.x + pos.width / 2, pos.y + pos.height / 2)
+  ];
 
   return elementsAtPoint.find(el => el.className.startsWith("cm-"));
 }
 
 class Preview extends PureComponent<Props, State> {
   target = null;
   constructor(props) {
     super(props);
--- a/devtools/client/debugger/new/src/utils/breakpoint/breakpointPositions.js
+++ b/devtools/client/debugger/new/src/utils/breakpoint/breakpointPositions.js
@@ -1,23 +1,22 @@
 // @flow
 
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 import { comparePosition } from "../location";
-import type {
-  BreakpointPositions,
-  SourceLocation,
-  Position
-} from "../../types";
+import { getSelectedLocation } from "../source-maps";
+import type { BreakpointPositions, SourceLocation } from "../../types";
 
 export function findPosition(
   positions: ?BreakpointPositions,
-  location: Position | SourceLocation
+  location: SourceLocation
 ) {
   if (!positions) {
     return null;
   }
 
-  return positions.find(pos => comparePosition(pos.location, location));
+  return positions.find(pos =>
+    comparePosition(getSelectedLocation(pos, location), location)
+  );
 }
--- a/devtools/client/debugger/new/src/utils/source-maps.js
+++ b/devtools/client/debugger/new/src/utils/source-maps.js
@@ -1,17 +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/>. */
 
 // @flow
 
 import { isOriginalId } from "devtools-source-map";
 import { getSource } from "../selectors";
-import { isGenerated } from "../utils/source";
 
 import type { SourceLocation, MappedLocation, Source } from "../types";
 import typeof SourceMaps from "../../packages/devtools-source-map/src";
 
 export async function getGeneratedLocation(
   state: Object,
   source: Source,
   location: SourceLocation,
@@ -99,14 +98,20 @@ export async function mapLocation(
 }
 
 export function isOriginalSource(source: ?Source) {
   return source && isOriginalId(source.id);
 }
 
 export function getSelectedLocation(
   mappedLocation: MappedLocation,
-  selectedSource: ?Source
+  context: ?(Source | SourceLocation)
 ): SourceLocation {
-  return selectedSource && isGenerated(selectedSource)
-    ? mappedLocation.generatedLocation
-    : mappedLocation.location;
+  if (!context) {
+    return mappedLocation.location;
+  }
+
+  // $FlowIgnore
+  const sourceId = context.sourceId || context.id;
+  return isOriginalId(sourceId)
+    ? mappedLocation.location
+    : mappedLocation.generatedLocation;
 }
--- a/devtools/client/debugger/new/test/mochitest/browser.ini
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -716,17 +716,17 @@ skip-if = verify
 [browser_dbg-outline-pretty.js]
 [browser_dbg-outline-filter.js]
 [browser_dbg-pause-exceptions.js]
 skip-if = !debug && (os == "win" && os_version == "6.1") # Bug 1456441
 [browser_dbg-pause-on-next.js]
 [browser_dbg-pause-ux.js]
 skip-if = os == "win"
 [browser_dbg-navigation.js]
-skip-if = (verify && debug && (os == 'mac'))
+skip-if = (verify && debug && (os == 'mac')) || (os == 'linux' && debug && bits == 64) || (os == 'mac' && debug) # Bug 1307249
 [browser_dbg-minified.js]
 [browser_dbg-pretty-print.js]
 [browser_dbg-pretty-print-console.js]
 [browser_dbg-pretty-print-paused.js]
 [browser_dbg-preview.js]
 skip-if = os == "win"
 [browser_dbg-preview-module.js]
 skip-if = os == "win"
--- a/devtools/client/netmonitor/src/actions/ui.js
+++ b/devtools/client/netmonitor/src/actions/ui.js
@@ -10,16 +10,17 @@ const {
   RESIZE_NETWORK_DETAILS,
   ENABLE_PERSISTENT_LOGS,
   DISABLE_BROWSER_CACHE,
   OPEN_STATISTICS,
   RESET_COLUMNS,
   SELECT_DETAILS_PANEL_TAB,
   TOGGLE_COLUMN,
   WATERFALL_RESIZE,
+  SET_COLUMNS_WIDTH,
 } = require("../constants");
 
 const { getDisplayedRequests } = require("../selectors/index");
 
 /**
  * Change network details panel.
  *
  * @param {boolean} open - expected network details panel open state
@@ -133,16 +134,28 @@ function selectDetailsPanelTab(id) {
 function toggleColumn(column) {
   return {
     type: TOGGLE_COLUMN,
     column,
   };
 }
 
 /**
+ * Set width of multiple columns
+ *
+ * @param {array} widths - array of pairs {name, width}
+ */
+function setColumnsWidth(widths) {
+  return {
+    type: SET_COLUMNS_WIDTH,
+    widths,
+  };
+}
+
+/**
  * Toggle network details panel.
  */
 function toggleNetworkDetails() {
   return (dispatch, getState) =>
     dispatch(openNetworkDetails(!getState().ui.networkDetailsOpen));
 }
 
 /**
@@ -174,13 +187,14 @@ module.exports = {
   resizeNetworkDetails,
   enablePersistentLogs,
   disableBrowserCache,
   openStatistics,
   resetColumns,
   resizeWaterfall,
   selectDetailsPanelTab,
   toggleColumn,
+  setColumnsWidth,
   toggleNetworkDetails,
   togglePersistentLogs,
   toggleBrowserCache,
   toggleStatistics,
 };
--- a/devtools/client/netmonitor/src/assets/styles/RequestList.css
+++ b/devtools/client/netmonitor/src/assets/styles/RequestList.css
@@ -2,17 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Request list empty panel */
 
 .request-list-empty-notice {
   margin: 0;
   flex: 1;
-  overflow: auto;
+  overflow-x: hidden;
 }
 
 .empty-notice-element {
   padding-top: 12px;
   padding-left: 12px;
   padding-right: 12px;
   font-size: 1.2rem;
 }
@@ -54,26 +54,28 @@
 .requests-list-scroll {
   overflow-x: hidden;
   overflow-y: auto;
 }
 
 .requests-list-table {
   /* Reset default browser style of <table> */
   border-spacing: 0;
+  width: 100%;
+  /* The layout must be fixed for resizing of columns to work.
+  The layout is based on the first row.
+  Set the width of those cells, and the rest of the table follows. */
+  table-layout: fixed;
 }
 
 .requests-list-column {
-  cursor: default;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
   vertical-align: middle;
-  max-width: 50px;
-  min-width: 50px;
 
   /* Reset default browser style of <td> */
   padding: 0;
 }
 
 .requests-list-column > * {
   display: inline-block;
 }
@@ -103,38 +105,38 @@
 .requests-list-header-button {
   background-color: transparent;
   border-image: linear-gradient(transparent 15%,
                                 var(--theme-splitter-color) 15%,
                                 var(--theme-splitter-color) 85%,
                                 transparent 85%) 1 1;
   border-width: 0;
   border-inline-start-width: 1px;
-  padding-inline-start: 16px;
   width: 100%;
   min-height: 23px;
   text-align: center;
   color: inherit;
+  padding: 1px 4px;
 }
 
 .requests-list-header-button::-moz-focus-inner {
   border: 0;
   padding: 0;
 }
 
 .requests-list-header-button:hover {
   background-color: rgba(0, 0, 0, 0.1);
 }
 
 .requests-list-header-button > .button-text {
   display: inline-block;
   text-align: center;
   vertical-align: middle;
   /* Align button text to center */
-  width: calc(100% - 8px);
+  width: 100%;
   overflow: hidden;
   text-overflow: ellipsis;
 }
 
 .requests-list-header-button > .button-icon {
   display: inline-block;
   width: 7px;
   height: 4px;
@@ -157,23 +159,42 @@
   color: var(--theme-selection-color);
 }
 
 .requests-list-header-button[data-sorted],
 .requests-list-column[data-active] + .requests-list-column .requests-list-header-button {
   border-image: linear-gradient(var(--theme-splitter-color), var(--theme-splitter-color)) 1 1;
 }
 
+/* Requests list headers column-resizer */
+
+.requests-list-headers .column-resizer {
+  z-index: 1000;
+  cursor: ew-resize;
+  margin-left: -3px;
+  width: 7px;
+  min-height: 23px;
+  position: absolute;
+  background-color: transparent;
+}
+
+/**
+ * Make sure headers are not processing any mouse
+ * events. This is good for performance during dragging.
+ */
+.requests-list-headers.dragging {
+  pointer-events: none;
+}
+
 /* Requests list column */
 
 /* Status column */
 
 .requests-list-status {
-  min-width: 70px;
-   /* Don't ellipsize status codes */
+  /* Don't ellipsize status codes */
   text-overflow: initial;
 }
 
 .requests-list-status-icon {
   background: #fff;
   height: 10px;
   width: 10px;
   margin-inline-start: 5px;
@@ -221,103 +242,24 @@
 }
 
 .requests-list-status-icon[data-code^="5"] {
   background-color: var(--status-code-color-5xx);
   border-radius: 0;
   transform: rotate(45deg);
 }
 
-/* Method column */
-
-.requests-list-method {
-  min-width: 85px;
-}
-
-/* File column */
-
-.requests-list-file {
-  width: 40%;
-}
-
 .requests-list-file.requests-list-column {
   text-align: start;
 }
 
 .request-list-item.selected {
   filter: brightness(1.3);
 }
 
-/* Protocol column */
-
-.requests-list-protocol {
-  width: 8%;
-}
-
-/* Cookies column */
-
-.requests-list-cookies {
-  width: 6%;
-}
-
-/* Set Cookies column */
-
-.requests-list-set-cookies {
-  width: 8%;
-}
-
-/* Scheme column */
-
-.requests-list-scheme {
-  width: 8%;
-}
-
-/* Start Time column */
-
-.requests-list-start-time {
-  width: 8%;
-}
-
-/* End Time column */
-
-.requests-list-end-time {
-  width: 8%;
-}
-
-/* Response Time column */
-
-.requests-list-response-time {
-  width: 10%;
-}
-
-/* Duration column */
-
-.requests-list-duration-time {
-  width: 8%;
-}
-
-/* Latency column */
-
-.requests-list-latency-time {
-  width: 8%;
-}
-
-/* Response header columns */
-
-.requests-list-response-header {
-  width: 10%;
-}
-
-/* Domain column */
-
-.requests-list-domain {
-  min-width: 100px;
-  width: 30%;
-}
-
 .requests-list-domain.requests-list-column {
   text-align: start;
 }
 
 .requests-security-state-icon {
   display: inline-block;
   width: 16px;
   height: 16px;
@@ -362,28 +304,16 @@
   background-image: url(chrome://devtools/content/netmonitor/src/assets/icons/shield.svg);
   background-repeat: no-repeat;
 }
 
 .selected .tracking-resource {
   filter: brightness(500%);
 }
 
-/* RemoteIP column */
-
-.requests-list-remoteip {
-  width: 9%;
-}
-
-/* Cause column */
-
-.requests-list-cause {
-  min-width: 75px;
-}
-
 .request-list-item .requests-list-cause.requests-list-column {
   padding-left: 5px;
 }
 
 .requests-list-cause-stack {
   display: inline-block;
   background-color: var(--theme-body-color-alt);
   color: var(--theme-body-background);
@@ -391,40 +321,19 @@
   font-weight: bold;
   line-height: 10px;
   border-radius: 3px;
   padding: 0 2px;
   margin: 0;
   margin-inline-end: 3px;
 }
 
-/* Type column */
-
-.requests-list-type {
-  min-width: 65px;
-}
-
-/* Transferred column */
-
-.requests-list-transferred {
-  min-width: 110px;
-}
-
-/* Size column */
-
-.requests-list-size {
-  min-width: 80px;
-}
-
 /* Waterfall column */
 
 .requests-list-waterfall {
-  width: 25vw;
-  max-width: 25vw;
-  min-width: 25vw;
   background-repeat: repeat-y;
   background-position: left center;
   /* Background created on a <canvas> in js. */
   /* @see devtools/client/netmonitor/src/waterfall-background.js */
   background-image: -moz-element(#waterfall-background);
 }
 
 .requests-list-waterfall:dir(rtl) {
@@ -567,20 +476,16 @@
 
 .request-list-item:not(.selected).fromCache > .requests-list-column:not(.requests-list-waterfall) {
   opacity: 0.7;
 }
 
 /* Responsive web design support */
 
 @media (max-width: 700px) {
-  .requests-list-header-button {
-    padding-inline-start: 8px;
-  }
-
   .requests-list-status-code {
     width: auto;
   }
 
   .requests-list-size {
     /* Given a fix max-width to display all columns in RWD mode */
     max-width: 7%;
   }
--- a/devtools/client/netmonitor/src/assets/styles/Toolbar.css
+++ b/devtools/client/netmonitor/src/assets/styles/Toolbar.css
@@ -8,40 +8,42 @@
   display: flex;
   line-height: 23px;
 }
 
 .devtools-toolbar-container {
   height: auto;
   flex-wrap: wrap;
   justify-content: space-between;
+  border: none;
+  padding: 0;
 }
 
 .devtools-toolbar-group {
   display: flex;
   flex: 0 0 auto;
   flex-wrap: nowrap;
   align-items: center;
 }
 
 .devtools-toolbar-two-rows-1,
 .devtools-toolbar-two-rows-2,
 .devtools-toolbar-single-row {
   flex-grow: 1;
   min-height: var(--primary-toolbar-height);
   background-color: var(--theme-body-background);
+  border-bottom: 1px solid var(--theme-splitter-color);
 }
 
 .devtools-toolbar-two-rows-1 {
   width: -moz-available;
 }
 
 .devtools-toolbar-two-rows-2 {
   justify-content: space-between;
-  border-top: 1px solid var(--theme-splitter-color);
 }
 
 .requests-list-filter-buttons {
   display: flex;
   flex-wrap: wrap;
   margin: 0 7px;
 }
 
@@ -97,17 +99,17 @@
 
 .devtools-toolbar-group .devtools-filterinput {
   border: none;
   box-shadow: none;
   background-color: var(--theme-body-background);
 }
 
 .devtools-toolbar-group .devtools-searchbox {
-  height: 29px;
+  height: 28px;
   border: 1px solid transparent;
 }
 
 .devtools-toolbar-group .devtools-searchbox:focus-within {
   border: 1px solid var(--blue-50);
   margin-bottom: 0;
   margin-top: 0;
   box-shadow: none;
--- a/devtools/client/netmonitor/src/components/RequestListContent.js
+++ b/devtools/client/netmonitor/src/components/RequestListContent.js
@@ -118,16 +118,20 @@ class RequestListContent extends Compone
   componentWillUnmount() {
     this.refs.scrollEl.removeEventListener("scroll", this.onScroll, true);
 
     // Uninstall the tooltip event handler
     this.tooltip.stopTogglingOnHover();
     window.removeEventListener("resize", this.onResize);
   }
 
+  /*
+   * Removing onResize() method causes perf regression - too many repaints of the panel.
+   * So it is needed in ComponentDidMount and ComponentDidUpdate. See Bug 1532914.
+   */
   onResize() {
     const parent = this.refs.scrollEl.parentNode;
     this.refs.scrollEl.style.width = parent.offsetWidth + "px";
     this.refs.scrollEl.style.height = parent.offsetHeight + "px";
   }
 
   isScrolledToBottom() {
     const { scrollEl, rowGroupEl } = this.refs;
--- a/devtools/client/netmonitor/src/components/RequestListEmptyNotice.js
+++ b/devtools/client/netmonitor/src/components/RequestListEmptyNotice.js
@@ -10,17 +10,16 @@ const PropTypes = require("devtools/clie
 const { connect } = require("devtools/client/shared/redux/visibility-handler-connect");
 const Actions = require("../actions/index");
 const { ACTIVITY_TYPE } = require("../constants");
 const { L10N } = require("../utils/l10n");
 const { getPerformanceAnalysisURL } = require("../utils/mdn-utils");
 
 // Components
 const MDNLink = createFactory(require("devtools/client/shared/components/MdnLink"));
-const RequestListHeader = createFactory(require("./RequestListHeader"));
 
 const { button, div, span } = dom;
 
 const RELOAD_NOTICE_1 = L10N.getStr("netmonitor.reloadNotice1");
 const RELOAD_NOTICE_2 = L10N.getStr("netmonitor.reloadNotice2");
 const RELOAD_NOTICE_3 = L10N.getStr("netmonitor.reloadNotice3");
 const PERFORMANCE_NOTICE_1 = L10N.getStr("netmonitor.perfNotice1");
 const PERFORMANCE_NOTICE_2 = L10N.getStr("netmonitor.perfNotice2");
@@ -40,17 +39,16 @@ class RequestListEmptyNotice extends Com
     };
   }
 
   render() {
     return div(
       {
         className: "request-list-empty-notice",
       },
-      RequestListHeader(),
       div({ className: "notice-reload-message empty-notice-element" },
         span(null, RELOAD_NOTICE_1),
         button(
           {
             className: "devtools-button requests-list-reload-notice-button",
             "data-standalone": true,
             onClick: this.props.onReloadClick,
           },
--- a/devtools/client/netmonitor/src/components/RequestListHeader.js
+++ b/devtools/client/netmonitor/src/components/RequestListHeader.js
@@ -1,51 +1,69 @@
 /* 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 Services = require("Services");
+const { createRef, Component, createFactory } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/redux/visibility-handler-connect");
-const { getTheme, addThemeObserver, removeThemeObserver } =
-  require("devtools/client/shared/theme");
+const {
+  getTheme,
+  addThemeObserver,
+  removeThemeObserver,
+} = require("devtools/client/shared/theme");
 const Actions = require("../actions/index");
-const { HEADERS, REQUESTS_WATERFALL } = require("../constants");
+const {
+  HEADERS,
+  REQUESTS_WATERFALL,
+  MIN_COLUMN_WIDTH,
+  DEFAULT_COLUMN_WIDTH,
+} = require("../constants");
 const { getWaterfallScale } = require("../selectors/index");
 const { getFormattedTime } = require("../utils/format-utils");
 const { L10N } = require("../utils/l10n");
 const RequestListHeaderContextMenu = require("../widgets/RequestListHeaderContextMenu");
 const WaterfallBackground = require("../widgets/WaterfallBackground");
+const Draggable = createFactory(require("devtools/client/shared/components/splitter/Draggable"));
 
 const { div, button } = dom;
 
+// Support for columns resizing is currently hidden behind this pref.
+const RESIZE_COLUMNS =
+  Services.prefs.getBoolPref("devtools.netmonitor.features.resizeColumns");
+
 /**
  * Render the request list header with sorting arrows for columns.
  * Displays tick marks in the waterfall column header.
  * Also draws the waterfall background canvas and updates it when needed.
  */
 class RequestListHeader extends Component {
   static get propTypes() {
     return {
       columns: PropTypes.object.isRequired,
       resetColumns: PropTypes.func.isRequired,
       resizeWaterfall: PropTypes.func.isRequired,
       scale: PropTypes.number,
       sort: PropTypes.object,
       sortBy: PropTypes.func.isRequired,
       toggleColumn: PropTypes.func.isRequired,
       waterfallWidth: PropTypes.number,
+      columnsData: PropTypes.object.isRequired,
+      setColumnsWidth: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
+    this.requestListHeader = createRef();
+
     this.onContextMenu = this.onContextMenu.bind(this);
     this.drawBackground = this.drawBackground.bind(this);
     this.resizeWaterfall = this.resizeWaterfall.bind(this);
     this.waterfallDivisionLabels = this.waterfallDivisionLabels.bind(this);
     this.waterfallLabel = this.waterfallLabel.bind(this);
   }
 
   componentWillMount() {
@@ -55,23 +73,33 @@ class RequestListHeader extends Componen
       toggleColumn,
     });
   }
 
   componentDidMount() {
     // Create the object that takes care of drawing the waterfall canvas background
     this.background = new WaterfallBackground(document);
     this.drawBackground();
+    // When visible columns add up to less or more than 100% => update widths in prefs.
+    if (this.shouldUpdateWidths()) {
+      this.updateColumnsWidth();
+    }
     this.resizeWaterfall();
     window.addEventListener("resize", this.resizeWaterfall);
     addThemeObserver(this.drawBackground);
   }
 
   componentDidUpdate() {
     this.drawBackground();
+    // check if the widths in prefs need to be updated
+    // e.g. after hide/show column
+    if (this.shouldUpdateWidths()) {
+      this.updateColumnsWidth();
+      this.resizeWaterfall();
+    }
   }
 
   componentWillUnmount() {
     this.background.destroy();
     this.background = null;
     window.removeEventListener("resize", this.resizeWaterfall);
     removeThemeObserver(this.drawBackground);
   }
@@ -158,78 +186,402 @@ class RequestListHeader extends Componen
     if (waterfallWidth !== null && scale !== null) {
       label = this.waterfallDivisionLabels(waterfallWidth, scale);
       className += " requests-list-waterfall-visible";
     }
 
     return div({ className }, label);
   }
 
+  // Dragging Events
+
+  /**
+   * Set 'resizing' cursor on entire container dragging.
+   * This avoids cursor-flickering when the mouse leaves
+   * the column-resizer area (happens frequently).
+   */
+  onStartMove() {
+    // Set cursor to dragging
+    const container = document.querySelector(".request-list-container");
+    container.style.cursor = "ew-resize";
+    // Class .dragging is used to disable pointer events while dragging - see css.
+    this.requestListHeader.classList.add("dragging");
+  }
+
+  /**
+   * A handler that calculates the new width of the columns
+   * based on mouse position and adjusts the width.
+   */
+  onMove(name, x) {
+    const parentEl = document.querySelector(".requests-list-headers");
+    const parentWidth = parentEl.getBoundingClientRect().width;
+
+    // Get the current column handle and save its old width
+    // before changing so we can compute the adjustment in width
+    const headerRef = this.refs[`${name}Header`];
+    const headerRefRect = headerRef.getBoundingClientRect();
+    const oldWidth = headerRefRect.width;
+
+    // Get the column handle that will compensate the width change.
+    const compensateHeaderName = this.getCompensateHeader();
+
+    if (name === compensateHeaderName) {
+      // this is the case where we are resizing waterfall
+      this.moveWaterfall(x, parentWidth);
+      return;
+    }
+
+    const compensateHeaderRef = this.refs[`${compensateHeaderName}Header`];
+    const compensateHeaderRefRect = compensateHeaderRef.getBoundingClientRect();
+    const oldCompensateWidth = compensateHeaderRefRect.width;
+    const sumOfBothColumns = oldWidth + oldCompensateWidth;
+
+    // Get minimal widths for both changed columns (in px).
+    const minWidth = this.getMinWidth(name);
+    const minCompensateWidth = this.getMinWidth(compensateHeaderName);
+
+    // Calculate new width (according to the mouse x-position) and set to style.
+    // Do not allow to set it below minWidth.
+    const newWidth = Math.max(x - headerRefRect.left, minWidth);
+    headerRef.style.width = `${this.px2percent(newWidth, parentWidth)}%`;
+    const adjustment = oldWidth - newWidth;
+
+    // Calculate new compensate width as the original width + adjustment.
+    // Do not allow to set it below minCompensateWidth.
+    const newCompensateWidth =
+      Math.max(adjustment + oldCompensateWidth, minCompensateWidth);
+    compensateHeaderRef.style.width =
+      `${this.px2percent(newCompensateWidth, parentWidth)}%`;
+
+    // Do not allow to reset size of column when compensate column is at minWidth.
+    if (newCompensateWidth === minCompensateWidth) {
+      headerRef.style.width =
+        `${this.px2percent((sumOfBothColumns - newCompensateWidth), parentWidth)}%`;
+    }
+  }
+
+  /**
+   * After resizing - we get the width for each 'column'
+   * and convert it into % and store it in user prefs.
+   * Also resets the 'resizing' cursor back to initial.
+   */
+  onStopMove() {
+    this.updateColumnsWidth();
+    // If waterfall is visible and width has changed, call resizeWaterfall.
+    const waterfallRef = this.refs.waterfallHeader;
+    if (waterfallRef) {
+      const { waterfallWidth } = this.props;
+      const realWaterfallWidth = waterfallRef.getBoundingClientRect().width;
+      if (Math.round(waterfallWidth) !== Math.round(realWaterfallWidth)) {
+        this.resizeWaterfall();
+      }
+    }
+
+    // Restore cursor back to default.
+    const container = document.querySelector(".request-list-container");
+    container.style.cursor = "initial";
+    this.requestListHeader.classList.remove("dragging");
+  }
+
+  /**
+   * Helper method to get the name of the column that will compensate
+   * the width change. It should be the last column before waterfall,
+   * (if waterfall visible) otherwise it is simply the last visible column.
+   */
+  getCompensateHeader() {
+    const visibleColumns = this.getVisibleColumns();
+    const lastColumn = visibleColumns[visibleColumns.length - 1].name;
+    const delta = (lastColumn === "waterfall") ? 2 : 1;
+    return visibleColumns[visibleColumns.length - delta].name;
+  }
+
+  /**
+   * Called from onMove() when resizing waterfall column
+   * because waterfall is a special case, where ALL other
+   * columns are made smaller when waterfall is bigger and vice versa.
+   */
+  moveWaterfall(x, parentWidth) {
+    const visibleColumns = this.getVisibleColumns();
+    const minWaterfall = this.getMinWidth("waterfall");
+    const waterfallRef = this.refs.waterfallHeader;
+
+    // Compute and set style.width for waterfall.
+    const waterfallRefRect = waterfallRef.getBoundingClientRect();
+    const oldWidth = waterfallRefRect.width;
+    const adjustment = waterfallRefRect.left - x;
+    if (this.allColumnsAtMinWidth() && adjustment > 0) {
+      // When we want to make waterfall wider but all
+      // other columns are already at minWidth => return.
+      return;
+    }
+
+    const newWidth = Math.max(oldWidth + adjustment, minWaterfall);
+
+    // Now distribute evenly the change in width to all other columns except waterfall.
+    const changeInWidth = oldWidth - newWidth;
+    const widths = this.autoSizeWidths(changeInWidth, visibleColumns);
+
+    // Set the new computed width for waterfall into array widths.
+    widths[widths.length - 1] = newWidth;
+
+    // Update style for all columns from array widths.
+    let i = 0;
+    visibleColumns.forEach(col => {
+      const name = col.name;
+      const headerRef = this.refs[`${name}Header`];
+      headerRef.style.width = `${this.px2percent(widths[i], parentWidth)}%`;
+      i++;
+    });
+  }
+
+  /**
+   * Helper method that checks if all columns have reached their minWidth.
+   * This can happen when making waterfall column wider.
+   */
+  allColumnsAtMinWidth() {
+    const visibleColumns = this.getVisibleColumns();
+    // Do not check width for waterfall because
+    // when all are getting smaller, waterfall is getting bigger.
+    for (let i = 0; i < visibleColumns.length - 1; i++) {
+      const name = visibleColumns[i].name;
+      const headerRef = this.refs[`${name}Header`];
+      const minColWidth = this.getMinWidth(name);
+      if (headerRef.getBoundingClientRect().width > minColWidth) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Method takes the total change in width for waterfall column
+   * and distributes it among all other columns. Returns an array
+   * where all visible columns have newly computed width in pixels.
+   */
+  autoSizeWidths(changeInWidth, visibleColumns) {
+    const widths = visibleColumns.map(col => {
+      const headerRef = this.refs[`${col.name}Header`];
+      const colWidth = headerRef.getBoundingClientRect().width;
+      return colWidth;
+    });
+
+    // Divide changeInWidth among all columns but waterfall (that's why -1).
+    const changeInWidthPerColumn = changeInWidth / (widths.length - 1);
+
+    while (changeInWidth) {
+      const lastChangeInWidth = changeInWidth;
+      // In the loop adjust all columns except last one - waterfall
+      for (let i = 0; i < widths.length - 1; i++) {
+        const name = visibleColumns[i].name;
+        const minColWidth = this.getMinWidth(name);
+        const newColWidth = Math.max(widths[i] + changeInWidthPerColumn, minColWidth);
+
+        widths[i] = newColWidth;
+        if (changeInWidth > 0) {
+          changeInWidth -= (newColWidth - widths[i]);
+        } else {
+          changeInWidth += (newColWidth - widths[i]);
+        }
+        if (!changeInWidth) {
+          break;
+        }
+      }
+      if (lastChangeInWidth == changeInWidth) {
+        break;
+      }
+    }
+    return widths;
+  }
+
+  /**
+   * Method returns 'true' - if the column widths need to be updated
+   * when the total % is less or more than 100%.
+   * It returns 'false' if they add up to 100% => no need to update.
+   */
+  shouldUpdateWidths() {
+    const visibleColumns = this.getVisibleColumns();
+    let totalPercent = 0;
+
+    visibleColumns.forEach(col => {
+      const name = col.name;
+      const headerRef = this.refs[`${name}Header`];
+      // Get column width from style.
+      let widthFromStyle = 0;
+      // In case the column is in visibleColumns but has display:none
+      // we don't want to count its style.width into totalPercent.
+      if (headerRef.getBoundingClientRect().width > 0) {
+        widthFromStyle = headerRef.style.width.slice(0, -1);
+      }
+      totalPercent += +widthFromStyle; // + converts it to a number
+    });
+
+    // Do not update if total percent is from 99-101% or when it is 0
+    // - it means that no columns are displayed (e.g. other panel is currently selected).
+    return Math.round(totalPercent) !== 100 && totalPercent !== 0;
+  }
+
+  /**
+   * Method reads real width of each column header
+   * and updates the style.width for that header.
+   * It returns updated columnsData.
+   */
+  updateColumnsWidth() {
+    const visibleColumns = this.getVisibleColumns();
+    const parentEl = document.querySelector(".requests-list-headers");
+    const parentElRect = parentEl.getBoundingClientRect();
+    const parentWidth = parentElRect.width;
+    const newWidths = [];
+    visibleColumns.forEach(col => {
+      const name = col.name;
+      const headerRef = this.refs[`${name}Header`];
+      const headerWidth = headerRef.getBoundingClientRect().width;
+
+      // Get actual column width, change into %, update style
+      const width = this.px2percent(headerWidth, parentWidth);
+
+      if (width > 0) {
+        // This prevents saving width 0 for waterfall when it is not showing for
+        // @media (max-width: 700px)
+        newWidths.push({name, width});
+      }
+    });
+    this.props.setColumnsWidth(newWidths);
+  }
+
+  /**
+   * Helper method to convert pixels into percent based on parent container width
+   */
+  px2percent(pxWidth, parentWidth) {
+    const percent = Math.round((100 * pxWidth / parentWidth) * 100) / 100;
+    return percent;
+  }
+
+  /**
+   * Helper method to get visibleColumns;
+   */
+  getVisibleColumns() {
+    const { columns } = this.props;
+    return HEADERS.filter((header) => columns[header.name]);
+  }
+
+  /**
+   * Helper method to get minWidth from columnsData;
+   */
+  getMinWidth(colName) {
+    const columnsData = this.props.columnsData;
+    if (columnsData.has(colName)) {
+      return columnsData.get(colName).minWidth;
+    }
+    return MIN_COLUMN_WIDTH;
+  }
+
+  /**
+   * Render one column header from the table headers.
+   */
+  renderColumn(header) {
+    const columnsData = this.props.columnsData;
+    const visibleColumns = this.getVisibleColumns();
+    const lastVisibleColumn = visibleColumns[visibleColumns.length - 1].name;
+    const name = header.name;
+    const boxName = header.boxName || name;
+    const label = header.noLocalization
+      ? name : L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
+
+    const { scale, sort, sortBy, waterfallWidth } = this.props;
+    let sorted, sortedTitle;
+    const active = sort.type == name ? true : undefined;
+
+    if (active) {
+      sorted = sort.ascending ? "ascending" : "descending";
+      sortedTitle = L10N.getStr(sort.ascending
+        ? "networkMenu.sortedAsc"
+        : "networkMenu.sortedDesc");
+    }
+
+    // If the pref for this column width exists, set the style
+    // otherwise use default.
+    let colWidth = DEFAULT_COLUMN_WIDTH;
+    if (columnsData.has(name)) {
+      const oneColumnEl = columnsData.get(name);
+      colWidth = oneColumnEl.width;
+    }
+    const columnStyle = {
+      width: colWidth + "%",
+    };
+
+    // Support for columns resizing is currently hidden behind a pref.
+    const draggable = RESIZE_COLUMNS ? Draggable({
+      className: "column-resizer ",
+      onStart: () => this.onStartMove(),
+      onStop: () => this.onStopMove(),
+      onMove: (x) => this.onMove(name, x),
+    }) : undefined;
+
+    return (
+      dom.td({
+        id: `requests-list-${boxName}-header-box`,
+        className: `requests-list-column requests-list-${boxName}`,
+        style: columnStyle,
+        key: name,
+        ref: `${name}Header`,
+        // Used to style the next column.
+        "data-active": active,
+      },
+        button({
+          id: `requests-list-${name}-button`,
+          className: `requests-list-header-button`,
+          "data-sorted": sorted,
+          title: sortedTitle ? `${label} (${sortedTitle})` : label,
+          onClick: () => sortBy(name),
+        },
+          name === "waterfall"
+            ? this.waterfallLabel(waterfallWidth, scale, label)
+            : div({ className: "button-text" }, label),
+          div({ className: "button-icon" })
+        ),
+        (name !== lastVisibleColumn) && draggable
+      )
+    );
+  }
+
+  /**
+   * Render all columns in the table header
+   */
+  renderColumns() {
+    const visibleColumns = this.getVisibleColumns();
+    return visibleColumns.map(header => this.renderColumn(header));
+  }
+
   render() {
-    const { columns, scale, sort, sortBy, waterfallWidth } = this.props;
-
     return (
       dom.thead({ className: "devtools-toolbar requests-list-headers-group" },
         dom.tr({
           className: "requests-list-headers",
           onContextMenu: this.onContextMenu,
+          ref: node => {
+            this.requestListHeader = node;
+          },
         },
-          HEADERS.filter((header) => columns[header.name]).map((header) => {
-            const name = header.name;
-            const boxName = header.boxName || name;
-            const label = header.noLocalization
-              ? name : L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
-            let sorted, sortedTitle;
-            const active = sort.type == name ? true : undefined;
-
-            if (active) {
-              sorted = sort.ascending ? "ascending" : "descending";
-              sortedTitle = L10N.getStr(sort.ascending
-                ? "networkMenu.sortedAsc"
-                : "networkMenu.sortedDesc");
-            }
-
-            return (
-              dom.td({
-                id: `requests-list-${boxName}-header-box`,
-                className: `requests-list-column requests-list-${boxName}`,
-                key: name,
-                ref: `${name}Header`,
-                // Used to style the next column.
-                "data-active": active,
-              },
-                button({
-                  id: `requests-list-${name}-button`,
-                  className: `requests-list-header-button`,
-                  "data-sorted": sorted,
-                  title: sortedTitle ? `${label} (${sortedTitle})` : label,
-                  onClick: () => sortBy(name),
-                },
-                  name === "waterfall"
-                    ? this.waterfallLabel(waterfallWidth, scale, label)
-                    : div({ className: "button-text" }, label),
-                  div({ className: "button-icon" })
-                )
-              )
-            );
-          })
+          this.renderColumns(),
         )
       )
     );
   }
 }
 
 module.exports = connect(
   (state) => ({
     columns: state.ui.columns,
+    columnsData: state.ui.columnsData,
     firstRequestStartedMillis: state.requests.firstStartedMillis,
     scale: getWaterfallScale(state),
     sort: state.sort,
     timingMarkers: state.timingMarkers,
     waterfallWidth: state.ui.waterfallWidth,
   }),
   (dispatch) => ({
     resetColumns: () => dispatch(Actions.resetColumns()),
     resizeWaterfall: (width) => dispatch(Actions.resizeWaterfall(width)),
     sortBy: (type) => dispatch(Actions.sortBy(type)),
     toggleColumn: (column) => dispatch(Actions.toggleColumn(column)),
+    setColumnsWidth: (widths) => dispatch(Actions.setColumnsWidth(widths)),
   })
 )(RequestListHeader);
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -25,16 +25,17 @@ const actionTypes = {
   SEND_CUSTOM_REQUEST: "SEND_CUSTOM_REQUEST",
   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",
 };
 
 // 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.
@@ -327,24 +328,31 @@ const TIMING_KEYS = [
   "dns",
   "connect",
   "ssl",
   "send",
   "wait",
   "receive",
 ];
 
+// Minimal width of Network Monitor column is 30px, for Waterfall 150px
+// Default width of columns (which are not defined in DEFAULT_COLUMNS_DATA) is 8%
+const MIN_COLUMN_WIDTH = 30; // in px
+const DEFAULT_COLUMN_WIDTH = 8; // in %
+
 const general = {
   ACTIVITY_TYPE,
   EVENTS,
   FILTER_SEARCH_DELAY: 200,
   UPDATE_PROPS,
   HEADERS,
   RESPONSE_HEADERS,
   FILTER_FLAGS,
   FILTER_TAGS,
   REQUESTS_WATERFALL,
   PANELS,
   TIMING_KEYS,
+  MIN_COLUMN_WIDTH,
+  DEFAULT_COLUMN_WIDTH,
 };
 
 // flatten constants
 module.exports = Object.assign({}, general, actionTypes);
--- a/devtools/client/netmonitor/src/create-store.js
+++ b/devtools/client/netmonitor/src/create-store.js
@@ -2,46 +2,52 @@
  * 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 { applyMiddleware, createStore } = require("devtools/client/shared/vendor/redux");
 
+const {
+  MIN_COLUMN_WIDTH,
+  DEFAULT_COLUMN_WIDTH,
+} = require("./constants");
+
 // Middleware
 const batching = require("./middleware/batching");
 const prefs = require("./middleware/prefs");
 const thunk = require("./middleware/thunk");
 const recording = require("./middleware/recording");
 const throttling = require("./middleware/throttling");
 const eventTelemetry = require("./middleware/event-telemetry");
 
 // 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 } = require("./reducers/ui");
+const { UI, Columns, ColumnsData } = require("./reducers/ui");
 
 /**
  * Configure state and middleware for the Network monitor tool.
  */
 function configureStore(connector, telemetry) {
   // Prepare initial state.
   const initialState = {
     filters: new Filters({
       requestFilterTypes: getFilterState(),
     }),
     requests: new Requests(),
     sort: new Sort(),
     timingMarkers: new TimingMarkers(),
     ui: UI({
       columns: getColumnState(),
+      columnsData: getColumnsData(),
     }),
   };
 
   // Prepare middleware.
   const middleware = applyMiddleware(
     thunk,
     prefs,
     batching,
@@ -66,16 +72,37 @@ function getColumnState() {
   for (const col in columns) {
     state[col] = visibleColumns.includes(col);
   }
 
   return state;
 }
 
 /**
+ * Get columns data (width, min-width)
+ */
+function getColumnsData() {
+  const columnsData = getPref("devtools.netmonitor.columnsData");
+  if (!columnsData.length) {
+    return ColumnsData();
+  }
+
+  const newMap = new Map();
+  columnsData.forEach(col => {
+    if (col.name) {
+      col.minWidth = col.minWidth ? col.minWidth : MIN_COLUMN_WIDTH;
+      col.width = col.width ? col.width : DEFAULT_COLUMN_WIDTH;
+      newMap.set(col.name, col);
+    }
+  });
+
+  return newMap;
+}
+
+/**
  * Get filter state from preferences.
  */
 function getFilterState() {
   const activeFilters = {};
   const filters = getPref("devtools.netmonitor.filters");
   filters.forEach((filter) => {
     activeFilters[filter] = true;
   });
--- a/devtools/client/netmonitor/src/middleware/prefs.js
+++ b/devtools/client/netmonitor/src/middleware/prefs.js
@@ -7,16 +7,17 @@
 const Services = require("Services");
 const {
   ENABLE_REQUEST_FILTER_TYPE_ONLY,
   RESET_COLUMNS,
   TOGGLE_COLUMN,
   TOGGLE_REQUEST_FILTER_TYPE,
   ENABLE_PERSISTENT_LOGS,
   DISABLE_BROWSER_CACHE,
+  SET_COLUMNS_WIDTH,
 } = require("../constants");
 
 /**
   * Update the relevant prefs when:
   *   - a column has been toggled
   *   - a filter type has been set
   */
 function prefsMiddleware(store) {
@@ -35,25 +36,50 @@ function prefsMiddleware(store) {
         Services.prefs.setBoolPref(
           "devtools.netmonitor.persistlog", store.getState().ui.persistentLogsEnabled);
         break;
       case DISABLE_BROWSER_CACHE:
         Services.prefs.setBoolPref(
           "devtools.cache.disabled", store.getState().ui.browserCacheDisabled);
         break;
       case TOGGLE_COLUMN:
+        persistVisibleColumns(store.getState());
+        break;
       case RESET_COLUMNS:
-        const visibleColumns = [];
-        const columns = store.getState().ui.columns;
-        for (const column in columns) {
-          if (columns[column]) {
-            visibleColumns.push(column);
-          }
-        }
-        Services.prefs.setCharPref(
-          "devtools.netmonitor.visibleColumns", JSON.stringify(visibleColumns));
+        persistVisibleColumns(store.getState());
+        persistColumnsData(store.getState());
+        break;
+      case SET_COLUMNS_WIDTH:
+        persistColumnsData(store.getState());
         break;
     }
     return res;
   };
 }
 
+/**
+ * Store list of visible columns into preferences.
+ */
+function persistVisibleColumns(state) {
+  const visibleColumns = [];
+  const columns = state.ui.columns;
+  for (const column in columns) {
+    if (columns[column]) {
+      visibleColumns.push(column);
+    }
+  }
+
+  Services.prefs.setCharPref(
+    "devtools.netmonitor.visibleColumns",
+    JSON.stringify(visibleColumns));
+}
+
+/**
+ * Store columns data (width, min-width, etc.) into preferences.
+ */
+function persistColumnsData(state) {
+  const columnsData = [...state.ui.columnsData.values()];
+  Services.prefs.setCharPref(
+    "devtools.netmonitor.columnsData",
+    JSON.stringify(columnsData));
+}
+
 module.exports = prefsMiddleware;
--- a/devtools/client/netmonitor/src/reducers/ui.js
+++ b/devtools/client/netmonitor/src/reducers/ui.js
@@ -16,16 +16,18 @@ const {
   RESET_COLUMNS,
   RESPONSE_HEADERS,
   SELECT_DETAILS_PANEL_TAB,
   SEND_CUSTOM_REQUEST,
   SELECT_REQUEST,
   TOGGLE_COLUMN,
   WATERFALL_RESIZE,
   PANELS,
+  MIN_COLUMN_WIDTH,
+  SET_COLUMNS_WIDTH,
 } = require("../constants");
 
 const cols = {
   status: true,
   method: true,
   domain: true,
   file: true,
   protocol: false,
@@ -39,42 +41,52 @@ const cols = {
   contentSize: true,
   startTime: false,
   endTime: false,
   responseTime: false,
   duration: false,
   latency: false,
   waterfall: true,
 };
+
 function Columns() {
   return Object.assign(
     cols,
     RESPONSE_HEADERS.reduce((acc, header) => Object.assign(acc, { [header]: false }), {})
   );
 }
 
+function ColumnsData() {
+  const defaultColumnsData = JSON.parse(
+    Services.prefs.getDefaultBranch(null).getCharPref("devtools.netmonitor.columnsData")
+  );
+  return new Map(defaultColumnsData.map(i => [i.name, i]));
+}
+
 function UI(initialState = {}) {
   return {
     columns: Columns(),
+    columnsData: ColumnsData(),
     detailsPanelSelectedTab: PANELS.HEADERS,
     networkDetailsOpen: false,
     networkDetailsWidth: null,
     networkDetailsHeight: null,
     persistentLogsEnabled: Services.prefs.getBoolPref("devtools.netmonitor.persistlog"),
     browserCacheDisabled: Services.prefs.getBoolPref("devtools.cache.disabled"),
     statisticsOpen: false,
     waterfallWidth: null,
     ...initialState,
   };
 }
 
 function resetColumns(state) {
   return {
     ...state,
     columns: Columns(),
+    columnsData: ColumnsData(),
   };
 }
 
 function resizeWaterfall(state, action) {
   return {
     ...state,
     waterfallWidth: action.width,
   };
@@ -134,16 +146,40 @@ function toggleColumn(state, action) {
     ...state,
     columns: {
       ...state.columns,
       [column]: !state.columns[column],
     },
   };
 }
 
+function setColumnsWidth(state, action) {
+  const { widths } = action;
+  const columnsData = new Map(state.columnsData);
+
+  widths.forEach(col => {
+    let data = columnsData.get(col.name);
+    if (!data) {
+      data = {
+        name: col.name,
+        minWidth: MIN_COLUMN_WIDTH,
+      };
+    }
+    columnsData.set(col.name, {
+      ...data,
+      width: col.width,
+    });
+  });
+
+  return {
+    ...state,
+    columnsData: columnsData,
+  };
+}
+
 function ui(state = UI(), action) {
   switch (action.type) {
     case CLEAR_REQUESTS:
       return openNetworkDetails(state, { open: false });
     case OPEN_NETWORK_DETAILS:
       return openNetworkDetails(state, action);
     case RESIZE_NETWORK_DETAILS:
       return resizeNetworkDetails(state, action);
@@ -162,18 +198,21 @@ function ui(state = UI(), action) {
     case SELECT_DETAILS_PANEL_TAB:
       return setDetailsPanelTab(state, action);
     case SELECT_REQUEST:
       return openNetworkDetails(state, { open: true });
     case TOGGLE_COLUMN:
       return toggleColumn(state, action);
     case WATERFALL_RESIZE:
       return resizeWaterfall(state, action);
+    case SET_COLUMNS_WIDTH:
+      return setColumnsWidth(state, action);
     default:
       return state;
   }
 }
 
 module.exports = {
   Columns,
+  ColumnsData,
   UI,
   ui,
 };
--- a/devtools/client/netmonitor/src/utils/prefs.js
+++ b/devtools/client/netmonitor/src/utils/prefs.js
@@ -8,10 +8,11 @@ const { PrefsHelper } = require("devtool
 
 /**
  * Shortcuts for accessing various network monitor preferences.
  */
 exports.Prefs = new PrefsHelper("devtools.netmonitor", {
   networkDetailsWidth: ["Int", "panes-network-details-width"],
   networkDetailsHeight: ["Int", "panes-network-details-height"],
   visibleColumns: ["Json", "visibleColumns"],
+  columnsData: ["Json", "columnsData"],
   filters: ["Json", "filters"],
 });
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -130,16 +130,17 @@ skip-if = (os == 'mac') || (os == 'win' 
 [browser_net_filter-04.js]
 [browser_net_filter-autocomplete.js]
 [browser_net_filter-flags.js]
 [browser_net_footer-summary.js]
 [browser_net_header-ref-policy.js]
 [browser_net_headers-alignment.js]
 [browser_net_headers_filter.js]
 [browser_net_headers_sorted.js]
+[browser_net_headers-resize.js]
 [browser_net_image-tooltip.js]
 [browser_net_json-b64.js]
 [browser_net_json-empty.js]
 [browser_net_json-null.js]
 [browser_net_json-long.js]
 [browser_net_json-malformed.js]
 [browser_net_json-nogrip.js]
 [browser_net_json_custom_mime.js]
--- a/devtools/client/netmonitor/test/browser_net_columns_last_column.js
+++ b/devtools/client/netmonitor/test/browser_net_columns_last_column.js
@@ -1,22 +1,29 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
- * Tests that last visible column can't be hidden
+ * Tests that last visible column can't be hidden. Note that the column
+ * header is visible only if there are requests in the list.
  */
 
 add_task(async function() {
-  const { monitor } = await initNetMonitor(SIMPLE_URL);
+  const { monitor, tab } = await initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  const { document, store, parent } = monitor.panelWin;
+  const { document, store, parent, windowRequire } = monitor.panelWin;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  store.dispatch(Actions.batchEnable(false));
+
+  const wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.reload();
+  await wait;
 
   const initialColumns = store.getState().ui.columns;
   for (const column in initialColumns) {
     const shown = initialColumns[column];
 
     const columns = store.getState().ui.columns;
     const visibleColumns = [];
     for (const c in columns) {
--- a/devtools/client/netmonitor/test/browser_net_columns_pref.js
+++ b/devtools/client/netmonitor/test/browser_net_columns_pref.js
@@ -1,25 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
- * Tests if visible columns are properly saved
+ * Tests if visible columns are properly saved. Note that the column
+ * header is visible only if there are requests in the list.
  */
 
 add_task(async function() {
   Services.prefs.setCharPref("devtools.netmonitor.visibleColumns",
     '["status", "contentSize", "waterfall"]');
 
-  const { monitor } = await initNetMonitor(SIMPLE_URL);
+  const { monitor, tab } = await initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  const { document } = monitor.panelWin;
+  const { document, store, windowRequire } = monitor.panelWin;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  store.dispatch(Actions.batchEnable(false));
+
+  const wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.reload();
+  await wait;
 
   ok(document.querySelector("#requests-list-status-button"),
      "Status column should be shown");
   ok(document.querySelector("#requests-list-contentSize-button"),
      "Content size column should be shown");
 
   await hideColumn(monitor, "status");
   await hideColumn(monitor, "contentSize");
--- a/devtools/client/netmonitor/test/browser_net_columns_reset.js
+++ b/devtools/client/netmonitor/test/browser_net_columns_reset.js
@@ -1,24 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
- * Tests reset column menu item
+ * Tests reset column menu item. Note that the column
+ * header is visible only if there are requests in the list.
  */
 add_task(async function() {
-  const { monitor } = await initNetMonitor(SIMPLE_URL);
+  const { monitor, tab } = await initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  const { document, parent, windowRequire } = monitor.panelWin;
+  const { document, store, parent, windowRequire } = monitor.panelWin;
   const { Prefs } = windowRequire("devtools/client/netmonitor/src/utils/prefs");
 
   const prefBefore = Prefs.visibleColumns;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  store.dispatch(Actions.batchEnable(false));
+
+  const wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.reload();
+  await wait;
 
   await hideColumn(monitor, "status");
   await hideColumn(monitor, "waterfall");
 
   const onRequestsFinished = waitForRequestsFinished(monitor);
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelector("#requests-list-contentSize-button"));
 
--- a/devtools/client/netmonitor/test/browser_net_columns_time.js
+++ b/devtools/client/netmonitor/test/browser_net_columns_time.js
@@ -1,26 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
- * Tests for timings columns.
+ * Tests for timings columns. Note that the column
+ * header is visible only if there are requests in the list.
  */
 add_task(async function() {
   const { tab, monitor } = await initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   const { document, store, windowRequire } = monitor.panelWin;
   const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   store.dispatch(Actions.batchEnable(false));
 
   const visibleColumns = store.getState().ui.columns;
 
+  const wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.reload();
+  await wait;
+
   // Hide the waterfall column to make sure timing data are fetched
   // by the other timing columns ("endTime", "responseTime", "duration",
   // "latency").
   // Note that all these timing columns are based on the same
   // `RequestListColumnTime` component.
   if (visibleColumns.waterfall) {
     await hideColumn(monitor, "waterfall");
   }
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_headers-resize.js
@@ -0,0 +1,199 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests resizing of columns in NetMonitor.
+ */
+add_task(async function() {
+  // Reset visibleColumns so we only get the default ones
+  // and not all that are set in head.js
+  Services.prefs.clearUserPref("devtools.netmonitor.visibleColumns");
+  let visibleColumns = JSON.parse(
+    Services.prefs.getCharPref("devtools.netmonitor.visibleColumns")
+  );
+
+  // Init network monitor
+  const { tab, monitor } = await initNetMonitor(SIMPLE_URL);
+  info("Starting test... ");
+
+  const { document, windowRequire, store } = monitor.panelWin;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  store.dispatch(Actions.batchEnable(false));
+
+  // Wait for network events (to have some requests in the table)
+  const wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.reload();
+  await wait;
+
+  const headers = document.querySelector(".requests-list-headers");
+  const parentWidth = headers.getBoundingClientRect().width;
+
+  // 1. Change File column from 25% (default) to 20%
+  // Size column should then change from 5% (default) to 10%
+  // When File width changes, contentSize should compensate the change.
+  info("Resize file & check changed prefs...");
+  const fileHeader = document.querySelector(`#requests-list-file-header-box`);
+
+  resizeColumn(fileHeader, 20, parentWidth);
+
+  // after resize - get fresh prefs for tests
+  let columnsData = JSON.parse(
+    Services.prefs.getCharPref("devtools.netmonitor.columnsData")
+  );
+  checkColumnsData(columnsData, "file", 20);
+  checkColumnsData(columnsData, "contentSize", 10);
+  checkSumOfVisibleColumns(columnsData, visibleColumns);
+
+  // 2. Change Waterfall column width and check that the size
+  // of waterfall changed correctly and all the other columns changed size.
+  info("Resize waterfall & check changed prefs...");
+  const waterfallHeader = document.querySelector(`#requests-list-waterfall-header-box`);
+  // before resizing waterfall -> save old columnsData for later testing
+  const oldColumnsData = JSON.parse(
+    Services.prefs.getCharPref("devtools.netmonitor.columnsData")
+  );
+  resizeWaterfallColumn(waterfallHeader, 30, parentWidth); // 30 fails currently!
+
+  // after resize - get fresh prefs for tests
+  columnsData = JSON.parse(
+    Services.prefs.getCharPref("devtools.netmonitor.columnsData")
+  );
+
+  checkColumnsData(columnsData, "waterfall", 30);
+  checkSumOfVisibleColumns(columnsData, visibleColumns);
+  checkAllColumnsChanged(columnsData, oldColumnsData, visibleColumns);
+
+  // 3. Check that all rows have the right column sizes.
+  info("Checking alignment of columns and headers...");
+  const requestsContainer = document.querySelector(".requests-list-row-group");
+  testColumnsAlignment(headers, requestsContainer);
+
+  // 4. Hide all columns but size and waterfall
+  // and check that they resize correctly. Then resize
+  // waterfall to 50% => size should take up 50%
+  info("Hide all but 2 columns - size & waterfall and check resizing...");
+  await hideMoreColumns(monitor,
+    ["status", "method", "domain", "file", "cause", "type", "transferred"]);
+
+  resizeWaterfallColumn(waterfallHeader, 50, parentWidth);
+  // after resize - get fresh prefs for tests
+  columnsData = JSON.parse(
+    Services.prefs.getCharPref("devtools.netmonitor.columnsData")
+  );
+  visibleColumns = JSON.parse(
+    Services.prefs.getCharPref("devtools.netmonitor.visibleColumns")
+  );
+
+  checkColumnsData(columnsData, "contentSize", 50);
+  checkColumnsData(columnsData, "waterfall", 50);
+  checkSumOfVisibleColumns(columnsData, visibleColumns);
+
+  // 5. Hide all columns but domain and file
+  // and resize domain to 50% => file should be 50%
+  info("Hide all but 2 columns - domain & file and check resizing...");
+  await showMoreColumns(monitor, ["domain", "file"]);
+  await hideMoreColumns(monitor, ["contentSize", "waterfall"]);
+
+  const domainHeader = document.querySelector(`#requests-list-domain-header-box`);
+  resizeColumn(domainHeader, 50, parentWidth);
+
+  // after resize - get fresh prefs for tests
+  columnsData = JSON.parse(
+    Services.prefs.getCharPref("devtools.netmonitor.columnsData")
+  );
+
+  visibleColumns = JSON.parse(
+    Services.prefs.getCharPref("devtools.netmonitor.visibleColumns")
+  );
+
+  checkColumnsData(columnsData, "domain", 50);
+  checkColumnsData(columnsData, "file", 50);
+  checkSumOfVisibleColumns(columnsData, visibleColumns);
+
+  // Done: clean up.
+  return teardown(monitor);
+});
+
+async function hideMoreColumns(monitor, arr) {
+  for (let i = 0; i < arr.length; i++) {
+    await hideColumn(monitor, arr[i]);
+  }
+}
+
+async function showMoreColumns(monitor, arr) {
+  for (let i = 0; i < arr.length; i++) {
+    await showColumn(monitor, arr[i]);
+  }
+}
+
+function resizeColumn(columnHeader, newPercent, parentWidth) {
+  const newWidthInPixels = newPercent * parentWidth / 100;
+  const win = columnHeader.ownerDocument.defaultView;
+  const mouseDown = columnHeader.getBoundingClientRect().width;
+  const mouseMove = newWidthInPixels;
+
+  EventUtils.synthesizeMouse(columnHeader, mouseDown, 1, { type: "mousedown" }, win);
+  EventUtils.synthesizeMouse(columnHeader, mouseMove, 1, { type: "mousemove" }, win);
+  EventUtils.synthesizeMouse(columnHeader, mouseMove, 1, { type: "mouseup" }, win);
+}
+
+function resizeWaterfallColumn(columnHeader, newPercent, parentWidth) {
+  const newWidthInPixels = newPercent * parentWidth / 100;
+  const win = columnHeader.ownerDocument.defaultView;
+  const mouseDown = columnHeader.getBoundingClientRect().left;
+  const mouseMove =
+    mouseDown + (columnHeader.getBoundingClientRect().width - newWidthInPixels);
+
+  EventUtils.synthesizeMouse(
+    columnHeader.parentElement, mouseDown, 1, { type: "mousedown" }, win);
+  EventUtils.synthesizeMouse(
+    columnHeader.parentElement, mouseMove, 1, { type: "mousemove" }, win);
+  EventUtils.synthesizeMouse(
+    columnHeader.parentElement, mouseMove, 1, { type: "mouseup" }, win);
+}
+
+function checkColumnsData(columnsData, column, expectedWidth) {
+  const widthInPref = Math.round(getWidthFromPref(columnsData, column));
+  is(widthInPref, expectedWidth, "Column " + column + " has expected size.");
+}
+
+function checkSumOfVisibleColumns(columnsData, visibleColumns) {
+  let sum = 0;
+  visibleColumns.forEach(column => {
+    sum += getWidthFromPref(columnsData, column);
+  });
+  sum = Math.round(sum);
+  is(sum, 100, "All visible columns cover 100%.");
+}
+
+function getWidthFromPref(columnsData, column) {
+  const widthInPref = columnsData.find(function(element) {
+    return element.name === column;
+  }).width;
+  return widthInPref;
+}
+
+function checkAllColumnsChanged(columnsData, oldColumnsData, visibleColumns) {
+  const oldWaterfallWidth = getWidthFromPref(oldColumnsData, "waterfall");
+  const newWaterfallWidth = getWidthFromPref(columnsData, "waterfall");
+  visibleColumns.forEach(column => {
+    // do not test waterfall against waterfall
+    if (column !== "waterfall") {
+      const oldWidth = getWidthFromPref(oldColumnsData, column);
+      const newWidth = getWidthFromPref(columnsData, column);
+
+      // Test that if waterfall is smaller all other columns are bigger
+      if (oldWaterfallWidth > newWaterfallWidth) {
+        is(oldWidth < newWidth, true,
+          "Column " + column + " has changed width correctly.");
+      }
+      // Test that if waterfall is bigger all other columns are smaller
+      if (oldWaterfallWidth < newWaterfallWidth) {
+        is(oldWidth > newWidth, true,
+          "Column " + column + " has changed width correctly.");
+      }
+    }
+  });
+}
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -113,26 +113,44 @@ const gDefaultFilters = Services.prefs.g
 Services.prefs.setCharPref(
   "devtools.netmonitor.visibleColumns",
   "[\"cause\",\"contentSize\",\"cookies\",\"domain\",\"duration\"," +
   "\"endTime\",\"file\",\"latency\",\"method\",\"protocol\"," +
   "\"remoteip\",\"responseTime\",\"scheme\",\"setCookies\"," +
   "\"startTime\",\"status\",\"transferred\",\"type\",\"waterfall\"]"
 );
 
+Services.prefs.setCharPref("devtools.netmonitor.columnsData",
+'[{"name":"status","minWidth":30,"width":5},' +
+  '{"name":"method","minWidth":30,"width":5},' +
+  '{"name":"domain","minWidth":30,"width":10},' +
+  '{"name":"file","minWidth":30,"width":25},' +
+  '{"name":"cause","minWidth":30,"width":10},' +
+  '{"name":"type","minWidth":30,"width":5},' +
+  '{"name":"transferred","minWidth":30,"width":10},' +
+  '{"name":"contentSize","minWidth":30,"width":5},' +
+  '{"name":"waterfall","minWidth":150,"width":25}]');
+
 // Increase UI limit for responses rendered using CodeMirror in tests.
 Services.prefs.setIntPref("devtools.netmonitor.response.ui.limit", 1024 * 105);
 
+// Support for columns resizing is currently hidden behind this pref,
+// but testing is on
+Services.prefs.setBoolPref("devtools.netmonitor.features.resizeColumns", true);
+
 registerCleanupFunction(() => {
   info("finish() was called, cleaning up...");
 
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setCharPref("devtools.netmonitor.filters", gDefaultFilters);
   Services.prefs.clearUserPref("devtools.cache.disabled");
+  Services.prefs.clearUserPref("devtools.netmonitor.columnsData");
   Services.prefs.clearUserPref("devtools.netmonitor.response.ui.limit");
+  Services.prefs.clearUserPref("devtools.netmonitor.visibleColumns");
+  Services.prefs.clearUserPref("devtools.netmonitor.features.resizeColumns");
   Services.cookies.removeAll();
 });
 
 function waitForNavigation(target) {
   return new Promise((resolve) => {
     target.once("will-navigate", () => {
       target.once("navigate", () => {
         resolve();
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -170,16 +170,22 @@ pref("devtools.application.enabled", fal
 
 // The default Network Monitor UI settings
 pref("devtools.netmonitor.panes-network-details-width", 550);
 pref("devtools.netmonitor.panes-network-details-height", 450);
 pref("devtools.netmonitor.filters", "[\"all\"]");
 pref("devtools.netmonitor.visibleColumns",
   "[\"status\",\"method\",\"domain\",\"file\",\"cause\",\"type\",\"transferred\",\"contentSize\",\"waterfall\"]"
 );
+pref("devtools.netmonitor.columnsData",
+  '[{"name":"status","minWidth":30,"width":5}, {"name":"method","minWidth":30,"width":5}, {"name":"domain","minWidth":30,"width":10}, {"name":"file","minWidth":30,"width":25}, {"name":"cause","minWidth":30,"width":10},{"name":"type","minWidth":30,"width":5},{"name":"transferred","minWidth":30,"width":10},{"name":"contentSize","minWidth":30,"width":5},{"name":"waterfall","minWidth":150,"width":25}]');
+
+// Support for columns resizing is currently hidden behind this pref.
+pref("devtools.netmonitor.features.resizeColumns", false);
+
 pref("devtools.netmonitor.response.ui.limit", 10240);
 
 // Save request/response bodies yes/no.
 pref("devtools.netmonitor.saveRequestAndResponseBodies", true);
 
 // The default Network monitor HAR export setting
 pref("devtools.netmonitor.har.defaultLogDir", "");
 pref("devtools.netmonitor.har.defaultFileName", "Archive %date");
--- a/devtools/client/shared/widgets/FlameGraph.js
+++ b/devtools/client/shared/widgets/FlameGraph.js
@@ -1,17 +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/. */
 "use strict";
 
 const { ViewHelpers, setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
 const { ELLIPSIS } = require("devtools/shared/l10n");
 
-loader.lazyRequireGetter(this, "defer", "devtools/shared/defer");
 loader.lazyRequireGetter(this, "EventEmitter",
   "devtools/shared/event-emitter");
 
 loader.lazyRequireGetter(this, "getColor",
   "devtools/client/shared/theme", true);
 
 loader.lazyRequireGetter(this, "CATEGORIES",
   "devtools/client/performance/modules/categories", true);
@@ -129,90 +128,91 @@ const COLOR_PALLETTE = Array.from(Array(
  *        The parent node holding the graph.
  * @param number sharpness [optional]
  *        Defaults to the current device pixel ratio.
  */
 function FlameGraph(parent, sharpness) {
   EventEmitter.decorate(this);
 
   this._parent = parent;
-  this._ready = defer();
 
   this.setTheme();
 
-  AbstractCanvasGraph.createIframe(GRAPH_SRC, parent, iframe => {
-    this._iframe = iframe;
-    this._window = iframe.contentWindow;
-    this._document = iframe.contentDocument;
-    this._pixelRatio = sharpness || this._window.devicePixelRatio;
+  this._ready = new Promise(resolve => {
+    AbstractCanvasGraph.createIframe(GRAPH_SRC, parent, iframe => {
+      this._iframe = iframe;
+      this._window = iframe.contentWindow;
+      this._document = iframe.contentDocument;
+      this._pixelRatio = sharpness || this._window.devicePixelRatio;
 
-    const container =
-      this._container = this._document.getElementById("graph-container");
-    container.className = "flame-graph-widget-container graph-widget-container";
+      const container =
+        this._container = this._document.getElementById("graph-container");
+      container.className = "flame-graph-widget-container graph-widget-container";
 
-    const canvas = this._canvas = this._document.getElementById("graph-canvas");
-    canvas.className = "flame-graph-widget-canvas graph-widget-canvas";
+      const canvas = this._canvas = this._document.getElementById("graph-canvas");
+      canvas.className = "flame-graph-widget-canvas graph-widget-canvas";
 
-    const bounds = parent.getBoundingClientRect();
-    bounds.width = this.fixedWidth || bounds.width;
-    bounds.height = this.fixedHeight || bounds.height;
-    iframe.setAttribute("width", bounds.width);
-    iframe.setAttribute("height", bounds.height);
+      const bounds = parent.getBoundingClientRect();
+      bounds.width = this.fixedWidth || bounds.width;
+      bounds.height = this.fixedHeight || bounds.height;
+      iframe.setAttribute("width", bounds.width);
+      iframe.setAttribute("height", bounds.height);
 
-    this._width = canvas.width = bounds.width * this._pixelRatio;
-    this._height = canvas.height = bounds.height * this._pixelRatio;
-    this._ctx = canvas.getContext("2d");
+      this._width = canvas.width = bounds.width * this._pixelRatio;
+      this._height = canvas.height = bounds.height * this._pixelRatio;
+      this._ctx = canvas.getContext("2d");
 
-    this._bounds = new GraphArea();
-    this._selection = new GraphArea();
-    this._selectionDragger = new GraphAreaDragger();
-    this._verticalOffset = 0;
-    this._verticalOffsetDragger = new GraphAreaDragger(0);
-    this._keyboardZoomAccelerationFactor = 1;
-    this._keyboardPanAccelerationFactor = 1;
+      this._bounds = new GraphArea();
+      this._selection = new GraphArea();
+      this._selectionDragger = new GraphAreaDragger();
+      this._verticalOffset = 0;
+      this._verticalOffsetDragger = new GraphAreaDragger(0);
+      this._keyboardZoomAccelerationFactor = 1;
+      this._keyboardPanAccelerationFactor = 1;
 
-    this._userInputStack = 0;
-    this._keysPressed = [];
+      this._userInputStack = 0;
+      this._keysPressed = [];
 
-    // Calculating text widths is necessary to trim the text inside the blocks
-    // while the scaling changes (e.g. via scrolling). This is very expensive,
-    // so maintain a cache of string contents to text widths.
-    this._textWidthsCache = {};
+      // Calculating text widths is necessary to trim the text inside the blocks
+      // while the scaling changes (e.g. via scrolling). This is very expensive,
+      // so maintain a cache of string contents to text widths.
+      this._textWidthsCache = {};
 
-    const fontSize = FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE * this._pixelRatio;
-    const fontFamily = FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY;
-    this._ctx.font = fontSize + "px " + fontFamily;
-    this._averageCharWidth = this._calcAverageCharWidth();
-    this._overflowCharWidth = this._getTextWidth(this.overflowChar);
+      const fontSize = FLAME_GRAPH_BLOCK_TEXT_FONT_SIZE * this._pixelRatio;
+      const fontFamily = FLAME_GRAPH_BLOCK_TEXT_FONT_FAMILY;
+      this._ctx.font = fontSize + "px " + fontFamily;
+      this._averageCharWidth = this._calcAverageCharWidth();
+      this._overflowCharWidth = this._getTextWidth(this.overflowChar);
 
-    this._onAnimationFrame = this._onAnimationFrame.bind(this);
-    this._onKeyDown = this._onKeyDown.bind(this);
-    this._onKeyUp = this._onKeyUp.bind(this);
-    this._onMouseMove = this._onMouseMove.bind(this);
-    this._onMouseDown = this._onMouseDown.bind(this);
-    this._onMouseUp = this._onMouseUp.bind(this);
-    this._onMouseWheel = this._onMouseWheel.bind(this);
-    this._onResize = this._onResize.bind(this);
-    this.refresh = this.refresh.bind(this);
+      this._onAnimationFrame = this._onAnimationFrame.bind(this);
+      this._onKeyDown = this._onKeyDown.bind(this);
+      this._onKeyUp = this._onKeyUp.bind(this);
+      this._onMouseMove = this._onMouseMove.bind(this);
+      this._onMouseDown = this._onMouseDown.bind(this);
+      this._onMouseUp = this._onMouseUp.bind(this);
+      this._onMouseWheel = this._onMouseWheel.bind(this);
+      this._onResize = this._onResize.bind(this);
+      this.refresh = this.refresh.bind(this);
 
-    this._window.addEventListener("keydown", this._onKeyDown);
-    this._window.addEventListener("keyup", this._onKeyUp);
-    this._window.addEventListener("mousemove", this._onMouseMove);
-    this._window.addEventListener("mousedown", this._onMouseDown);
-    this._window.addEventListener("mouseup", this._onMouseUp);
-    this._window.addEventListener("MozMousePixelScroll", this._onMouseWheel);
+      this._window.addEventListener("keydown", this._onKeyDown);
+      this._window.addEventListener("keyup", this._onKeyUp);
+      this._window.addEventListener("mousemove", this._onMouseMove);
+      this._window.addEventListener("mousedown", this._onMouseDown);
+      this._window.addEventListener("mouseup", this._onMouseUp);
+      this._window.addEventListener("MozMousePixelScroll", this._onMouseWheel);
 
-    const ownerWindow = this._parent.ownerDocument.defaultView;
-    ownerWindow.addEventListener("resize", this._onResize);
+      const ownerWindow = this._parent.ownerDocument.defaultView;
+      ownerWindow.addEventListener("resize", this._onResize);
 
-    this._animationId =
-      this._window.requestAnimationFrame(this._onAnimationFrame);
+      this._animationId =
+        this._window.requestAnimationFrame(this._onAnimationFrame);
 
-    this._ready.resolve(this);
-    this.emit("ready", this);
+      resolve(this);
+      this.emit("ready", this);
+    });
   });
 }
 
 FlameGraph.prototype = {
   /**
    * Read-only width and height of the canvas.
    * @return number
    */
@@ -222,17 +222,17 @@ FlameGraph.prototype = {
   get height() {
     return this._height;
   },
 
   /**
    * Returns a promise resolved once this graph is ready to receive data.
    */
   ready: function() {
-    return this._ready.promise;
+    return this._ready;
   },
 
   /**
    * Destroys this graph.
    */
   async destroy() {
     await this.ready();
 
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -247,17 +247,17 @@ a {
 .message-body-wrapper .table-widget-body {
   overflow: visible;
 }
 
 /* The bubble that shows the number of times a message is repeated */
 .message-repeats {
   -moz-user-select: none;
   flex-shrink: 0;
-  margin: 2px 5px;
+  margin: 2px 5px 0 5px;
   padding: 0 6px;
   height: 1.25em;
   color: white;
   background-color: var(--repeat-bubble-background-color);
   border-radius: 40px;
   font: message-box;
   font-size: 0.8em;
   font-weight: normal;
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_scroll.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_scroll.js
@@ -98,16 +98,43 @@ add_task(async function() {
   });
   message = await onMessage;
   ok(hasVerticalOverflow(outputContainer), "There is a vertical overflow");
   ok(isScrolledToBottom(outputContainer), "The console is scrolled to the bottom");
 
   info("Wait until the stacktrace is rendered");
   await waitFor(() => message.node.querySelector(".frame"));
   ok(isScrolledToBottom(outputContainer), "The console is scrolled to the bottom");
+
+  info("Check that repeated messages don't prevent scroll to bottom");
+  // We log a first message.
+  onMessage = waitForMessage(hud, "repeat");
+  ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+    content.wrappedJSObject.console.log("repeat");
+  });
+  message = await onMessage;
+
+  // And a second one. We can't log them at the same time since we batch redux actions,
+  // and the message would already appear with the repeat badge, and the bug is
+  // only triggered when the badge is rendered after the initial message rendering.
+  ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+    content.wrappedJSObject.console.log("repeat");
+  });
+  await waitFor(() => message.node.querySelector(".message-repeats"));
+  ok(isScrolledToBottom(outputContainer),
+    "The console is still scrolled to the bottom when the repeat badge is added");
+
+  info("Check that adding a message after a repeated message scrolls to bottom");
+  onMessage = waitForMessage(hud, "after repeat");
+  ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+    content.wrappedJSObject.console.log("after repeat");
+  });
+  message = await onMessage;
+  ok(isScrolledToBottom(outputContainer),
+    "The console is scrolled to the bottom after a repeated message");
 });
 
 function hasVerticalOverflow(container) {
   return container.scrollHeight > container.clientHeight;
 }
 
 function isScrolledToBottom(container) {
   if (!container.lastChild) {
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -1773,17 +1773,16 @@ struct MOZ_STACK_CLASS CompartmentFinder
   // Input: we look for a compartment which is same-origin with the
   // given principal.
   nsIPrincipal* principal;
 
   // Output: We set this member if we find a compartment.
   JS::Compartment* compartment;
 };
 
-#if 0 /* Temporarily disabled; will reenable in bug 1533105 */
 static JS::CompartmentIterResult FindSameOriginCompartment(
     JSContext* aCx, void* aData, JS::Compartment* aCompartment) {
   auto* data = static_cast<CompartmentFinderState*>(aData);
   MOZ_ASSERT(!data->compartment, "Why are we getting called?");
 
   // If this compartment is not safe to share across globals, don't do
   // anything with it; in particular we should not be getting a
   // CompartmentPrivate from such a compartment, because it may be in
@@ -1798,17 +1797,16 @@ static JS::CompartmentIterResult FindSam
     // Can't reuse this one, keep going.
     return JS::CompartmentIterResult::KeepGoing;
   }
 
   // We have a winner!
   data->compartment = aCompartment;
   return JS::CompartmentIterResult::Stop;
 }
-#endif
 
 static JS::RealmCreationOptions& SelectZone(
     JSContext* aCx, nsIPrincipal* aPrincipal, nsGlobalWindowInner* aNewInner,
     JS::RealmCreationOptions& aOptions) {
   // Use the shared system compartment for chrome windows.
   if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
     return aOptions.setExistingCompartment(xpc::PrivilegedJunkScope());
   }
@@ -1819,26 +1817,24 @@ static JS::RealmCreationOptions& SelectZ
       // We're a toplevel load.  Use a new zone.  This way, when we do
       // zone-based compartment sharing we won't share compartments
       // across navigations.
       return aOptions.setNewCompartmentAndZone();
     }
 
     // If we have a top-level window, use its zone.
     if (top && top->GetGlobalJSObject()) {
-#if 0 /* Temporarily disabled; will reenable in bug 1533105 */
       JS::Zone* zone = JS::GetObjectZone(top->GetGlobalJSObject());
       // Now try to find an existing compartment that's same-origin
       // with our principal.
       CompartmentFinderState data(aPrincipal);
       JS_IterateCompartmentsInZone(aCx, zone, &data, FindSameOriginCompartment);
       if (data.compartment) {
         return aOptions.setExistingCompartment(data.compartment);
       }
-#endif
       return aOptions.setNewCompartmentInExistingZone(top->GetGlobalJSObject());
     }
   }
 
   return aOptions.setNewCompartmentAndZone();
 }
 
 /**
--- a/dom/base/test/test_shared_compartment1.html
+++ b/dom/base/test/test_shared_compartment1.html
@@ -22,17 +22,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     if (testsDone === 4) {
       SimpleTest.finish();
     }
   }
 
   // Test 1: same-origin iframe.
   function testFrame1() {
     var frameWin = document.getElementById("frame1").contentWindow;
-    todo(isSameCompartment(window, frameWin),
+    ok(isSameCompartment(window, frameWin),
        "Same-origin iframe must be same-compartment");
     finishIfDone();
   }
 
   // Test 2: cross-origin iframe.
   function testFrame2() {
     var frameWin = document.getElementById("frame2").contentWindow;
     ok(!isSameCompartment(window, frameWin),
@@ -43,28 +43,28 @@ https://bugzilla.mozilla.org/show_bug.cg
   // Test 3: same-site, cross-origin iframe.
   function testFrame3() {
     var frame = document.getElementById("frame3");
     ok(!isSameCompartment(window, frame.contentWindow),
        "Same-site cross-origin iframe must be cross-compartment");
 
     // Now load a same-origin page in this iframe.
     frame.onload = function() {
-      todo(isSameCompartment(window, frame.contentWindow),
+      ok(isSameCompartment(window, frame.contentWindow),
          "Frame must be same-compartment now");
       finishIfDone();
     };
     frame.src = "file_empty.html";
   }
 
   // Test 4: dynamically created iframe.
   addLoadEvent(function() {
     var frame = document.createElement("iframe");
     document.body.appendChild(frame);
-    todo(isSameCompartment(window, frame.contentWindow),
+    ok(isSameCompartment(window, frame.contentWindow),
        "Newly created iframe must be same-compartment");
     finishIfDone();
   });
 
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1530608">Mozilla Bug 1530608</a>
--- a/dom/base/test/test_shared_compartment2.html
+++ b/dom/base/test/test_shared_compartment2.html
@@ -25,17 +25,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   function go(innerWin) {
     var Cu = SpecialPowers.Cu;
     var isSameCompartment = Cu.getJSTestingFunctions().isSameCompartment;
 
     var frame = document.getElementById("frame");
     ok(!isSameCompartment(window, frame.contentWindow),
        "Cross-origin iframe must be cross-compartment");
 
-    todo(isSameCompartment(window, innerWin),
+    ok(isSameCompartment(window, innerWin),
        "Same-origin inner iframe must be same-compartment");
 
     SimpleTest.finish();
   }
 
   </script>
 </head>
 <body>
--- a/dom/media/AudioDeviceInfo.cpp
+++ b/dom/media/AudioDeviceInfo.cpp
@@ -57,16 +57,17 @@ AudioDeviceInfo::AudioDeviceInfo(
              "Wrong default format");
 }
 
 AudioDeviceID AudioDeviceInfo::DeviceID() const { return mDeviceId; }
 const nsString& AudioDeviceInfo::Name() const { return mName; }
 uint32_t AudioDeviceInfo::MaxChannels() const { return mMaxChannels; }
 uint32_t AudioDeviceInfo::Type() const { return mType; }
 uint32_t AudioDeviceInfo::State() const { return mState; }
+const nsString& AudioDeviceInfo::GroupID() const { return mGroupId; }
 
 bool AudioDeviceInfo::Preferred() const { return mPreferred; }
 
 /* readonly attribute DOMString name; */
 NS_IMETHODIMP
 AudioDeviceInfo::GetName(nsAString& aName) {
   aName = mName;
   return NS_OK;
--- a/dom/media/AudioDeviceInfo.h
+++ b/dom/media/AudioDeviceInfo.h
@@ -27,16 +27,17 @@ class AudioDeviceInfo final : public nsI
                   uint32_t aMinLatency);
   explicit AudioDeviceInfo(cubeb_device_info* aInfo);
 
   AudioDeviceID DeviceID() const;
   const nsString& Name() const;
   uint32_t MaxChannels() const;
   uint32_t Type() const;
   uint32_t State() const;
+  const nsString& GroupID() const;
   bool Preferred() const;
 
  private:
   virtual ~AudioDeviceInfo() = default;
 
   const AudioDeviceID mDeviceId;
   const nsString mName;
   const nsString mGroupId;
--- a/dom/media/MediaDeviceInfo.h
+++ b/dom/media/MediaDeviceInfo.h
@@ -19,18 +19,17 @@ namespace dom {
     0x25091870, 0x84d6, 0x4acf, {                      \
       0xaf, 0x97, 0x6e, 0xd5, 0x5b, 0xe0, 0x47, 0xb2   \
     }                                                  \
   }
 
 class MediaDeviceInfo final : public nsISupports, public nsWrapperCache {
  public:
   explicit MediaDeviceInfo(const nsAString& aDeviceId, MediaDeviceKind aKind,
-                           const nsAString& aLabel,
-                           const nsAString& aGroupId = nsString());
+                           const nsAString& aLabel, const nsAString& aGroupId);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaDeviceInfo)
   NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_MEDIADEVICEINFO_IMPLEMENTATION_IID)
 
   JSObject* WrapObject(JSContext* cx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
--- a/dom/media/MediaDevices.cpp
+++ b/dom/media/MediaDevices.cpp
@@ -110,17 +110,17 @@ already_AddRefed<Promise> MediaDevices::
                  nsString label;
                  if (MediaManager::Get()->IsActivelyCapturingOrHasAPermission(
                          windowId) ||
                      Preferences::GetBool("media.navigator.permission.disabled",
                                           false)) {
                    label = device->mName;
                  }
                  infos.AppendElement(MakeRefPtr<MediaDeviceInfo>(
-                     device->mID, device->mKind, label));
+                     device->mID, device->mKind, label, device->mGroupID));
                }
                p->MaybeResolve(std::move(infos));
              },
              [this, self, p](const RefPtr<MediaMgrError>& error) {
                nsPIDOMWindowInner* window = GetWindowIfCurrent();
                if (!window) {
                  return;  // Leave Promise pending after navigation by design.
                }
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -808,61 +808,65 @@ class GetUserMediaWindowListener {
 
 /**
  * nsIMediaDevice implementation.
  */
 NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
 
 MediaDevice::MediaDevice(const RefPtr<MediaEngineSource>& aSource,
                          const nsString& aName, const nsString& aID,
-                         const nsString& aRawID)
+                         const nsString& aGroupID, const nsString& aRawID)
     : mSource(aSource),
       mSinkInfo(nullptr),
       mKind((mSource && MediaEngineSource::IsVideo(mSource->GetMediaSource()))
                 ? dom::MediaDeviceKind::Videoinput
                 : dom::MediaDeviceKind::Audioinput),
       mScary(mSource->GetScary()),
       mType(NS_ConvertUTF8toUTF16(
           dom::MediaDeviceKindValues::strings[uint32_t(mKind)].value)),
       mName(aName),
       mID(aID),
+      mGroupID(aGroupID),
       mRawID(aRawID) {
   MOZ_ASSERT(mSource);
 }
 
 MediaDevice::MediaDevice(const RefPtr<AudioDeviceInfo>& aAudioDeviceInfo,
-                         const nsString& aID, const nsString& aRawID)
+                         const nsString& aID, const nsString& aGroupID,
+                         const nsString& aRawID)
     : mSource(nullptr),
       mSinkInfo(aAudioDeviceInfo),
       mKind(mSinkInfo->Type() == AudioDeviceInfo::TYPE_INPUT
                 ? dom::MediaDeviceKind::Audioinput
                 : dom::MediaDeviceKind::Audiooutput),
       mScary(false),
       mType(NS_ConvertUTF8toUTF16(
           dom::MediaDeviceKindValues::strings[uint32_t(mKind)].value)),
       mName(mSinkInfo->Name()),
       mID(aID),
+      mGroupID(aGroupID),
       mRawID(aRawID) {
   // For now this ctor is used only for Audiooutput.
   // It could be used for Audioinput and Videoinput
   // when we do not instantiate a MediaEngineSource
   // during EnumerateDevices.
   MOZ_ASSERT(mKind == dom::MediaDeviceKind::Audiooutput);
   MOZ_ASSERT(mSinkInfo);
 }
 
 MediaDevice::MediaDevice(const RefPtr<MediaDevice>& aOther, const nsString& aID,
-                         const nsString& aRawID)
+                         const nsString& aGroupID, const nsString& aRawID)
     : mSource(aOther->mSource),
       mSinkInfo(aOther->mSinkInfo),
       mKind(aOther->mKind),
       mScary(aOther->mScary),
       mType(aOther->mType),
       mName(aOther->mName),
       mID(aID),
+      mGroupID(aGroupID),
       mRawID(aRawID) {
   MOZ_ASSERT(aOther);
 }
 
 /**
  * Helper functions that implement the constraints algorithm from
  * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
  */
@@ -947,16 +951,23 @@ MediaDevice::GetId(nsAString& aID) {
 NS_IMETHODIMP
 MediaDevice::GetRawId(nsAString& aID) {
   MOZ_ASSERT(NS_IsMainThread());
   aID.Assign(mRawID);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+MediaDevice::GetGroupId(nsAString& aGroupID) {
+  MOZ_ASSERT(NS_IsMainThread());
+  aGroupID.Assign(mGroupID);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 MediaDevice::GetScary(bool* aScary) {
   *aScary = mScary;
   return NS_OK;
 }
 
 void MediaDevice::GetSettings(dom::MediaTrackSettings& aOutSettings) const {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mSource);
@@ -1763,16 +1774,80 @@ class GetUserMediaRunnableWrapper : publ
     return NS_OK;
   }
 
  private:
   nsAutoPtr<GetUserMediaTask> mTask;
 };
 #endif
 
+// This function tries to guess the group id for a video device
+// based on the device name. If only one audio device's name contains
+// the name of the video device, then, this video device will take
+// the group id of the audio device. Since this is a guess we try
+// to minimize the probability of false positive. If we fail to find
+// a correlation we leave the video group id untouched. In that case the
+// group id will be the video device name.
+/* static */
+void MediaManager::GuessVideoDeviceGroupIDs(MediaDeviceSet& aDevices) {
+  // Run the logic in a lambda to avoid duplication.
+  auto updateGroupIdIfNeeded = [&](RefPtr<MediaDevice>& aVideo,
+                                   const dom::MediaDeviceKind aKind) -> bool {
+    MOZ_ASSERT(aVideo->mKind == dom::MediaDeviceKind::Videoinput);
+    MOZ_ASSERT(aKind == dom::MediaDeviceKind::Audioinput ||
+               aKind == dom::MediaDeviceKind::Audiooutput);
+    // This will store the new group id if a match is found.
+    nsString newVideoGroupID;
+    // If the group id needs to be updated this will become true. It is
+    // necessary when the new group id is an empty string. Without this extra
+    // variable to signal the update, we would resort to test if
+    // `newVideoGroupId` is empty. However,
+    // that check does not work when the new group id is an empty string.
+    bool updateGroupId = false;
+    for (const RefPtr<MediaDevice>& dev : aDevices) {
+      if (dev->mKind != aKind) {
+        continue;
+      }
+      if (!FindInReadable(aVideo->mName, dev->mName)) {
+        continue;
+      }
+      if (newVideoGroupID.IsEmpty()) {
+        // This is only expected on first match. If that's the only match group
+        // id will be updated to this one at the end of the loop.
+        updateGroupId = true;
+        newVideoGroupID = dev->mGroupID;
+      } else {
+        // More than one device found, it is impossible to know which group id
+        // is the correct one.
+        updateGroupId = false;
+        newVideoGroupID = NS_LITERAL_STRING("");
+        break;
+      }
+    }
+    if (updateGroupId) {
+      aVideo =
+          new MediaDevice(aVideo, aVideo->mID, newVideoGroupID, aVideo->mRawID);
+      return true;
+    }
+    return false;
+  };
+
+  for (RefPtr<MediaDevice>& video : aDevices) {
+    if (video->mKind != dom::MediaDeviceKind::Videoinput) {
+      continue;
+    }
+    if (updateGroupIdIfNeeded(video, dom::MediaDeviceKind::Audioinput)) {
+      // GroupId has been updated, continue to the next video device
+      continue;
+    }
+    // GroupId has not been updated, check among the outputs
+    updateGroupIdIfNeeded(video, dom::MediaDeviceKind::Audiooutput);
+  }
+}
+
 /**
  * EnumerateRawDevices - Enumerate a list of audio & video devices that
  * satisfy passed-in constraints. List contains raw id's.
  */
 
 RefPtr<MediaManager::MgrPromise> MediaManager::EnumerateRawDevices(
     uint64_t aWindowId, MediaSourceEnum aVideoInputType,
     MediaSourceEnum aAudioInputType, MediaSinkEnum aAudioOutputType,
@@ -1875,16 +1950,19 @@ RefPtr<MediaManager::MgrPromise> MediaMa
     }
     if (hasAudioOutput) {
       MediaDeviceSet outputs;
       MOZ_ASSERT(realBackend);
       realBackend->EnumerateDevices(aWindowId, MediaSourceEnum::Other,
                                     MediaSinkEnum::Speaker, &outputs);
       aOutDevices->AppendElements(outputs);
     }
+    if (hasVideo) {
+      GuessVideoDeviceGroupIDs(*aOutDevices);
+    }
 
     holder->Resolve(false, __func__);
   });
 
   if (realDeviceRequested && aForceNoPermRequest &&
       Preferences::GetBool("media.navigator.permission.device", false)) {
     // Need to ask permission to retrieve list of all devices;
     // notify frontend observer and wait for callback notification to post task.
@@ -2999,24 +3077,37 @@ RefPtr<MediaManager::StreamPromise> Medi
   vc.mMediaSource.AssignASCII(EnumToASCII(dom::MediaSourceEnumValues::strings,
                                           MediaSourceEnum::Screen));
 
   return MediaManager::GetUserMedia(aWindow, c, aCallerType);
 }
 
 /* static */
 void MediaManager::AnonymizeDevices(MediaDeviceSet& aDevices,
-                                    const nsACString& aOriginKey) {
+                                    const nsACString& aOriginKey,
+                                    const uint64_t aWindowId) {
+
   if (!aOriginKey.IsEmpty()) {
     for (RefPtr<MediaDevice>& device : aDevices) {
       nsString id;
       device->GetId(id);
       nsString rawId(id);
       AnonymizeId(id, aOriginKey);
-      device = new MediaDevice(device, id, rawId);
+
+      nsString groupId;
+      device->GetGroupId(groupId);
+      // Use window id to salt group id in order to make it session based as
+      // required by the spec. This does not provide unique group ids through
+      // out a browser restart. However, this is not agaist the spec.
+      // Furtermore, since device ids are the same after a browser restart the
+      // fingerprint is not bigger.
+      groupId.AppendInt(aWindowId);
+      AnonymizeId(groupId, aOriginKey);
+
+      device = new MediaDevice(device, id, groupId, rawId);
     }
   }
 }
 
 /* static */
 nsresult MediaManager::AnonymizeId(nsAString& aId,
                                    const nsACString& aOriginKey) {
   MOZ_ASSERT(NS_IsMainThread());
@@ -3185,17 +3276,18 @@ RefPtr<MediaManager::MgrPromise> MediaMa
                    }
                  }
                }
                if (!mgr->IsWindowStillActive(aWindowId)) {
                  return MgrPromise::CreateAndReject(
                      MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError),
                      __func__);
                }
-               MediaManager::AnonymizeDevices(*aOutDevices, *originKey);
+               MediaManager::AnonymizeDevices(*aOutDevices, *originKey,
+                                              aWindowId);
                return MgrPromise::CreateAndResolve(false, __func__);
              },
              [](RefPtr<MediaMgrError>&& aError) {
                return MgrPromise::CreateAndReject(std::move(aError), __func__);
              });
 }
 
 RefPtr<MediaManager::DevicesPromise> MediaManager::EnumerateDevices(
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -62,26 +62,26 @@ class GetUserMediaWindowListener;
 class MediaManager;
 class SourceListener;
 
 class MediaDevice : public nsIMediaDevice {
  public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEDIADEVICE
 
-  explicit MediaDevice(const RefPtr<MediaEngineSource>& aSource,
-                       const nsString& aName, const nsString& aID,
-                       const nsString& aRawID);
+  MediaDevice(const RefPtr<MediaEngineSource>& aSource, const nsString& aName,
+              const nsString& aID, const nsString& aGroupID,
+              const nsString& aRawID);
 
-  explicit MediaDevice(const RefPtr<AudioDeviceInfo>& aAudioDeviceInfo,
-                       const nsString& aID,
-                       const nsString& aRawID = NS_LITERAL_STRING(""));
+  MediaDevice(const RefPtr<AudioDeviceInfo>& aAudioDeviceInfo,
+              const nsString& aID, const nsString& aGroupID,
+              const nsString& aRawID = NS_LITERAL_STRING(""));
 
-  explicit MediaDevice(const RefPtr<MediaDevice>& aOther, const nsString& aID,
-                       const nsString& aRawID);
+  MediaDevice(const RefPtr<MediaDevice>& aOther, const nsString& aID,
+              const nsString& aGroupID, const nsString& aRawID);
 
   uint32_t GetBestFitnessDistance(
       const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
       bool aIsChrome);
 
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs,
                     const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
@@ -125,16 +125,17 @@ class MediaDevice : public nsIMediaDevic
  public:
   const RefPtr<MediaEngineSource> mSource;
   const RefPtr<AudioDeviceInfo> mSinkInfo;
   const dom::MediaDeviceKind mKind;
   const bool mScary;
   const nsString mType;
   const nsString mName;
   const nsString mID;
+  const nsString mGroupID;
   const nsString mRawID;
 };
 
 typedef nsRefPtrHashtable<nsUint64HashKey, GetUserMediaWindowListener>
     WindowTable;
 typedef MozPromise<RefPtr<AudioDeviceInfo>, nsresult, true> SinkInfoPromise;
 
 class MediaManager final : public nsIMediaManagerService,
@@ -270,19 +271,21 @@ class MediaManager final : public nsIMed
   virtual void OnDeviceChange() override;
 
  private:
   static nsresult GenerateUUID(nsAString& aResult);
   static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey);
 
  public:  // TODO: make private once we upgrade to GCC 4.8+ on linux.
   static void AnonymizeDevices(MediaDeviceSet& aDevices,
-                               const nsACString& aOriginKey);
+                               const nsACString& aOriginKey,
+                               const uint64_t aWindowId);
   static already_AddRefed<nsIWritableVariant> ToJSArray(
       MediaDeviceSet& aDevices);
+  static void GuessVideoDeviceGroupIDs(MediaManager::MediaDeviceSet& aDevices);
 
  private:
   enum class DeviceEnumerationType : uint8_t {
     Normal,  // Enumeration should not return loopback or fake devices
     Fake,    // Enumeration should return fake device(s)
     Loopback /* Enumeration should return loopback device(s) (possibly in
              addition to normal devices) */
   };
new file mode 100644
--- /dev/null
+++ b/dom/media/gtest/TestGroupId.cpp
@@ -0,0 +1,336 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioDeviceInfo.h"
+#include "MediaManager.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest-printers.h"
+#include "gtest/gtest.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+#include "webrtc/MediaEngineSource.h"
+
+using ::testing::Return;
+
+void PrintTo(const nsString& aValue, ::std::ostream* aStream) {
+  NS_ConvertUTF16toUTF8 str(aValue);
+  (*aStream) << str.get();
+}
+void PrintTo(const nsCString& aValue, ::std::ostream* aStream) {
+  (*aStream) << aValue.get();
+}
+
+class MockMediaEngineSource : public MediaEngineSource {
+ public:
+  MOCK_CONST_METHOD0(GetMediaSource, dom::MediaSourceEnum());
+
+  /* Unused overrides */
+  MOCK_CONST_METHOD0(GetName, nsString());
+  MOCK_CONST_METHOD0(GetUUID, nsCString());
+  MOCK_CONST_METHOD0(GetGroupId, nsString());
+  MOCK_METHOD6(Allocate, nsresult(const dom::MediaTrackConstraints&,
+                                  const MediaEnginePrefs&, const nsString&,
+                                  const ipc::PrincipalInfo&, AllocationHandle**,
+                                  const char**));
+  MOCK_METHOD4(SetTrack, void(const RefPtr<const AllocationHandle>&,
+                              const RefPtr<SourceMediaStream>&, TrackID,
+                              const PrincipalHandle&));
+  MOCK_METHOD1(Start, nsresult(const RefPtr<const AllocationHandle>&));
+  MOCK_METHOD5(Reconfigure, nsresult(const RefPtr<AllocationHandle>&,
+                                     const dom::MediaTrackConstraints&,
+                                     const MediaEnginePrefs&, const nsString&,
+                                     const char**));
+  MOCK_METHOD1(Stop, nsresult(const RefPtr<const AllocationHandle>&));
+  MOCK_METHOD1(Deallocate, nsresult(const RefPtr<const AllocationHandle>&));
+  MOCK_CONST_METHOD2(GetBestFitnessDistance,
+                     uint32_t(const nsTArray<const NormalizedConstraintSet*>&,
+                              const nsString&));
+  MOCK_METHOD6(Pull,
+               void(const RefPtr<const AllocationHandle>& aHandle,
+                    const RefPtr<SourceMediaStream>& aStream, TrackID aTrackID,
+                    StreamTime aEndOfAppendedData, StreamTime aDesiredTime,
+                    const PrincipalHandle& aPrincipalHandle));
+};
+
+RefPtr<AudioDeviceInfo> MakeAudioDeviceInfo(const nsString aName) {
+  return MakeRefPtr<AudioDeviceInfo>(
+      nullptr, aName, NS_LITERAL_STRING("GroupId"), NS_LITERAL_STRING("Vendor"),
+      AudioDeviceInfo::TYPE_OUTPUT, AudioDeviceInfo::STATE_ENABLED,
+      AudioDeviceInfo::PREF_NONE, AudioDeviceInfo::FMT_F32LE,
+      AudioDeviceInfo::FMT_F32LE, 2u, 44100u, 44100u, 44100u, 0, 0);
+}
+
+RefPtr<MediaDevice> MakeCameraDevice(const nsString& aName,
+                                     const nsString& aGroupId) {
+  auto v = MakeRefPtr<MockMediaEngineSource>();
+  EXPECT_CALL(*v, GetMediaSource())
+      .WillRepeatedly(Return(dom::MediaSourceEnum::Camera));
+
+  return MakeRefPtr<MediaDevice>(v, aName, NS_LITERAL_STRING(""), aGroupId,
+                                 NS_LITERAL_STRING(""));
+}
+
+RefPtr<MediaDevice> MakeMicDevice(const nsString& aName,
+                                  const nsString& aGroupId) {
+  auto a = MakeRefPtr<MockMediaEngineSource>();
+  EXPECT_CALL(*a, GetMediaSource())
+      .WillRepeatedly(Return(dom::MediaSourceEnum::Microphone));
+
+  return MakeRefPtr<MediaDevice>(a, aName, NS_LITERAL_STRING(""), aGroupId,
+                                 NS_LITERAL_STRING(""));
+}
+
+RefPtr<MediaDevice> MakeSpeakerDevice(const nsString& aName,
+                                      const nsString& aGroupId) {
+  return MakeRefPtr<MediaDevice>(MakeAudioDeviceInfo(aName),
+                                 NS_LITERAL_STRING("ID"), aGroupId,
+                                 NS_LITERAL_STRING("RawID"));
+}
+
+/* Verify that when an audio input device name contains the video input device
+ * name the video device group id is updated to become equal to the audio
+ * device group id. */
+TEST(TestGroupId, MatchInput_PartOfName) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video group id is the same as audio input group id.";
+}
+
+/* Verify that when an audio input device name is the same as the video input
+ * device name the video device group id is updated to become equal to the audio
+ * device group id. */
+TEST(TestGroupId, MatchInput_FullName) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(MakeMicDevice(NS_LITERAL_STRING("Vendor Model"),
+                                      NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video group id is the same as audio input group id.";
+}
+
+/* Verify that when an audio input device name does not contain the video input
+ * device name the video device group id does not change. */
+TEST(TestGroupId, NoMatchInput) {
+  MediaManager::MediaDeviceSet devices;
+
+  nsString Cam_Model_GroupId = NS_LITERAL_STRING("Cam-Model-GroupId");
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"), Cam_Model_GroupId));
+
+  devices.AppendElement(MakeMicDevice(NS_LITERAL_STRING("Model Analog Stereo"),
+                                      NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, Cam_Model_GroupId)
+      << "Video group id has not been updated.";
+  EXPECT_NE(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video group id is different than audio input group id.";
+}
+
+/* Verify that when more that one audio input and more than one audio output
+ * device name contain the video input device name the video device group id
+ * does not change. */
+TEST(TestGroupId, NoMatch_TwoIdenticalDevices) {
+  MediaManager::MediaDeviceSet devices;
+
+  nsString Cam_Model_GroupId = NS_LITERAL_STRING("Cam-Model-GroupId");
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"), Cam_Model_GroupId));
+
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, Cam_Model_GroupId)
+      << "Video group id has not been updated.";
+  EXPECT_NE(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video group id is different than audio input group id.";
+  EXPECT_NE(devices[0]->mGroupID, devices[3]->mGroupID)
+      << "Video group id is different than audio output group id.";
+}
+
+/* Verify that when more that one audio input device name contain the video
+ * input device name the video device group id is not updated by audio input
+ * device group id but it continues looking at audio output devices where it
+ * finds a match so video input group id is updated by audio output group id. */
+TEST(TestGroupId, Match_TwoIdenticalInputsMatchOutput) {
+  MediaManager::MediaDeviceSet devices;
+
+  nsString Cam_Model_GroupId = NS_LITERAL_STRING("Cam-Model-GroupId");
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"), Cam_Model_GroupId));
+
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[3]->mGroupID)
+      << "Video group id is the same as audio output group id.";
+}
+
+/* Verify that when more that one audio input and more than one audio output
+ * device names contain the video input device name the video device group id
+ * does not change. */
+TEST(TestGroupId, NoMatch_ThreeIdenticalDevices) {
+  MediaManager::MediaDeviceSet devices;
+
+  nsString Cam_Model_GroupId = NS_LITERAL_STRING("Cam-Model-GroupId");
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"), Cam_Model_GroupId));
+
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                    NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, Cam_Model_GroupId)
+      << "Video group id has not been updated.";
+  EXPECT_NE(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video group id is different than audio input group id.";
+  EXPECT_NE(devices[0]->mGroupID, devices[4]->mGroupID)
+      << "Video group id is different than audio output group id.";
+}
+
+/* Verify that when an audio output device name contains the video input device
+ * name the video device group id is updated to become equal to the audio
+ * device group id. */
+TEST(TestGroupId, MatchOutput) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(MakeMicDevice(NS_LITERAL_STRING("Mic Analog Stereo"),
+                                      NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model Analog Stereo"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[2]->mGroupID)
+      << "Video group id is the same as audio output group id.";
+}
+
+/* Verify that when an audio input device name is the same as audio output
+ * device and video input device name the video device group id is updated to
+ * become equal to the audio input device group id. */
+TEST(TestGroupId, InputOutputSameName) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(MakeMicDevice(NS_LITERAL_STRING("Vendor Model"),
+                                      NS_LITERAL_STRING("Mic-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model"),
+                        NS_LITERAL_STRING("Speaker-Model-GroupId")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video input group id is the same as audio input group id.";
+}
+
+/* Verify that when an audio input device name contains the video input device
+ * and the audio input group id is an empty string, the video device group id
+ * is updated to become equal to the audio device group id. */
+TEST(TestGroupId, InputEmptyGroupId) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(
+      MakeMicDevice(NS_LITERAL_STRING("Vendor Model"), NS_LITERAL_STRING("")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video input group id is the same as audio input group id.";
+}
+
+/* Verify that when an audio output device name contains the video input device
+ * and the audio output group id is an empty string, the video device group id
+ * is updated to become equal to the audio output device group id. */
+TEST(TestGroupId, OutputEmptyGroupId) {
+  MediaManager::MediaDeviceSet devices;
+
+  devices.AppendElement(
+      MakeCameraDevice(NS_LITERAL_STRING("Vendor Model"),
+                       NS_LITERAL_STRING("Cam-Model-GroupId")));
+
+  devices.AppendElement(MakeSpeakerDevice(NS_LITERAL_STRING("Vendor Model"),
+                                          NS_LITERAL_STRING("")));
+
+  MediaManager::GuessVideoDeviceGroupIDs(devices);
+
+  EXPECT_EQ(devices[0]->mGroupID, devices[1]->mGroupID)
+      << "Video input group id is the same as audio output group id.";
+}
--- a/dom/media/gtest/moz.build
+++ b/dom/media/gtest/moz.build
@@ -23,16 +23,17 @@ UNIFIED_SOURCES += [
     'TestAudioTrackEncoder.cpp',
     'TestBitWriter.cpp',
     'TestBlankVideoDataCreator.cpp',
     'TestCDMStorage.cpp',
     'TestDataMutex.cpp',
     'TestGMPCrossOrigin.cpp',
     'TestGMPRemoveAndDelete.cpp',
     'TestGMPUtils.cpp',
+    'TestGroupId.cpp',
     'TestIntervalSet.cpp',
     'TestMediaDataDecoder.cpp',
     'TestMediaDataEncoder.cpp',
     'TestMediaEventSource.cpp',
     'TestMediaMIMETypes.cpp',
     'TestMP3Demuxer.cpp',
     'TestMP4Demuxer.cpp',
     'TestOpusParser.cpp',
--- a/dom/media/nsIDOMNavigatorUserMedia.idl
+++ b/dom/media/nsIDOMNavigatorUserMedia.idl
@@ -8,16 +8,17 @@
 [scriptable, builtinclass, uuid(ba3b2e08-1c07-4cd3-8822-f4d7e35ff2ae)]
 interface nsIMediaDevice : nsISupports
 {
   readonly attribute AString type;
   readonly attribute AString name;
   readonly attribute AString id;
   readonly attribute AString mediaSource;
   readonly attribute AString rawId;
+  readonly attribute AString groupId;
   readonly attribute boolean scary;
 };
 
 [scriptable, function, uuid(24544878-d35e-4962-8c5f-fb84e97bdfee)]
 interface nsIGetUserMediaDevicesSuccessCallback : nsISupports
 {
   void onSuccess(in nsIVariant devices);
 };
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -42,16 +42,17 @@ skip-if = toolkit == 'android'  # Bug 11
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_bug1013809.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_dataOnlyBufferedAmountLow.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_noOffer.html]
 [test_enumerateDevices.html]
 [test_enumerateDevices_navigation.html]
+[test_groupId.html]
 [test_ondevicechange.html]
 skip-if = os == 'android' || verify
 [test_getUserMedia_active_autoplay.html]
 [test_getUserMedia_audioCapture.html]
 skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator), android(Bug 1264333)
 [test_getUserMedia_addTrackRemoveTrack.html]
 skip-if = android_version == '18' || os == 'linux' # android(Bug 1189784, timeouts on 4.3 emulator), linux bug 1377450
 [test_getUserMedia_addtrack_removetrack_events.html]
--- a/dom/media/tests/mochitest/test_enumerateDevices.html
+++ b/dom/media/tests/mochitest/test_enumerateDevices.html
@@ -33,19 +33,17 @@ async function mustFailWith(msg, reason,
 }
 
 var gUM = c => navigator.mediaDevices.getUserMedia(c);
 
 var validateDevice = ({kind, label, deviceId, groupId}) => {
   ok(kind == "videoinput" || kind == "audioinput", "Known device kind");
   is(deviceId.length, 44, "deviceId length id as expected for Firefox");
   ok(label.length !== undefined, "Device label: " + label);
-
-  // TODO: s/todo_// once Bug 1213453 is fixed.
-  todo_isnot(groupId, "", "groupId must be present.");
+  isnot(groupId, "", "groupId must be present.");
 }
 
 runTest(async () => {
   await pushPrefs(["media.navigator.streams.fake", true]);
 
   // Validate enumerated devices.
 
   let devices = await navigator.mediaDevices.enumerateDevices();
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_groupId.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({ title: "Test group id of MediaDeviceInfo", bug: "1213453" });
+
+let getDefaultDevices = async () => {
+  let devices = await navigator.mediaDevices.enumerateDevices();
+  is(devices.length, 2, "Two fake devices found.");
+
+  devices.forEach(d => isnot(d.groupId, "", "GroupId is included in every device"));
+
+  let videos = devices.filter(d => d.kind == "videoinput");
+  is(videos.length, 1, "One video device found.");
+  let audios = devices.filter(d => d.kind == "audioinput");
+  is(audios.length, 1, "One microphone device found.");
+
+  return {audio: audios[0], video: videos[0]};
+}
+
+runTest(async () => {
+  // Force fake devices in order to be able to change camera name by pref.
+  await pushPrefs(["media.navigator.streams.fake", true],
+                  ["media.audio_loopback_dev", ""],
+                  ["media.video_loopback_dev", ""]);
+
+  let {audio, video} = await getDefaultDevices();
+
+  /* The low level method to correlate groupIds is by device names.
+   * Use a similar comparison here to verify that it works.
+   * Multiple devices of the same device name are not expected in
+   * automation. */
+  isnot(audio.groupId, video.groupId,"Not the same groupIds");
+  // Change video name to match.
+  await pushPrefs(["media.getusermedia.fake-camera-name", audio.label]);
+  ({audio, video} = await getDefaultDevices());
+  is(audio.groupId, video.groupId, "GroupIds should be the same");
+});
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -27,33 +27,59 @@
 #  include "YuvStamper.h"
 #endif
 
 #define DEFAULT_AUDIO_TIMER_MS 10
 namespace mozilla {
 
 using namespace mozilla::gfx;
 
+static nsString DefaultVideoName() {
+  // For the purpose of testing we allow to change the name of the fake device
+  // by pref.
+  nsAutoString cameraNameFromPref;
+  nsresult rv;
+  NS_DispatchToMainThread(
+      NS_NewRunnableFunction(__func__,
+                             [&]() {
+                               rv = Preferences::GetString(
+                                   "media.getusermedia.fake-camera-name",
+                                   cameraNameFromPref);
+                             }),
+      NS_DISPATCH_SYNC);
+
+  if (NS_SUCCEEDED(rv)) {
+    return std::move(cameraNameFromPref);
+  }
+  return NS_LITERAL_STRING(u"Default Video Device");
+}
+
 /**
  * Default video source.
  */
 
 MediaEngineDefaultVideoSource::MediaEngineDefaultVideoSource()
-    : mTimer(nullptr), mMutex("MediaEngineDefaultVideoSource::mMutex") {}
+    : mTimer(nullptr),
+      mMutex("MediaEngineDefaultVideoSource::mMutex"),
+      mName(DefaultVideoName()) {}
 
 MediaEngineDefaultVideoSource::~MediaEngineDefaultVideoSource() {}
 
 nsString MediaEngineDefaultVideoSource::GetName() const {
-  return NS_LITERAL_STRING(u"Default Video Device");
+  return mName;
 }
 
 nsCString MediaEngineDefaultVideoSource::GetUUID() const {
   return NS_LITERAL_CSTRING("1041FCBD-3F12-4F7B-9E9B-1EC556DD5676");
 }
 
+nsString MediaEngineDefaultVideoSource::GetGroupId() const {
+  return NS_LITERAL_STRING(u"Default Video Group");
+}
+
 uint32_t MediaEngineDefaultVideoSource::GetBestFitnessDistance(
     const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId) const {
   AssertIsOnOwningThread();
 
   uint32_t distance = 0;
 #ifdef MOZ_WEBRTC
   for (const auto* cs : aConstraintSets) {
@@ -357,16 +383,20 @@ MediaEngineDefaultAudioSource::~MediaEng
 nsString MediaEngineDefaultAudioSource::GetName() const {
   return NS_LITERAL_STRING(u"Default Audio Device");
 }
 
 nsCString MediaEngineDefaultAudioSource::GetUUID() const {
   return NS_LITERAL_CSTRING("B7CBD7C1-53EF-42F9-8353-73F61C70C092");
 }
 
+nsString MediaEngineDefaultAudioSource::GetGroupId() const {
+  return NS_LITERAL_STRING(u"Default Audio Group");
+}
+
 uint32_t MediaEngineDefaultAudioSource::GetBestFitnessDistance(
     const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId) const {
   uint32_t distance = 0;
 #ifdef MOZ_WEBRTC
   for (const auto* cs : aConstraintSets) {
     distance =
         MediaConstraintsHelper::GetMinimumFitnessDistance(*cs, aDeviceId);
@@ -547,39 +577,41 @@ void MediaEngineDefault::EnumerateDevice
       // in Allocate().
 
       nsTArray<RefPtr<MediaEngineSource>>* devicesForThisWindow =
           mVSources.LookupOrAdd(aWindowId);
       auto newSource = MakeRefPtr<MediaEngineDefaultVideoSource>();
       devicesForThisWindow->AppendElement(newSource);
       aDevices->AppendElement(MakeRefPtr<MediaDevice>(
           newSource, newSource->GetName(),
-          NS_ConvertUTF8toUTF16(newSource->GetUUID()), NS_LITERAL_STRING("")));
+          NS_ConvertUTF8toUTF16(newSource->GetUUID()),
+          newSource->GetGroupId(), NS_LITERAL_STRING("")));
       return;
     }
     case dom::MediaSourceEnum::Microphone: {
       nsTArray<RefPtr<MediaEngineDefaultAudioSource>>* devicesForThisWindow =
           mASources.LookupOrAdd(aWindowId);
       for (const RefPtr<MediaEngineDefaultAudioSource>& source :
            *devicesForThisWindow) {
         if (source->IsAvailable()) {
           aDevices->AppendElement(MakeRefPtr<MediaDevice>(
               source, source->GetName(),
-              NS_ConvertUTF8toUTF16(source->GetUUID()), NS_LITERAL_STRING("")));
+              NS_ConvertUTF8toUTF16(source->GetUUID()),
+              source->GetGroupId(), NS_LITERAL_STRING("")));
         }
       }
 
       if (aDevices->IsEmpty()) {
         // All streams are currently busy, just make a new one.
         auto newSource = MakeRefPtr<MediaEngineDefaultAudioSource>();
         devicesForThisWindow->AppendElement(newSource);
-        aDevices->AppendElement(
-            MakeRefPtr<MediaDevice>(newSource, newSource->GetName(),
-                                    NS_ConvertUTF8toUTF16(newSource->GetUUID()),
-                                    NS_LITERAL_STRING("")));
+        aDevices->AppendElement(MakeRefPtr<MediaDevice>(
+            newSource, newSource->GetName(),
+            NS_ConvertUTF8toUTF16(newSource->GetUUID()),
+            newSource->GetGroupId(), NS_LITERAL_STRING("")));
       }
       return;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("Unsupported source type");
       return;
   }
 
--- a/dom/media/webrtc/MediaEngineDefault.h
+++ b/dom/media/webrtc/MediaEngineDefault.h
@@ -36,16 +36,17 @@ class MediaEngineDefault;
  * The default implementation of the MediaEngine interface.
  */
 class MediaEngineDefaultVideoSource : public MediaEngineSource {
  public:
   MediaEngineDefaultVideoSource();
 
   nsString GetName() const override;
   nsCString GetUUID() const override;
+  nsString GetGroupId() const override;
 
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs, const nsString& aDeviceId,
                     const ipc::PrincipalInfo& aPrincipalInfo,
                     AllocationHandle** aOutHandle,
                     const char** aOutBadConstraint) override;
   void SetTrack(const RefPtr<const AllocationHandle>& aHandle,
                 const RefPtr<SourceMediaStream>& aStream, TrackID aTrackID,
@@ -93,26 +94,30 @@ class MediaEngineDefaultVideoSource : pu
   MediaEngineSourceState mState = kReleased;
   RefPtr<layers::Image> mImage;
   RefPtr<SourceMediaStream> mStream;
   TrackID mTrackID = TRACK_NONE;
 
   MediaEnginePrefs mOpts;
   int mCb = 16;
   int mCr = 16;
+
+private:
+  const nsString mName;
 };
 
 class SineWaveGenerator;
 
 class MediaEngineDefaultAudioSource : public MediaEngineSource {
  public:
   MediaEngineDefaultAudioSource();
 
   nsString GetName() const override;
   nsCString GetUUID() const override;
+  nsString GetGroupId() const override;
 
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs, const nsString& aDeviceId,
                     const ipc::PrincipalInfo& aPrincipalInfo,
                     AllocationHandle** aOutHandle,
                     const char** aOutBadConstraint) override;
   void SetTrack(const RefPtr<const AllocationHandle>& aHandle,
                 const RefPtr<SourceMediaStream>& aStream, TrackID aTrackID,
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -81,16 +81,17 @@ void MediaEngineRemoteVideoSource::Init(
                               kMaxDeviceNameLength, uniqueId,
                               kMaxUniqueIdLength, nullptr)) {
     LOG("Error initializing RemoteVideoSource (GetCaptureDevice)");
     return;
   }
 
   SetName(NS_ConvertUTF8toUTF16(deviceName));
   SetUUID(uniqueId);
+  SetGroupId(NS_ConvertUTF8toUTF16(deviceName));
 
   mInitDone = true;
 }
 
 void MediaEngineRemoteVideoSource::Shutdown() {
   LOG(__PRETTY_FUNCTION__);
   AssertIsOnOwningThread();
 
@@ -175,16 +176,28 @@ void MediaEngineRemoteVideoSource::SetUU
 }
 
 nsCString MediaEngineRemoteVideoSource::GetUUID() const {
   AssertIsOnOwningThread();
 
   return mUniqueId;
 }
 
+void MediaEngineRemoteVideoSource::SetGroupId(nsString aGroupId) {
+  AssertIsOnOwningThread();
+
+  mGroupId = std::move(aGroupId);
+}
+
+nsString MediaEngineRemoteVideoSource::GetGroupId() const {
+  AssertIsOnOwningThread();
+
+  return mGroupId;
+}
+
 nsresult MediaEngineRemoteVideoSource::Allocate(
     const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs,
     const nsString& aDeviceId,
     const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
     AllocationHandle** aOutHandle, const char** aOutBadConstraint) {
   LOG(__PRETTY_FUNCTION__);
   AssertIsOnOwningThread();
 
@@ -987,11 +1000,12 @@ void MediaEngineRemoteVideoSource::Refre
                               mCapEngine, aIndex, deviceName,
                               sizeof(deviceName), uniqueId, sizeof(uniqueId),
                               nullptr)) {
     return;
   }
 
   SetName(NS_ConvertUTF8toUTF16(deviceName));
   MOZ_ASSERT(mUniqueId.Equals(uniqueId));
+  SetGroupId(NS_ConvertUTF8toUTF16(deviceName));
 }
 
 }  // namespace mozilla
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.h
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.h
@@ -148,16 +148,19 @@ class MediaEngineRemoteVideoSource : pub
   void Shutdown() override;
 
   nsString GetName() const override;
   void SetName(nsString aName);
 
   nsCString GetUUID() const override;
   void SetUUID(const char* aUUID);
 
+  nsString GetGroupId() const override;
+  void SetGroupId(nsString aGroupId);
+
   bool GetScary() const override { return mScary; }
 
   RefPtr<GenericNonExclusivePromise> GetFirstFramePromise() const override {
     return mFirstFramePromise;
   }
 
  private:
   // Initialize the needed Video engine interfaces.
@@ -245,16 +248,17 @@ class MediaEngineRemoteVideoSource : pub
    *
    * This is mutable so that the const method NumCapabilities() can reset it.
    * Owning thread only.
    */
   mutable nsTArray<webrtc::CaptureCapability> mHardcodedCapabilities;
 
   nsString mDeviceName;
   nsCString mUniqueId;
+  nsString mGroupId;
   nsString mFacingMode;
 
   // Whether init has successfully completed.
   // Set in Init(), reset in Shutdown().
   // Owning thread only.
   bool mInitDone = false;
 };
 
--- a/dom/media/webrtc/MediaEngineSource.h
+++ b/dom/media/webrtc/MediaEngineSource.h
@@ -100,16 +100,21 @@ class MediaEngineSourceInterface {
   virtual nsString GetName() const = 0;
 
   /**
    * Gets the UUID of this device.
    */
   virtual nsCString GetUUID() const = 0;
 
   /**
+   * Gets the Group id of this device.
+   */
+  virtual nsString GetGroupId() const = 0;
+
+  /**
    * Get the enum describing the underlying type of MediaSource.
    */
   virtual dom::MediaSourceEnum GetMediaSource() const = 0;
 
   /**
    * Override w/true if source does end-run around cross origin restrictions.
    */
   virtual bool GetScary() const = 0;
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -110,16 +110,20 @@ nsresult MediaEngineTabVideoSource::Dest
 nsString MediaEngineTabVideoSource::GetName() const {
   return NS_LITERAL_STRING(u"&getUserMedia.videoSource.tabShare;");
 }
 
 nsCString MediaEngineTabVideoSource::GetUUID() const {
   return NS_LITERAL_CSTRING("tab");
 }
 
+nsString MediaEngineTabVideoSource::GetGroupId() const {
+  return NS_LITERAL_STRING(u"&getUserMedia.videoSource.tabShareGroup;");
+}
+
 #define DEFAULT_TABSHARE_VIDEO_MAX_WIDTH 4096
 #define DEFAULT_TABSHARE_VIDEO_MAX_HEIGHT 4096
 #define DEFAULT_TABSHARE_VIDEO_FRAMERATE 30
 
 nsresult MediaEngineTabVideoSource::Allocate(
     const dom::MediaTrackConstraints& aConstraints,
     const MediaEnginePrefs& aPrefs, const nsString& aDeviceId,
     const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -16,16 +16,17 @@ class ImageContainer;
 }
 
 class MediaEngineTabVideoSource : public MediaEngineSource {
  public:
   MediaEngineTabVideoSource();
 
   nsString GetName() const override;
   nsCString GetUUID() const override;
+  nsString GetGroupId() const override;
 
   bool GetScary() const override { return true; }
 
   dom::MediaSourceEnum GetMediaSource() const override {
     return dom::MediaSourceEnum::Browser;
   }
 
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -139,25 +139,25 @@ void MediaEngineWebRTC::EnumerateVideoDe
       static_cast<MediaEngineRemoteVideoSource*>(vSource.get())->Refresh(i);
     } else {
       vSource = new MediaEngineRemoteVideoSource(i, aCapEngine,
                                                  scaryKind || scarySource);
       devicesForThisWindow->Put(uuid, vSource);
     }
     aDevices->AppendElement(MakeRefPtr<MediaDevice>(
         vSource, vSource->GetName(), NS_ConvertUTF8toUTF16(vSource->GetUUID()),
-        NS_LITERAL_STRING("")));
+        vSource->GetGroupId(), NS_LITERAL_STRING("")));
   }
 
   if (mHasTabVideoSource || aCapEngine == camera::BrowserEngine) {
     RefPtr<MediaEngineSource> tabVideoSource = new MediaEngineTabVideoSource();
     aDevices->AppendElement(MakeRefPtr<MediaDevice>(
         tabVideoSource, tabVideoSource->GetName(),
         NS_ConvertUTF8toUTF16(tabVideoSource->GetUUID()),
-        NS_LITERAL_STRING("")));
+        tabVideoSource->GetGroupId(), NS_LITERAL_STRING("")));
   }
 }
 
 void MediaEngineWebRTC::EnumerateMicrophoneDevices(
     uint64_t aWindowId, nsTArray<RefPtr<MediaDevice>>* aDevices) {
   mMutex.AssertCurrentThreadOwns();
 
   mEnumerator = CubebDeviceEnumerator::GetInstance();
@@ -176,21 +176,21 @@ void MediaEngineWebRTC::EnumerateMicroph
          NS_ConvertUTF16toUTF8(devices[i]->Name()).get(),
          devices[i]->DeviceID()));
 
     if (devices[i]->State() == CUBEB_DEVICE_STATE_ENABLED) {
       MOZ_ASSERT(devices[i]->Type() == CUBEB_DEVICE_TYPE_INPUT);
       RefPtr<MediaEngineSource> source = new MediaEngineWebRTCMicrophoneSource(
           devices[i], devices[i]->Name(),
           // Lie and provide the name as UUID
-          NS_ConvertUTF16toUTF8(devices[i]->Name()), devices[i]->MaxChannels(),
-          mDelayAgnostic, mExtendedFilter);
+          NS_ConvertUTF16toUTF8(devices[i]->Name()), devices[i]->GroupID(),
+          devices[i]->MaxChannels(), mDelayAgnostic, mExtendedFilter);
       RefPtr<MediaDevice> device = MakeRefPtr<MediaDevice>(
           source, source->GetName(), NS_ConvertUTF8toUTF16(source->GetUUID()),
-          NS_LITERAL_STRING(""));
+          source->GetGroupId(), NS_LITERAL_STRING(""));
       if (devices[i]->Preferred()) {
 #ifdef DEBUG
         if (!foundPreferredDevice) {
           foundPreferredDevice = true;
         } else {
           MOZ_ASSERT(!foundPreferredDevice,
                      "Found more than one preferred audio input device"
                      "while enumerating");
@@ -215,17 +215,18 @@ void MediaEngineWebRTC::EnumerateSpeaker
   for (auto& device : devices) {
     if (device->State() == CUBEB_DEVICE_STATE_ENABLED) {
       MOZ_ASSERT(device->Type() == CUBEB_DEVICE_TYPE_OUTPUT);
       nsString uuid(device->Name());
       // If, for example, input and output are in the same device, uuid
       // would be the same for both which ends up to create the same
       // deviceIDs (in JS).
       uuid.Append(NS_LITERAL_STRING("_Speaker"));
-      aDevices->AppendElement(MakeRefPtr<MediaDevice>(device, uuid));
+      nsString groupId(device->GroupID());
+      aDevices->AppendElement(MakeRefPtr<MediaDevice>(device, uuid, groupId));
     }
   }
 }
 
 void MediaEngineWebRTC::EnumerateDevices(
     uint64_t aWindowId, dom::MediaSourceEnum aMediaSource,
     MediaSinkEnum aMediaSink, nsTArray<RefPtr<MediaDevice>>* aDevices) {
   MOZ_ASSERT(aMediaSource != dom::MediaSourceEnum::Other ||
@@ -258,17 +259,17 @@ void MediaEngineWebRTC::EnumerateDevices
         break;
     }
   } else if (aMediaSource == dom::MediaSourceEnum::AudioCapture) {
     RefPtr<MediaEngineWebRTCAudioCaptureSource> audioCaptureSource =
         new MediaEngineWebRTCAudioCaptureSource(nullptr);
     aDevices->AppendElement(MakeRefPtr<MediaDevice>(
         audioCaptureSource, audioCaptureSource->GetName(),
         NS_ConvertUTF8toUTF16(audioCaptureSource->GetUUID()),
-        NS_LITERAL_STRING("")));
+        audioCaptureSource->GetGroupId(), NS_LITERAL_STRING("")));
   } else if (aMediaSource == dom::MediaSourceEnum::Microphone) {
     MOZ_ASSERT(aMediaSource == dom::MediaSourceEnum::Microphone);
     EnumerateMicrophoneDevices(aWindowId, aDevices);
   }
 
   if (aMediaSink == MediaSinkEnum::Speaker) {
     EnumerateSpeakerDevices(aWindowId, aDevices);
   }
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -41,25 +41,26 @@ extern LazyLogModule gMediaManagerLog;
 #define LOG_ERROR(...) MOZ_LOG(gMediaManagerLog, LogLevel::Error, (__VA_ARGS__))
 
 /**
  * WebRTC Microphone MediaEngineSource.
  */
 
 MediaEngineWebRTCMicrophoneSource::MediaEngineWebRTCMicrophoneSource(
     RefPtr<AudioDeviceInfo> aInfo, const nsString& aDeviceName,
-    const nsCString& aDeviceUUID, uint32_t aMaxChannelCount,
-    bool aDelayAgnostic, bool aExtendedFilter)
+    const nsCString& aDeviceUUID, const nsString& aDeviceGroup,
+    uint32_t aMaxChannelCount, bool aDelayAgnostic, bool aExtendedFilter)
     : mTrackID(TRACK_NONE),
       mPrincipal(PRINCIPAL_HANDLE_NONE),
       mDeviceInfo(std::move(aInfo)),
       mDelayAgnostic(aDelayAgnostic),
       mExtendedFilter(aExtendedFilter),
       mDeviceName(aDeviceName),
       mDeviceUUID(aDeviceUUID),
+      mDeviceGroup(aDeviceGroup),
       mDeviceMaxChannelCount(aMaxChannelCount),
       mSettings(new nsMainThreadPtrHolder<
                 media::Refcountable<dom::MediaTrackSettings>>(
           "MediaEngineWebRTCMicrophoneSource::mSettings",
           new media::Refcountable<dom::MediaTrackSettings>(),
           // Non-strict means it won't assert main thread for us.
           // It would be great if it did but we're already on the media thread.
           /* aStrict = */ false)) {
@@ -79,16 +80,20 @@ MediaEngineWebRTCMicrophoneSource::Media
 nsString MediaEngineWebRTCMicrophoneSource::GetName() const {
   return mDeviceName;
 }
 
 nsCString MediaEngineWebRTCMicrophoneSource::GetUUID() const {
   return mDeviceUUID;
 }
 
+nsString MediaEngineWebRTCMicrophoneSource::GetGroupId() const {
+  return mDeviceGroup;
+}
+
 // GetBestFitnessDistance returns the best distance the capture device can offer
 // as a whole, given an accumulated number of ConstraintSets.
 // Ideal values are considered in the first ConstraintSet only.
 // Plain values are treated as Ideal in the first ConstraintSet.
 // Plain values are treated as Exact in subsequent ConstraintSets.
 // Infinity = UINT32_MAX e.g. device cannot satisfy accumulated ConstraintSets.
 // A finite result may be used to calculate this device's ranking as a choice.
 
@@ -1158,16 +1163,20 @@ nsCString MediaEngineWebRTCAudioCaptureS
 
   uuid.ToProvidedString(uuidBuffer);
   asciiString.AssignASCII(uuidBuffer);
 
   // Remove {} and the null terminator
   return nsCString(Substring(asciiString, 1, NSID_LENGTH - 3));
 }
 
+nsString MediaEngineWebRTCAudioCaptureSource::GetGroupId() const {
+  return NS_LITERAL_STRING(u"AudioCaptureGroup");
+}
+
 void MediaEngineWebRTCAudioCaptureSource::SetTrack(
     const RefPtr<const AllocationHandle>&,
     const RefPtr<SourceMediaStream>& aStream, TrackID aTrackID,
     const PrincipalHandle& aPrincipalHandle) {
   AssertIsOnOwningThread();
   // Nothing to do here. aStream is a placeholder dummy and not exposed.
 }
 
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.h
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.h
@@ -23,24 +23,25 @@ class AudioInputProcessing;
 //   the main thread and then the MSG thread so that it can be used as part of
 //   the graph processing. On destruction, similarly, a message is sent to the
 //   graph so that it stops using it, and then it is deleted.
 // - mSettings is created on the MediaManager thread is always ever accessed on
 //   the Main Thread. It is const.
 class MediaEngineWebRTCMicrophoneSource : public MediaEngineSource {
  public:
   MediaEngineWebRTCMicrophoneSource(RefPtr<AudioDeviceInfo> aInfo,
-                                    const nsString& name, const nsCString& uuid,
-                                    uint32_t maxChannelCount,
+                                    const nsString& aName, const nsCString& aUuid,
+                                    const nsString& aGroupId, uint32_t aMaxChannelCount,
                                     bool aDelayAgnostic, bool aExtendedFilter);
 
   bool RequiresSharing() const override { return false; }
 
   nsString GetName() const override;
   nsCString GetUUID() const override;
+  nsString GetGroupId() const override;
 
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs, const nsString& aDeviceId,
                     const ipc::PrincipalInfo& aPrincipalInfo,
                     AllocationHandle** aOutHandle,
                     const char** aOutBadConstraint) override;
   nsresult Deallocate(const RefPtr<const AllocationHandle>& aHandle) override;
   void SetTrack(const RefPtr<const AllocationHandle>& aHandle,
@@ -112,16 +113,17 @@ class MediaEngineWebRTCMicrophoneSource 
   TrackID mTrackID = TRACK_NONE;
   PrincipalHandle mPrincipal = PRINCIPAL_HANDLE_NONE;
 
   const RefPtr<AudioDeviceInfo> mDeviceInfo;
   const bool mDelayAgnostic;
   const bool mExtendedFilter;
   const nsString mDeviceName;
   const nsCString mDeviceUUID;
+  const nsString mDeviceGroup;
 
   // The maximum number of channels that this device supports.
   const uint32_t mDeviceMaxChannelCount;
   // The current settings for the underlying device.
   // Constructed on the MediaManager thread, and then only ever accessed on the
   // main thread.
   const nsMainThreadPtrHandle<media::Refcountable<dom::MediaTrackSettings>>
       mSettings;
@@ -250,16 +252,17 @@ class AudioInputProcessing : public Audi
   bool mEnded;
 };
 
 class MediaEngineWebRTCAudioCaptureSource : public MediaEngineSource {
  public:
   explicit MediaEngineWebRTCAudioCaptureSource(const char* aUuid) {}
   nsString GetName() const override;
   nsCString GetUUID() const override;
+  nsString GetGroupId() const override;
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs, const nsString& aDeviceId,
                     const ipc::PrincipalInfo& aPrincipalInfo,
                     AllocationHandle** aOutHandle,
                     const char** aOutBadConstraint) override {
     // Nothing to do here, everything is managed in MediaManager.cpp
     *aOutHandle = nullptr;
     return NS_OK;
--- a/dom/media/webrtc/MediaTrackConstraints.cpp
+++ b/dom/media/webrtc/MediaTrackConstraints.cpp
@@ -512,19 +512,19 @@ uint32_t MediaConstraintsHelper::Fitness
   return "";
 }
 
 /* static */ const char* MediaConstraintsHelper::FindBadConstraint(
     const NormalizedConstraints& aConstraints,
     const RefPtr<MediaEngineSource>& aMediaEngineSource,
     const nsString& aDeviceId) {
   AutoTArray<RefPtr<MediaDevice>, 1> devices;
-  devices.AppendElement(
-      MakeRefPtr<MediaDevice>(aMediaEngineSource, aMediaEngineSource->GetName(),
-                              aDeviceId, NS_LITERAL_STRING("")));
+  devices.AppendElement(MakeRefPtr<MediaDevice>(
+      aMediaEngineSource, aMediaEngineSource->GetName(), aDeviceId,
+      aMediaEngineSource->GetGroupId(), NS_LITERAL_STRING("")));
   return FindBadConstraint(aConstraints, devices);
 }
 
 static void LogConstraintStringRange(
     const NormalizedConstraintSet::StringRange& aRange) {
   if (aRange.mExact.size() <= 1 && aRange.mIdeal.size() <= 1) {
     LOG("  %s: { exact: [%s], ideal: [%s] }", aRange.mName,
         (aRange.mExact.size()
--- a/dom/svg/SVGViewportElement.cpp
+++ b/dom/svg/SVGViewportElement.cpp
@@ -200,29 +200,42 @@ gfx::Matrix SVGViewportElement::GetViewB
 //----------------------------------------------------------------------
 // SVGViewportElement
 
 float SVGViewportElement::GetLength(uint8_t aCtxType) {
   const SVGViewBoxRect* viewbox = GetViewBoxInternal().HasRect()
                                       ? &GetViewBoxInternal().GetAnimValue()
                                       : nullptr;
 
-  float h, w;
+  float h = 0.0f, w = 0.0f;
+  bool shouldComputeWidth =
+           (aCtxType == SVGContentUtils::X || aCtxType == SVGContentUtils::XY),
+       shouldComputeHeight =
+           (aCtxType == SVGContentUtils::Y || aCtxType == SVGContentUtils::XY);
+
   if (viewbox) {
     w = viewbox->width;
     h = viewbox->height;
   } else if (IsInner()) {
     SVGViewportElement* ctx = GetCtx();
-    w = mLengthAttributes[ATTR_WIDTH].GetAnimValue(ctx);
-    h = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(ctx);
+    if (shouldComputeWidth) {
+      w = mLengthAttributes[ATTR_WIDTH].GetAnimValue(ctx);
+    }
+    if (shouldComputeHeight) {
+      h = mLengthAttributes[ATTR_HEIGHT].GetAnimValue(ctx);
+    }
   } else if (ShouldSynthesizeViewBox()) {
-    w = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
-                                           mViewportWidth, this);
-    h = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
-                                           mViewportHeight, this);
+    if (shouldComputeWidth) {
+      w = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_WIDTH],
+                                             mViewportWidth, this);
+    }
+    if (shouldComputeHeight) {
+      h = ComputeSynthesizedViewBoxDimension(mLengthAttributes[ATTR_HEIGHT],
+                                             mViewportHeight, this);
+    }
   } else {
     w = mViewportWidth;
     h = mViewportHeight;
   }
 
   w = std::max(w, 0.0f);
   h = std::max(h, 0.0f);
 
--- a/dom/svg/crashtests/crashtests.list
+++ b/dom/svg/crashtests/crashtests.list
@@ -86,8 +86,9 @@ load 1347617-2.svg
 load 1347617-3.svg
 load 1402798.html
 load 1419250-1.html
 load 1420492.html
 load 1477853.html
 load 1486488.html
 load 1493447.html
 skip-if(Android) load 1507961-1.html  # times out on Android due to the test size
+load test_nested_svg.html
new file mode 100644
--- /dev/null
+++ b/dom/svg/crashtests/test_nested_svg.html
@@ -0,0 +1,63 @@
+<!-- if not handled properly, this file will cause 100% CPU for exponentially long time -->
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
+<svg>
--- a/gfx/wr/ci-scripts/docker-image/setup.sh
+++ b/gfx/wr/ci-scripts/docker-image/setup.sh
@@ -18,16 +18,17 @@ apt-get install -y \
     cmake \
     curl \
     gcc \
     git \
     g++ \
     libfontconfig1-dev \
     libgl1-mesa-dev \
     libx11-dev \
+    openjdk-8-jdk \
     pkg-config \
     python \
     python-mako \
     python-pip \
     python-setuptools \
     python-voluptuous \
     python-yaml \
     software-properties-common
--- a/gfx/wr/wrench/Cargo.toml
+++ b/gfx/wr/wrench/Cargo.toml
@@ -45,17 +45,17 @@ mozangle = {version = "0.1.5", features 
 [target.'cfg(all(unix, not(target_os = "android")))'.dependencies]
 font-loader = "0.7"
 
 # Configuration information used when building wrench as an APK.
 [package.metadata.android]
 package_name = "org.mozilla.wrench"
 label = "Wrench"
 assets = "reftests"
-android_version = 26
+android_version = 28
 target_sdk_version = 18
 min_sdk_version = 18
 fullscreen = true
 build_targets = [ "armv7-linux-androideabi" ]
 opengles_version_major = 3
 opengles_version_minor = 0
 [package.metadata.android.application_attributes]
 "android:hardwareAccelerated" = "true"
--- a/gfx/wr/wrench/src/wrench.rs
+++ b/gfx/wr/wrench/src/wrench.rs
@@ -88,19 +88,19 @@ impl Notifier {
                     }
                 }
                 _ => {
                     println!("Notified of frame, but no frame was ready?");
                 }
             }
         }
 
-        if let Some(ref elp) = data.events_loop_proxy {
+        if let Some(ref _elp) = data.events_loop_proxy {
             #[cfg(not(target_os = "android"))]
-            let _ = elp.wakeup();
+            let _ = _elp.wakeup();
         }
     }
 }
 
 impl RenderNotifier for Notifier {
     fn clone(&self) -> Box<RenderNotifier> {
         Box::new(Notifier(self.0.clone()))
     }
@@ -222,19 +222,19 @@ impl Wrench {
             chase_primitive,
             enable_picture_caching: true,
             testing: true,
             max_texture_size: Some(8196), // Needed for rawtest::test_resize_image.
             ..Default::default()
         };
 
         // put an Awakened event into the queue to kick off the first frame
-        if let Some(ref elp) = proxy {
+        if let Some(ref _elp) = proxy {
             #[cfg(not(target_os = "android"))]
-            let _ = elp.wakeup();
+            let _ = _elp.wakeup();
         }
 
         let (timing_sender, timing_receiver) = chase_lev::deque();
         let notifier = notifier.unwrap_or_else(|| {
             let data = Arc::new(Mutex::new(NotifierData::new(proxy, timing_receiver, verbose)));
             Box::new(Notifier(data))
         });
 
@@ -445,17 +445,17 @@ impl Wrench {
             .build();
         let (font, index) = system_fonts::get(&property).unwrap();
         self.font_key_from_bytes(font, index as u32)
     }
 
     #[cfg(target_os = "android")]
     pub fn font_key_from_properties(
         &mut self,
-        family: &str,
+        _family: &str,
         _weight: u32,
         _style: u32,
         _stretch: u32,
     ) -> FontKey {
         unimplemented!()
     }
 
     #[cfg(all(unix, not(target_os = "android")))]
@@ -463,17 +463,17 @@ impl Wrench {
         let property = system_fonts::FontPropertyBuilder::new()
             .family(font_name)
             .build();
         let (font, index) = system_fonts::get(&property).unwrap();
         self.font_key_from_bytes(font, index as u32)
     }
 
     #[cfg(target_os = "android")]
-    pub fn font_key_from_name(&mut self, font_name: &str) -> FontKey {
+    pub fn font_key_from_name(&mut self, _font_name: &str) -> FontKey {
         unimplemented!()
     }
 
     pub fn font_key_from_bytes(&mut self, bytes: Vec<u8>, index: u32) -> FontKey {
         let key = self.api.generate_font_key();
         let mut txn = Transaction::new();
         txn.add_raw_font(key, bytes, index);
         self.api.update_resources(txn.resource_updates);
--- a/js/src/builtin/Array.cpp
+++ b/js/src/builtin/Array.cpp
@@ -3622,18 +3622,18 @@ static bool ArraySliceDenseKernel(JSCont
   }
 
   MOZ_ASSERT(count >= result->length());
   result->setLength(cx, count);
 
   return true;
 }
 
-JSObject* js::array_slice_dense(JSContext* cx, HandleObject obj, int32_t begin,
-                                int32_t end, HandleObject result) {
+JSObject* js::ArraySliceDense(JSContext* cx, HandleObject obj, int32_t begin,
+                              int32_t end, HandleObject result) {
   if (result && IsArraySpecies(cx, obj)) {
     if (!ArraySliceDenseKernel(cx, &obj->as<ArrayObject>(), begin, end,
                                &result->as<ArrayObject>())) {
       return nullptr;
     }
     return result;
   }
 
@@ -4320,16 +4320,29 @@ ArrayObject* js::NewCopiedArrayForCallin
   RootedObjectGroup group(
       cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array, proto));
   if (!group) {
     return nullptr;
   }
   return NewCopiedArrayTryUseGroup(cx, group, vp, length);
 }
 
+ArrayObject* js::NewArrayWithGroup(JSContext* cx, uint32_t length,
+                                   HandleObjectGroup group,
+                                   bool convertDoubleElements) {
+  ArrayObject* res = NewFullyAllocatedArrayTryUseGroup(cx, group, length);
+  if (!res) {
+    return nullptr;
+  }
+  if (convertDoubleElements) {
+    res->setShouldConvertDoubleElements();
+  }
+  return res;
+}
+
 #ifdef DEBUG
 bool js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   RootedObject obj(cx);
 
   for (unsigned i = 0; i < args.length(); i++) {
     HandleValue arg = args[i];
 
--- a/js/src/builtin/Array.h
+++ b/js/src/builtin/Array.h
@@ -107,16 +107,20 @@ extern ArrayObject* NewCopiedArrayTryUse
     JSContext* cx, HandleObjectGroup group, const Value* vp, size_t length,
     NewObjectKind newKind = GenericObject,
     ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
 
 extern ArrayObject* NewCopiedArrayForCallingAllocationSite(
     JSContext* cx, const Value* vp, size_t length,
     HandleObject proto = nullptr);
 
+extern ArrayObject* NewArrayWithGroup(JSContext* cx, uint32_t length,
+                                      HandleObjectGroup group,
+                                      bool convertDoubleElements);
+
 extern bool GetLengthProperty(JSContext* cx, HandleObject obj,
                               uint32_t* lengthp);
 
 extern bool SetLengthProperty(JSContext* cx, HandleObject obj, uint32_t length);
 
 /*
  * Copy 'length' elements from aobj to vp.
  *
@@ -140,19 +144,18 @@ extern bool array_join(JSContext* cx, un
 extern void ArrayShiftMoveElements(NativeObject* obj);
 
 extern bool array_shift(JSContext* cx, unsigned argc, js::Value* vp);
 
 extern bool array_unshift(JSContext* cx, unsigned argc, js::Value* vp);
 
 extern bool array_slice(JSContext* cx, unsigned argc, js::Value* vp);
 
-extern JSObject* array_slice_dense(JSContext* cx, HandleObject obj,
-                                   int32_t begin, int32_t end,
-                                   HandleObject result);
+extern JSObject* ArraySliceDense(JSContext* cx, HandleObject obj, int32_t begin,
+                                 int32_t end, HandleObject result);
 
 extern bool array_reverse(JSContext* cx, unsigned argc, js::Value* vp);
 
 extern bool array_splice(JSContext* cx, unsigned argc, js::Value* vp);
 
 extern const JSJitInfo array_splice_info;
 
 /*
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -2525,19 +2525,19 @@ bool ASTSerializer::classMethod(ClassMet
          expression(&classMethod->method(), &val) &&
          builder.classMethod(key, val, kind, isStatic, &classMethod->pn_pos,
                              dst);
 }
 
 bool ASTSerializer::classField(ClassField* classField, MutableHandleValue dst) {
   RootedValue key(cx), val(cx);
   // Dig through the lambda and get to the actual expression
-  if (classField->hasInitializer()) {
+  if (classField->initializer()) {
     ParseNode* value = classField->initializer()
-                           .body()
+                           ->body()
                            ->head()
                            ->as<LexicalScopeNode>()
                            .scopeBody()
                            ->as<ListNode>()
                            .head()
                            ->as<UnaryNode>()
                            .kid()
                            ->as<AssignmentNode>()
--- a/js/src/builtin/String.cpp
+++ b/js/src/builtin/String.cpp
@@ -2905,17 +2905,17 @@ static bool StrFlatReplaceGlobal(JSConte
     return false;
   }
 
   return true;
 }
 
 // This is identical to "str.split(pattern).join(replacement)" except that we
 // do some deforestation optimization in Ion.
-JSString* js::str_flat_replace_string(JSContext* cx, HandleString string,
+JSString* js::StringFlatReplaceString(JSContext* cx, HandleString string,
                                       HandleString pattern,
                                       HandleString replacement) {
   MOZ_ASSERT(string);
   MOZ_ASSERT(pattern);
   MOZ_ASSERT(replacement);
 
   if (!string->length()) {
     return string;
@@ -3272,19 +3272,19 @@ static ArrayObject* SplitSingleCharHelpe
                                  ch, group);
   }
 
   return SplitSingleCharHelper(cx, str, linearChars.twoByteChars(), strLength,
                                ch, group);
 }
 
 // ES 2016 draft Mar 25, 2016 21.1.3.17 steps 4, 8, 12-18.
-ArrayObject* js::str_split_string(JSContext* cx, HandleObjectGroup group,
-                                  HandleString str, HandleString sep,
-                                  uint32_t limit) {
+ArrayObject* js::StringSplitString(JSContext* cx, HandleObjectGroup group,
+                                   HandleString str, HandleString sep,
+                                   uint32_t limit) {
   MOZ_ASSERT(limit > 0, "Only called for strictly positive limit.");
 
   RootedLinearString linearStr(cx, str->ensureLinear(cx));
   if (!linearStr) {
     return nullptr;
   }
 
   RootedLinearString linearSep(cx, sep->ensureLinear(cx));
--- a/js/src/builtin/String.h
+++ b/js/src/builtin/String.h
@@ -132,21 +132,21 @@ extern bool str_normalize(JSContext* cx,
 // JSLocaleCallbacks) when Intl functionality is not exposed.
 
 extern bool str_localeCompare(JSContext* cx, unsigned argc, Value* vp);
 
 #endif  // EXPOSE_INTL_API
 
 extern bool str_concat(JSContext* cx, unsigned argc, Value* vp);
 
-ArrayObject* str_split_string(JSContext* cx, HandleObjectGroup group,
-                              HandleString str, HandleString sep,
-                              uint32_t limit);
+ArrayObject* StringSplitString(JSContext* cx, HandleObjectGroup group,
+                               HandleString str, HandleString sep,
+                               uint32_t limit);
 
-JSString* str_flat_replace_string(JSContext* cx, HandleString string,
+JSString* StringFlatReplaceString(JSContext* cx, HandleString string,
                                   HandleString pattern,
                                   HandleString replacement);
 
 JSString* str_replace_string_raw(JSContext* cx, HandleString string,
                                  HandleString pattern,
                                  HandleString replacement);
 
 extern JSString* StringToLowerCase(JSContext* cx, HandleString string);
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2447,19 +2447,18 @@ bool BytecodeEmitter::emitScript(ParseNo
   }
 
   tellDebuggerAboutCompiledScript(cx);
 
   return true;
 }
 
 bool BytecodeEmitter::emitInitializeInstanceFields() {
-  FieldInitializers fieldInfo = this->fieldInitializers_;
-  MOZ_ASSERT(fieldInfo.valid);
-  size_t numFields = fieldInfo.numFieldInitializers;
+  MOZ_ASSERT(fieldInitializers_.valid);
+  size_t numFields = fieldInitializers_.numFieldInitializers;
 
   if (numFields == 0) {
     return true;
   }
 
   if (!emitGetName(cx->names().dotInitializers)) {
     //              [stack] ARRAY
     return false;
@@ -7736,27 +7735,21 @@ bool BytecodeEmitter::emitConditionalExp
 
   return true;
 }
 
 bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe,
                                        PropListType type) {
   //                [stack] CTOR? OBJ
 
-  size_t numFields = 0;
   for (ParseNode* propdef : obj->contents()) {
     if (propdef->is<ClassField>()) {
       // Skip over class fields and emit them at the end.  This is needed
       // because they're all emitted into a single array, which is then stored
-      // into a private slot.
-      FunctionNode* initializer = &propdef->as<ClassField>().initializer();
-      // Don't include fields without initializers.
-      if (initializer != nullptr) {
-        numFields++;
-      }
+      // into a local variable.
       continue;
     }
 
     // Handle __proto__: v specially because *only* this form, and no other
     // involving "__proto__", performs [[Prototype]] mutation.
     if (propdef->isKind(ParseNodeKind::MutateProto)) {
       //            [stack] OBJ
       MOZ_ASSERT(type == ObjectLiteral);
@@ -7998,64 +7991,96 @@ bool BytecodeEmitter::emitPropertyList(L
           return false;
         }
         break;
       default:
         MOZ_CRASH("Invalid op");
     }
   }
 
-  if (numFields > 0) {
-    // .initializers is a variable that stores an array of lambdas containing
-    // code (the initializer) for each field. Upon an object's construction,
-    // these lambdas will be called, defining the values.
-
-    NameOpEmitter noe(this, cx->names().dotInitializers,
-                      NameOpEmitter::Kind::Initialize);
-    if (!noe.prepareForRhs()) {
-      return false;
-    }
-
-    if (!emitUint32Operand(JSOP_NEWARRAY, numFields)) {
-      //            [stack] CTOR? OBJ ARRAY
-      return false;
-    }
-
-    size_t curFieldIndex = 0;
-    for (ParseNode* propdef : obj->contents()) {
-      if (propdef->is<ClassField>()) {
-        FunctionNode* initializer = &propdef->as<ClassField>().initializer();
-        if (initializer == nullptr) {
-          continue;
-        }
-
-        if (!emitTree(initializer)) {
-          //        [stack] CTOR? OBJ ARRAY LAMBDA
-          return false;
-        }
-
-        if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldIndex)) {
-          //        [stack] CTOR? OBJ ARRAY
-          return false;
-        }
-
-        curFieldIndex++;
-      }
-    }
-
-    if (!noe.emitAssignment()) {
-      //            [stack] CTOR? OBJ ARRAY
-      return false;
-    }
-
-    if (!emit1(JSOP_POP)) {
-      //            [stack] CTOR? OBJ
-      return false;
-    }
-  }
+  if (obj->getKind() == ParseNodeKind::ClassMemberList) {
+    if (!emitCreateFieldInitializers(obj)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+FieldInitializers BytecodeEmitter::setupFieldInitializers(
+    ListNode* classMembers) {
+  size_t numFields = 0;
+  for (ParseNode* propdef : classMembers->contents()) {
+    if (propdef->is<ClassField>()) {
+      FunctionNode* initializer = propdef->as<ClassField>().initializer();
+      // Don't include fields without initializers.
+      if (initializer != nullptr) {
+        numFields++;
+      }
+    }
+  }
+  return FieldInitializers(numFields);
+}
+
+bool BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj) {
+  const FieldInitializers& fieldInitializers = fieldInitializers_;
+  MOZ_ASSERT(fieldInitializers.valid);
+  size_t numFields = fieldInitializers.numFieldInitializers;
+
+  if (numFields == 0) {
+    return true;
+  }
+
+  // .initializers is a variable that stores an array of lambdas containing
+  // code (the initializer) for each field. Upon an object's construction,
+  // these lambdas will be called, defining the values.
+
+  NameOpEmitter noe(this, cx->names().dotInitializers,
+                    NameOpEmitter::Kind::Initialize);
+  if (!noe.prepareForRhs()) {
+    return false;
+  }
+
+  if (!emitUint32Operand(JSOP_NEWARRAY, numFields)) {
+    //            [stack] CTOR? OBJ ARRAY
+    return false;
+  }
+
+  size_t curFieldIndex = 0;
+  for (ParseNode* propdef : obj->contents()) {
+    if (propdef->is<ClassField>()) {
+      FunctionNode* initializer = propdef->as<ClassField>().initializer();
+      if (initializer == nullptr) {
+        continue;
+      }
+
+      if (!emitTree(initializer)) {
+        //        [stack] CTOR? OBJ ARRAY LAMBDA
+        return false;
+      }
+
+      if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldIndex)) {
+        //        [stack] CTOR? OBJ ARRAY
+        return false;
+      }
+
+      curFieldIndex++;
+    }
+  }
+
+  if (!noe.emitAssignment()) {
+    //            [stack] CTOR? OBJ ARRAY
+    return false;
+  }
+
+  if (!emit1(JSOP_POP)) {
+    //            [stack] CTOR? OBJ
+    return false;
+  }
+
   return true;
 }
 
 // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
 // the comment on emitSwitch.
 MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ListNode* objNode) {
   if (!objNode->hasNonConstInitializer() && objNode->head() &&
       checkSingletonContext()) {
@@ -8815,28 +8840,18 @@ bool BytecodeEmitter::emitClass(
   MOZ_ASSERT((nameKind == ClassNameKind::InferredName) ==
              (nameForAnonymousClass != nullptr));
 
   ParseNode* heritageExpression = classNode->heritage();
   ListNode* classMembers = classNode->memberList();
   FunctionNode* constructor = FindConstructor(cx, classMembers);
 
   // set this->fieldInitializers_
-  size_t numFields = 0;
-  for (ParseNode* propdef : classMembers->contents()) {
-    if (propdef->is<ClassField>()) {
-      FunctionNode* initializer = &propdef->as<ClassField>().initializer();
-      // Don't include fields without initializers.
-      if (initializer != nullptr) {
-        numFields++;
-      }
-    }
-  }
-  FieldInitializers fieldInfo(numFields);
-  AutoResetFieldInitializers _innermostClassAutoReset(this, fieldInfo);
+  AutoResetFieldInitializers _innermostClassAutoReset(
+      this, setupFieldInitializers(classMembers));
 
   // If |nameKind != ClassNameKind::ComputedName|
   //                [stack]
   // Else
   //                [stack] NAME
 
   ClassEmitter ce(this);
   RootedAtom innerName(cx);
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -600,16 +600,19 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   MOZ_MUST_USE bool replaceNewInitWithNewObject(JSObject* obj,
                                                 ptrdiff_t offset);
 
   MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList);
 
   MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe,
                                      PropListType type);
 
+  FieldInitializers setupFieldInitializers(ListNode* classMembers);
+  MOZ_MUST_USE bool emitCreateFieldInitializers(ListNode* obj);
+
   // To catch accidental misuse, emitUint16Operand/emit3 assert that they are
   // not used to unconditionally emit JSOP_GETLOCAL. Variable access should
   // instead be emitted using EmitVarOp. In special cases, when the caller
   // definitely knows that a given local slot is unaliased, this function may be
   // used as a non-asserting version of emitUint16Operand.
   MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot);
 
   MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot);
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -1960,19 +1960,19 @@ class ClassField : public BinaryNode {
   static bool test(const ParseNode& node) {
     bool match = node.isKind(ParseNodeKind::ClassField);
     MOZ_ASSERT_IF(match, node.is<BinaryNode>());
     return match;
   }
 
   ParseNode& name() const { return *left(); }
 
-  bool hasInitializer() const { return right() != nullptr; }
-
-  FunctionNode& initializer() const { return right()->as<FunctionNode>(); }
+  FunctionNode* initializer() const {
+    return right() ? &right()->as<FunctionNode>() : nullptr;
+  }
 };
 
 class SwitchStatement : public BinaryNode {
   bool hasDefault_; /* only for ParseNodeKind::Switch */
 
  public:
   SwitchStatement(uint32_t begin, ParseNode* discriminant,
                   LexicalScopeNode* lexicalForCaseList, bool hasDefault)
--- a/js/src/jit/BaselineFrame-inl.h
+++ b/js/src/jit/BaselineFrame-inl.h
@@ -38,17 +38,17 @@ inline void BaselineFrame::replaceInnerm
   MOZ_ASSERT(env.enclosingEnvironment() ==
              envChain_->as<EnvironmentObject>().enclosingEnvironment());
   envChain_ = &env;
 }
 
 inline bool BaselineFrame::pushLexicalEnvironment(JSContext* cx,
                                                   Handle<LexicalScope*> scope) {
   LexicalEnvironmentObject* env =
-      LexicalEnvironmentObject::create(cx, scope, this);
+      LexicalEnvironmentObject::createForFrame(cx, scope, this);
   if (!env) {
     return false;
   }
   pushOnEnvironmentChain(*env);
 
   return true;
 }
 
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -2744,23 +2744,16 @@ class OutOfLineRegExpMatcher : public Ou
 
   void accept(CodeGenerator* codegen) override {
     codegen->visitOutOfLineRegExpMatcher(this);
   }
 
   LRegExpMatcher* lir() const { return lir_; }
 };
 
-typedef bool (*RegExpMatcherRawFn)(JSContext* cx, HandleObject regexp,
-                                   HandleString input, int32_t lastIndex,
-                                   MatchPairs* pairs,
-                                   MutableHandleValue output);
-static const VMFunction RegExpMatcherRawInfo =
-    FunctionInfo<RegExpMatcherRawFn>(RegExpMatcherRaw, "RegExpMatcherRaw");
-
 void CodeGenerator::visitOutOfLineRegExpMatcher(OutOfLineRegExpMatcher* ool) {
   LRegExpMatcher* lir = ool->lir();
   Register lastIndex = ToRegister(lir->lastIndex());
   Register input = ToRegister(lir->string());
   Register regexp = ToRegister(lir->regexp());
 
   AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
   regs.take(lastIndex);
@@ -2773,17 +2766,20 @@ void CodeGenerator::visitOutOfLineRegExp
 
   pushArg(temp);
   pushArg(lastIndex);
   pushArg(input);
   pushArg(regexp);
 
   // We are not using oolCallVM because we are in a Call, and that live
   // registers are already saved by the the register allocator.
-  callVM(RegExpMatcherRawInfo, lir);
+  using Fn = bool (*)(JSContext * cx, HandleObject regexp, HandleString input,
+                      int32_t lastIndex, MatchPairs * pairs,
+                      MutableHandleValue output);
+  callVM<Fn, RegExpMatcherRaw>(lir);
 
   masm.jump(ool->rejoin());
 }
 
 void CodeGenerator::visitRegExpMatcher(LRegExpMatcher* lir) {
   MOZ_ASSERT(ToRegister(lir->regexp()) == RegExpMatcherRegExpReg);
   MOZ_ASSERT(ToRegister(lir->string()) == RegExpMatcherStringReg);
   MOZ_ASSERT(ToRegister(lir->lastIndex()) == RegExpMatcherLastIndexReg);
@@ -2929,22 +2925,16 @@ class OutOfLineRegExpSearcher : public O
 
   void accept(CodeGenerator* codegen) override {
     codegen->visitOutOfLineRegExpSearcher(this);
   }
 
   LRegExpSearcher* lir() const { return lir_; }
 };
 
-typedef bool (*RegExpSearcherRawFn)(JSContext* cx, HandleObject regexp,
-                                    HandleString input, int32_t lastIndex,
-                                    MatchPairs* pairs, int32_t* result);
-static const VMFunction RegExpSearcherRawInfo =
-    FunctionInfo<RegExpSearcherRawFn>(RegExpSearcherRaw, "RegExpSearcherRaw");
-
 void CodeGenerator::visitOutOfLineRegExpSearcher(OutOfLineRegExpSearcher* ool) {
   LRegExpSearcher* lir = ool->lir();
   Register lastIndex = ToRegister(lir->lastIndex());
   Register input = ToRegister(lir->string());
   Register regexp = ToRegister(lir->regexp());
 
   AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
   regs.take(lastIndex);
@@ -2957,17 +2947,19 @@ void CodeGenerator::visitOutOfLineRegExp
 
   pushArg(temp);
   pushArg(lastIndex);
   pushArg(input);
   pushArg(regexp);
 
   // We are not using oolCallVM because we are in a Call, and that live
   // registers are already saved by the the register allocator.
-  callVM(RegExpSearcherRawInfo, lir);
+  using Fn = bool (*)(JSContext * cx, HandleObject regexp, HandleString input,
+                      int32_t lastIndex, MatchPairs * pairs, int32_t * result);
+  callVM<Fn, RegExpSearcherRaw>(lir);
 
   masm.jump(ool->rejoin());
 }
 
 void CodeGenerator::visitRegExpSearcher(LRegExpSearcher* lir) {
   MOZ_ASSERT(ToRegister(lir->regexp()) == RegExpTesterRegExpReg);
   MOZ_ASSERT(ToRegister(lir->string()) == RegExpTesterStringReg);
   MOZ_ASSERT(ToRegister(lir->lastIndex()) == RegExpTesterLastIndexReg);
@@ -3071,35 +3063,31 @@ class OutOfLineRegExpTester : public Out
 
   void accept(CodeGenerator* codegen) override {
     codegen->visitOutOfLineRegExpTester(this);
   }
 
   LRegExpTester* lir() const { return lir_; }
 };
 
-typedef bool (*RegExpTesterRawFn)(JSContext* cx, HandleObject regexp,
-                                  HandleString input, int32_t lastIndex,
-                                  int32_t* result);
-static const VMFunction RegExpTesterRawInfo =
-    FunctionInfo<RegExpTesterRawFn>(RegExpTesterRaw, "RegExpTesterRaw");
-
 void CodeGenerator::visitOutOfLineRegExpTester(OutOfLineRegExpTester* ool) {
   LRegExpTester* lir = ool->lir();
   Register lastIndex = ToRegister(lir->lastIndex());
   Register input = ToRegister(lir->string());
   Register regexp = ToRegister(lir->regexp());
 
   pushArg(lastIndex);
   pushArg(input);
   pushArg(regexp);
 
   // We are not using oolCallVM because we are in a Call, and that live
   // registers are already saved by the the register allocator.
-  callVM(RegExpTesterRawInfo, lir);
+  using Fn = bool (*)(JSContext * cx, HandleObject regexp, HandleString input,
+                      int32_t lastIndex, int32_t * result);
+  callVM<Fn, RegExpTesterRaw>(lir);
 
   masm.jump(ool->rejoin());
 }
 
 void CodeGenerator::visitRegExpTester(LRegExpTester* lir) {
   MOZ_ASSERT(ToRegister(lir->regexp()) == RegExpTesterRegExpReg);
   MOZ_ASSERT(ToRegister(lir->string()) == RegExpTesterStringReg);
   MOZ_ASSERT(ToRegister(lir->lastIndex()) == RegExpTesterLastIndexReg);
@@ -3297,23 +3285,16 @@ void CodeGenerator::visitGetFirstDollarI
   {
     FindFirstDollarIndex(masm, str, len, temp0, temp1, output,
                          CharEncoding::Latin1);
   }
   masm.bind(&done);
   masm.bind(ool->rejoin());
 }
 
-typedef JSString* (*StringReplaceFn)(JSContext*, HandleString, HandleString,
-                                     HandleString);
-static const VMFunction StringFlatReplaceInfo = FunctionInfo<StringReplaceFn>(
-    js::str_flat_replace_string, "str_flat_replace_string");
-static const VMFunction StringReplaceInfo =
-    FunctionInfo<StringReplaceFn>(StringReplace, "StringReplace");
-
 void CodeGenerator::visitStringReplace(LStringReplace* lir) {
   if (lir->replacement()->isConstant()) {
     pushArg(ImmGCPtr(lir->replacement()->toConstant()->toString()));
   } else {
     pushArg(ToRegister(lir->replacement()));
   }
 
   if (lir->pattern()->isConstant()) {
@@ -3323,20 +3304,22 @@ void CodeGenerator::visitStringReplace(L
   }
 
   if (lir->string()->isConstant()) {
     pushArg(ImmGCPtr(lir->string()->toConstant()->toString()));
   } else {
     pushArg(ToRegister(lir->string()));
   }
 
+  using Fn =
+      JSString* (*)(JSContext*, HandleString, HandleString, HandleString);
   if (lir->mir()->isFlatReplacement()) {
-    callVM(StringFlatReplaceInfo, lir);
-  } else {
-    callVM(StringReplaceInfo, lir);
+    callVM<Fn, StringFlatReplaceString>(lir);
+  } else {
+    callVM<Fn, StringReplace>(lir);
   }
 }
 
 void CodeGenerator::visitBinaryValueCache(LBinaryValueCache* lir) {
   LiveRegisterSet liveRegs = lir->safepoint()->liveRegs();
   TypedOrValueRegister lhs =
       TypedOrValueRegister(ToValue(lir, LBinaryValueCache::LhsInput));
   TypedOrValueRegister rhs =
@@ -3475,21 +3458,16 @@ class OutOfLineLambdaArrow : public OutO
 
   void accept(CodeGenerator* codegen) override {
     codegen->visitOutOfLineLambdaArrow(this);
   }
 
   Label* entryNoPop() { return &entryNoPop_; }
 };
 
-typedef JSObject* (*LambdaArrowFn)(JSContext*, HandleFunction, HandleObject,
-                                   HandleValue);
-static const VMFunction LambdaArrowInfo =
-    FunctionInfo<LambdaArrowFn>(js::LambdaArrow, "LambdaArrow");
-
 void CodeGenerator::visitOutOfLineLambdaArrow(OutOfLineLambdaArrow* ool) {
   Register envChain = ToRegister(ool->lir->environmentChain());
   ValueOperand newTarget = ToValue(ool->lir, LLambdaArrow::NewTargetValue);
   Register output = ToRegister(ool->lir->output());
   const LambdaFunctionInfo& info = ool->lir->mir()->info();
 
   // When we get here, we may need to restore part of the newTarget,
   // which has been conscripted into service as a temp register.
@@ -3498,17 +3476,19 @@ void CodeGenerator::visitOutOfLineLambda
   masm.bind(ool->entryNoPop());
 
   saveLive(ool->lir);
 
   pushArg(newTarget);
   pushArg(envChain);
   pushArg(ImmGCPtr(info.funUnsafe()));
 
-  callVM(LambdaArrowInfo, ool->lir);
+  using Fn =
+      JSObject* (*)(JSContext*, HandleFunction, HandleObject, HandleValue);
+  callVM<Fn, js::LambdaArrow>(ool->lir);
   StoreRegisterTo(output).generate(this);
 
   restoreLiveIgnore(ool->lir, StoreRegisterTo(output).clobbered());
 
   masm.jump(ool->rejoin());
 }
 
 void CodeGenerator::visitLambdaArrow(LLambdaArrow* lir) {
@@ -3679,26 +3659,22 @@ void CodeGenerator::visitTableSwitchV(LT
   masm.bind(&unboxInt);
   masm.unboxInt32(value, index);
 
   masm.bind(&isInt);
 
   emitTableSwitchDispatch(mir, index, ToRegisterOrInvalid(ins->tempPointer()));
 }
 
-typedef JSObject* (*DeepCloneObjectLiteralFn)(JSContext*, HandleObject,
-                                              NewObjectKind);
-static const VMFunction DeepCloneObjectLiteralInfo =
-    FunctionInfo<DeepCloneObjectLiteralFn>(DeepCloneObjectLiteral,
-                                           "DeepCloneObjectLiteral");
-
 void CodeGenerator::visitCloneLiteral(LCloneLiteral* lir) {
   pushArg(ImmWord(TenuredObject));
   pushArg(ToRegister(lir->getObjectLiteral()));
-  callVM(DeepCloneObjectLiteralInfo, lir);
+
+  using Fn = JSObject* (*)(JSContext*, HandleObject, NewObjectKind);
+  callVM<Fn, DeepCloneObjectLiteral>(lir);
 }
 
 void CodeGenerator::visitParameter(LParameter* lir) {}
 
 void CodeGenerator::visitCallee(LCallee* lir) {
   Register callee = ToRegister(lir->output());
   Address ptr(masm.getStackPointer(),
               frameSize() + JitFrameLayout::offsetOfCalleeToken());
@@ -4296,42 +4272,34 @@ void CodeGenerator::visitHomeObjectSuper
   OutOfLineCode* ool = oolCallVM(HomeObjectSuperBaseInfo, lir,
                                  ArgList(homeObject), StoreRegisterTo(output));
 
   masm.loadObjProto(homeObject, output);
   masm.branchPtr(Assembler::BelowOrEqual, output, ImmWord(1), ool->entry());
   masm.bind(ool->rejoin());
 }
 
-typedef LexicalEnvironmentObject* (*NewLexicalEnvironmentObjectFn)(
-    JSContext*, Handle<LexicalScope*>, HandleObject, gc::InitialHeap);
-static const VMFunction NewLexicalEnvironmentObjectInfo =
-    FunctionInfo<NewLexicalEnvironmentObjectFn>(
-        LexicalEnvironmentObject::create, "LexicalEnvironmentObject::create");
-
 void CodeGenerator::visitNewLexicalEnvironmentObject(
     LNewLexicalEnvironmentObject* lir) {
   pushArg(Imm32(gc::DefaultHeap));
   pushArg(ToRegister(lir->enclosing()));
   pushArg(ImmGCPtr(lir->mir()->scope()));
-  callVM(NewLexicalEnvironmentObjectInfo, lir);
-}
-
-typedef JSObject* (*CopyLexicalEnvironmentObjectFn)(JSContext*, HandleObject,
-                                                    bool);
-static const VMFunction CopyLexicalEnvironmentObjectInfo =
-    FunctionInfo<CopyLexicalEnvironmentObjectFn>(
-        js::jit::CopyLexicalEnvironmentObject,
-        "js::jit::CopyLexicalEnvironmentObject");
+
+  using Fn = LexicalEnvironmentObject* (*)(JSContext*, Handle<LexicalScope*>,
+                                           HandleObject, gc::InitialHeap);
+  callVM<Fn, LexicalEnvironmentObject::create>(lir);
+}
 
 void CodeGenerator::visitCopyLexicalEnvironmentObject(
     LCopyLexicalEnvironmentObject* lir) {
   pushArg(Imm32(lir->mir()->copySlots()));
   pushArg(ToRegister(lir->env()));
-  callVM(CopyLexicalEnvironmentObjectInfo, lir);
+
+  using Fn = JSObject* (*)(JSContext*, HandleObject, bool);
+  callVM<Fn, jit::CopyLexicalEnvironmentObject>(lir);
 }
 
 void CodeGenerator::visitGuardShape(LGuardShape* guard) {
   Register obj = ToRegister(guard->input());
   Register temp = ToTempRegisterOrInvalid(guard->temp());
   Label bail;
   masm.branchTestObjShape(Assembler::NotEqual, obj, guard->mir()->shape(), temp,
                           obj, &bail);
@@ -5049,45 +5017,39 @@ void CodeGenerator::visitCallDOMNative(L
   // is no need for leaveFakeExitFrame.
 
   // Move the StackPointer back to its original location, unwinding the native
   // exit frame.
   masm.adjustStack(IonDOMMethodExitFrameLayout::Size() - unusedStack);
   MOZ_ASSERT(masm.framePushed() == initialStack);
 }
 
-typedef bool (*GetIntrinsicValueFn)(JSContext* cx, HandlePropertyName,
-                                    MutableHandleValue);
-static const VMFunction GetIntrinsicValueInfo =
-    FunctionInfo<GetIntrinsicValueFn>(GetIntrinsicValue, "GetIntrinsicValue");
-
 void CodeGenerator::visitCallGetIntrinsicValue(LCallGetIntrinsicValue* lir) {
   pushArg(ImmGCPtr(lir->mir()->name()));
-  callVM(GetIntrinsicValueInfo, lir);
-}
-
-typedef bool (*InvokeFunctionFn)(JSContext*, HandleObject, bool, bool, uint32_t,
-                                 Value*, MutableHandleValue);
-static const VMFunction InvokeFunctionInfo =
-    FunctionInfo<InvokeFunctionFn>(InvokeFunction, "InvokeFunction");
+
+  using Fn = bool (*)(JSContext * cx, HandlePropertyName, MutableHandleValue);
+  callVM<Fn, GetIntrinsicValue>(lir);
+}
 
 void CodeGenerator::emitCallInvokeFunction(
     LInstruction* call, Register calleereg, bool constructing,
     bool ignoresReturnValue, uint32_t argc, uint32_t unusedStack) {
   // Nestle %esp up to the argument vector.
   // Each path must account for framePushed_ separately, for callVM to be valid.
   masm.freeStack(unusedStack);
 
   pushArg(masm.getStackPointer());  // argv.
   pushArg(Imm32(argc));             // argc.
   pushArg(Imm32(ignoresReturnValue));
   pushArg(Imm32(constructing));  // constructing.
   pushArg(calleereg);            // JSFunction*.
 
-  callVM(InvokeFunctionInfo, call);
+  using Fn = bool (*)(JSContext*, HandleObject, bool, bool, uint32_t, Value*,
+                      MutableHandleValue);
+  callVM<Fn, jit::InvokeFunction>(call);
 
   // Un-nestle %esp from the argument vector. No prefix was pushed.
   masm.reserveStack(unusedStack);
 }
 
 void CodeGenerator::visitCallGeneric(LCallGeneric* call) {
   Register calleereg = ToRegister(call->getFunction());
   Register objreg = ToRegister(call->getTempObject());
@@ -5191,32 +5153,29 @@ void CodeGenerator::visitCallGeneric(LCa
     masm.branchTestPrimitive(Assembler::NotEqual, JSReturnOperand,
                              &notPrimitive);
     masm.loadValue(Address(masm.getStackPointer(), unusedStack),
                    JSReturnOperand);
     masm.bind(&notPrimitive);
   }
 }
 
-typedef bool (*InvokeFunctionShuffleFn)(JSContext*, HandleObject, uint32_t,
-                                        uint32_t, Value*, MutableHandleValue);
-static const VMFunction InvokeFunctionShuffleInfo =
-    FunctionInfo<InvokeFunctionShuffleFn>(InvokeFunctionShuffleNewTarget,
-                                          "InvokeFunctionShuffleNewTarget");
 void CodeGenerator::emitCallInvokeFunctionShuffleNewTarget(
     LCallKnown* call, Register calleeReg, uint32_t numFormals,
     uint32_t unusedStack) {
   masm.freeStack(unusedStack);
 
   pushArg(masm.getStackPointer());
   pushArg(Imm32(numFormals));
   pushArg(Imm32(call->numActualArgs()));
   pushArg(calleeReg);
 
-  callVM(InvokeFunctionShuffleInfo, call);
+  using Fn = bool (*)(JSContext*, HandleObject, uint32_t, uint32_t, Value*,
+                      MutableHandleValue);
+  callVM<Fn, InvokeFunctionShuffleNewTarget>(call);
 
   masm.reserveStack(unusedStack);
 }
 
 void CodeGenerator::visitCallKnown(LCallKnown* call) {
   Register calleereg = ToRegister(call->getFunction());
   Register objreg = ToRegister(call->getTempObject());
   uint32_t unusedStack = StackOffsetOfPassedArg(call->argslot());
@@ -5326,17 +5285,19 @@ void CodeGenerator::emitCallInvokeFuncti
 
   pushArg(objreg);                            // argv.
   pushArg(ToRegister(apply->getArgc()));      // argc.
   pushArg(Imm32(false));                      // ignoresReturnValue.
   pushArg(Imm32(false));                      // isConstrucing.
   pushArg(ToRegister(apply->getFunction()));  // JSFunction*.
 
   // This specialization og callVM restore the extraStackSize after the call.
-  callVM(InvokeFunctionInfo, apply, &extraStackSize);
+  using Fn = bool (*)(JSContext*, HandleObject, bool, bool, uint32_t, Value*,
+                      MutableHandleValue);
+  callVM<Fn, jit::InvokeFunction>(apply, &extraStackSize);
 
   masm.Pop(extraStackSize);
 }
 
 // Do not bailout after the execution of this function since the stack no longer
 // correspond to what is expected by the snapshots.
 void CodeGenerator::emitAllocateSpaceForApply(Register argcreg,
                                               Register extraStackSpace,
@@ -5728,33 +5689,29 @@ void CodeGenerator::visitGetDynamicName(
   const ValueOperand out = ToOutValue(lir);
 
   masm.loadValue(Address(masm.getStackPointer(), 0), out);
   masm.adjustStack(sizeof(Value));
 
   bailoutIfFalseBool(ReturnReg, lir->snapshot());
 }
 
-typedef bool (*DirectEvalSFn)(JSContext*, HandleObject, HandleScript,
-                              HandleValue, HandleString, jsbytecode*,
-                              MutableHandleValue);
-static const VMFunction DirectEvalStringInfo = FunctionInfo<DirectEvalSFn>(
-    DirectEvalStringFromIon, "DirectEvalStringFromIon");
-
 void CodeGenerator::visitCallDirectEval(LCallDirectEval* lir) {
   Register envChain = ToRegister(lir->getEnvironmentChain());
   Register string = ToRegister(lir->getString());
 
   pushArg(ImmPtr(lir->mir()->pc()));
   pushArg(string);
   pushArg(ToValue(lir, LCallDirectEval::NewTarget));
   pushArg(ImmGCPtr(current->mir()->info().script()));
   pushArg(envChain);
 
-  callVM(DirectEvalStringInfo, lir);
+  using Fn = bool (*)(JSContext*, HandleObject, HandleScript, HandleValue,
+                      HandleString, jsbytecode*, MutableHandleValue);
+  callVM<Fn, DirectEvalStringFromIon>(lir);
 }
 
 void CodeGenerator::generateArgumentsChecks(bool assert) {
   // This function can be used the normal way to check the argument types,
   // before entering the function and bailout when arguments don't match.
   // For debug purpose, this is can also be used to force/check that the
   // arguments are correct. Upon fail it will hit a breakpoint.
 
@@ -5908,31 +5865,28 @@ void CodeGenerator::visitDefFun(LDefFun*
   pushArg(fun);
   pushArg(envChain);
   pushArg(ImmGCPtr(current->mir()->info().script()));
 
   using Fn = bool (*)(JSContext*, HandleScript, HandleObject, HandleFunction);
   callVM<Fn, DefFunOperation>(lir);
 }
 
-typedef bool (*CheckOverRecursedFn)(JSContext*);
-static const VMFunction CheckOverRecursedInfo =
-    FunctionInfo<CheckOverRecursedFn>(CheckOverRecursed, "CheckOverRecursed");
-
 void CodeGenerator::visitCheckOverRecursedFailure(
     CheckOverRecursedFailure* ool) {
   // The OOL path is hit if the recursion depth has been exceeded.
   // Throw an InternalError for over-recursion.
 
   // LFunctionEnvironment can appear before LCheckOverRecursed, so we have
   // to save all live registers to avoid crashes if CheckOverRecursed triggers
   // a GC.
   saveLive(ool->lir());
 
-  callVM(CheckOverRecursedInfo, ool->lir());
+  using Fn = bool (*)(JSContext*);
+  callVM<Fn, CheckOverRecursed>(ool->lir());
 
   restoreLive(ool->lir());
   masm.jump(ool->rejoin());
 }
 
 IonScriptCounts* CodeGenerator::maybeCreateScriptCounts() {
   // If scripts are being profiled, create a new IonScriptCounts for the
   // profiling data, which will be attached to the associated JSScript or
@@ -6468,81 +6422,57 @@ class OutOfLineNewArray : public OutOfLi
 
   void accept(CodeGenerator* codegen) override {
     codegen->visitOutOfLineNewArray(this);
   }
 
   LNewArray* lir() const { return lir_; }
 };
 
-typedef JSObject* (*NewArrayOperationFn)(JSContext*, HandleScript, jsbytecode*,
-                                         uint32_t, NewObjectKind);
-static const VMFunction NewArrayOperationInfo =
-    FunctionInfo<NewArrayOperationFn>(NewArrayOperation, "NewArrayOperation");
-
-static JSObject* NewArrayWithGroup(JSContext* cx, uint32_t length,
-                                   HandleObjectGroup group,
-                                   bool convertDoubleElements) {
-  ArrayObject* res = NewFullyAllocatedArrayTryUseGroup(cx, group, length);
-  if (!res) {
-    return nullptr;
-  }
-  if (convertDoubleElements) {
-    res->setShouldConvertDoubleElements();
-  }
-  return res;
-}
-
-typedef JSObject* (*NewArrayWithGroupFn)(JSContext*, uint32_t,
-                                         HandleObjectGroup, bool);
-static const VMFunction NewArrayWithGroupInfo =
-    FunctionInfo<NewArrayWithGroupFn>(NewArrayWithGroup, "NewArrayWithGroup");
-
 void CodeGenerator::visitNewArrayCallVM(LNewArray* lir) {
   Register objReg = ToRegister(lir->output());
 
   MOZ_ASSERT(!lir->isCall());
   saveLive(lir);
 
   JSObject* templateObject = lir->mir()->templateObject();
 
   if (templateObject) {
     pushArg(Imm32(lir->mir()->convertDoubleElements()));
     pushArg(ImmGCPtr(templateObject->group()));
     pushArg(Imm32(lir->mir()->length()));
 
-    callVM(NewArrayWithGroupInfo, lir);
+    using Fn = ArrayObject* (*)(JSContext*, uint32_t, HandleObjectGroup, bool);
+    callVM<Fn, NewArrayWithGroup>(lir);
   } else {
     pushArg(Imm32(GenericObject));
     pushArg(Imm32(lir->mir()->length()));
     pushArg(ImmPtr(lir->mir()->pc()));
     pushArg(ImmGCPtr(lir->mir()->block()->info().script()));
 
-    callVM(NewArrayOperationInfo, lir);
+    using Fn = JSObject* (*)(JSContext*, HandleScript, jsbytecode*, uint32_t,
+                             NewObjectKind);
+    callVM<Fn, NewArrayOperation>(lir);
   }
 
   if (ReturnReg != objReg) {
     masm.movePtr(ReturnReg, objReg);
   }
 
   restoreLive(lir);
 }
 
-typedef JSObject* (*NewDerivedTypedObjectFn)(JSContext*, HandleObject type,
-                                             HandleObject owner,
-                                             int32_t offset);
-static const VMFunction CreateDerivedTypedObjInfo =
-    FunctionInfo<NewDerivedTypedObjectFn>(CreateDerivedTypedObj,
-                                          "CreateDerivedTypedObj");
-
 void CodeGenerator::visitNewDerivedTypedObject(LNewDerivedTypedObject* lir) {
   pushArg(ToRegister(lir->offset()));
   pushArg(ToRegister(lir->owner()));
   pushArg(ToRegister(lir->type()));
-  callVM(CreateDerivedTypedObjInfo, lir);
+
+  using Fn = JSObject* (*)(JSContext*, HandleObject type, HandleObject owner,
+                           int32_t offset);
+  callVM<Fn, CreateDerivedTypedObj>(lir);
 }
 
 void CodeGenerator::visitAtan2D(LAtan2D* lir) {
   Register temp = ToRegister(lir->temp());
   FloatRegister y = ToFloatRegister(lir->y());
   FloatRegister x = ToFloatRegister(lir->x());
 
   masm.setupUnalignedABICall(temp);
@@ -6731,18 +6661,19 @@ void CodeGenerator::visitNewIterator(LNe
 
   masm.bind(ool->rejoin());
 }
 
 typedef TypedArrayObject* (*TypedArrayConstructorOneArgFn)(JSContext*,
                                                            HandleObject,
                                                            int32_t length);
 static const VMFunction TypedArrayConstructorOneArgInfo =
-    FunctionInfo<TypedArrayConstructorOneArgFn>(TypedArrayCreateWithTemplate,
-                                                "TypedArrayCreateWithTemplate");
+    FunctionInfo<TypedArrayConstructorOneArgFn>(
+        NewTypedArrayWithTemplateAndLength,
+        "NewTypedArrayWithTemplateAndLength");
 
 void CodeGenerator::visitNewTypedArray(LNewTypedArray* lir) {
   Register objReg = ToRegister(lir->output());
   Register tempReg = ToRegister(lir->temp1());
   Register lengthReg = ToRegister(lir->temp2());
   LiveRegisterSet liveRegs = lir->safepoint()->liveRegs();
 
   JSObject* templateObject = lir->mir()->templateObject();
@@ -6785,101 +6716,84 @@ void CodeGenerator::visitNewTypedArrayDy
 
   masm.initTypedArraySlots(objReg, tempReg, lengthReg, liveRegs, ool->entry(),
                            ttemplate,
                            MacroAssembler::TypedArrayLength::Dynamic);
 
   masm.bind(ool->rejoin());
 }
 
-typedef TypedArrayObject* (*TypedArrayCreateWithTemplateFn)(JSContext*,
-                                                            HandleObject,
-                                                            HandleObject);
-static const VMFunction TypedArrayCreateWithTemplateInfo =
-    FunctionInfo<TypedArrayCreateWithTemplateFn>(
-        js::TypedArrayCreateWithTemplate, "TypedArrayCreateWithTemplate");
-
 void CodeGenerator::visitNewTypedArrayFromArray(LNewTypedArrayFromArray* lir) {
   pushArg(ToRegister(lir->array()));
   pushArg(ImmGCPtr(lir->mir()->templateObject()));
-  callVM(TypedArrayCreateWithTemplateInfo, lir);
-}
-
-typedef TypedArrayObject* (*TypedArrayCreateFromArrayBufferWithTemplateFn)(
-    JSContext*, HandleObject, HandleObject, HandleValue, HandleValue);
-static const VMFunction TypedArrayCreateFromArrayBufferWithTemplateInfo =
-    FunctionInfo<TypedArrayCreateFromArrayBufferWithTemplateFn>(
-        js::TypedArrayCreateWithTemplate, "TypedArrayCreateWithTemplate");
+
+  using Fn = TypedArrayObject* (*)(JSContext*, HandleObject, HandleObject);
+  callVM<Fn, js::NewTypedArrayWithTemplateAndArray>(lir);
+}
 
 void CodeGenerator::visitNewTypedArrayFromArrayBuffer(
     LNewTypedArrayFromArrayBuffer* lir) {
   pushArg(ToValue(lir, LNewTypedArrayFromArrayBuffer::LengthIndex));
   pushArg(ToValue(lir, LNewTypedArrayFromArrayBuffer::ByteOffsetIndex));
   pushArg(ToRegister(lir->arrayBuffer()));
   pushArg(ImmGCPtr(lir->mir()->templateObject()));
-  callVM(TypedArrayCreateFromArrayBufferWithTemplateInfo, lir);
+
+  using Fn = TypedArrayObject* (*)(JSContext*, HandleObject, HandleObject,
+                                   HandleValue, HandleValue);
+  callVM<Fn, js::NewTypedArrayWithTemplateAndBuffer>(lir);
 }
 
 // Out-of-line object allocation for JSOP_NEWOBJECT.
 class OutOfLineNewObject : public OutOfLineCodeBase<CodeGenerator> {
   LNewObject* lir_;
 
  public:
   explicit OutOfLineNewObject(LNewObject* lir) : lir_(lir) {}
 
   void accept(CodeGenerator* codegen) override {
     codegen->visitOutOfLineNewObject(this);
   }
 
   LNewObject* lir() const { return lir_; }
 };
 
-typedef JSObject* (*NewInitObjectWithTemplateFn)(JSContext*, HandleObject);
-static const VMFunction NewInitObjectWithTemplateInfo =
-    FunctionInfo<NewInitObjectWithTemplateFn>(NewObjectOperationWithTemplate,
-                                              "NewObjectOperationWithTemplate");
-
-typedef JSObject* (*NewInitObjectFn)(JSContext*, HandleScript, jsbytecode* pc,
-                                     NewObjectKind);
-static const VMFunction NewInitObjectInfo =
-    FunctionInfo<NewInitObjectFn>(NewObjectOperation, "NewObjectOperation");
-
-typedef PlainObject* (*ObjectCreateWithTemplateFn)(JSContext*,
-                                                   HandlePlainObject);
-static const VMFunction ObjectCreateWithTemplateInfo =
-    FunctionInfo<ObjectCreateWithTemplateFn>(ObjectCreateWithTemplate,
-                                             "ObjectCreateWithTemplate");
-
 void CodeGenerator::visitNewObjectVMCall(LNewObject* lir) {
   Register objReg = ToRegister(lir->output());
 
   MOZ_ASSERT(!lir->isCall());
   saveLive(lir);
 
   JSObject* templateObject = lir->mir()->templateObject();
 
   // If we're making a new object with a class prototype (that is, an object
   // that derives its class from its prototype instead of being
   // PlainObject::class_'d) from self-hosted code, we need a different init
   // function.
   switch (lir->mir()->mode()) {
     case MNewObject::ObjectLiteral:
       if (templateObject) {
         pushArg(ImmGCPtr(templateObject));
-        callVM(NewInitObjectWithTemplateInfo, lir);
+
+        using Fn = JSObject* (*)(JSContext*, HandleObject);
+        callVM<Fn, NewObjectOperationWithTemplate>(lir);
       } else {
         pushArg(Imm32(GenericObject));
         pushArg(ImmPtr(lir->mir()->resumePoint()->pc()));
         pushArg(ImmGCPtr(lir->mir()->block()->info().script()));
-        callVM(NewInitObjectInfo, lir);
+
+        using Fn = JSObject* (*)(JSContext*, HandleScript, jsbytecode * pc,
+                                 NewObjectKind);
+        callVM<Fn, NewObjectOperation>(lir);
       }
       break;
     case MNewObject::ObjectCreate:
       pushArg(ImmGCPtr(templateObject));
-      callVM(ObjectCreateWithTemplateInfo, lir);
+
+      using Fn = PlainObject* (*)(JSContext*, HandlePlainObject);
+      callVM<Fn, ObjectCreateWithTemplate>(lir);
       break;
   }
 
   if (ReturnReg != objReg) {
     masm.movePtr(ReturnReg, objReg);
   }
 
   restoreLive(lir);
@@ -7102,30 +7016,27 @@ void CodeGenerator::visitNewStringObject
   masm.storeValue(JSVAL_TYPE_STRING, input,
                   Address(output, StringObject::offsetOfPrimitiveValue()));
   masm.storeValue(JSVAL_TYPE_INT32, temp,
                   Address(output, StringObject::offsetOfLength()));
 
   masm.bind(ool->rejoin());
 }
 
-typedef bool (*InitElemFn)(JSContext* cx, jsbytecode* pc, HandleObject obj,
-                           HandleValue id, HandleValue value);
-static const VMFunction InitElemInfo =
-    FunctionInfo<InitElemFn>(InitElemOperation, "InitElemOperation");
-
 void CodeGenerator::visitInitElem(LInitElem* lir) {
   Register objReg = ToRegister(lir->getObject());
 
   pushArg(ToValue(lir, LInitElem::ValueIndex));
   pushArg(ToValue(lir, LInitElem::IdIndex));
   pushArg(objReg);
   pushArg(ImmPtr(lir->mir()->resumePoint()->pc()));
 
-  callVM(InitElemInfo, lir);
+  using Fn = bool (*)(JSContext * cx, jsbytecode * pc, HandleObject obj,
+                      HandleValue id, HandleValue value);
+  callVM<Fn, InitElemOperation>(lir);
 }
 
 void CodeGenerator::visitInitElemGetterSetter(LInitElemGetterSetter* lir) {
   Register obj = ToRegister(lir->object());
   Register value = ToRegister(lir->value());
 
   pushArg(value);
   pushArg(ToValue(lir, LInitElemGetterSetter::IdIndex));
@@ -7156,47 +7067,36 @@ void CodeGenerator::visitInitPropGetterS
   pushArg(obj);
   pushArg(ImmPtr(lir->mir()->resumePoint()->pc()));
 
   using Fn = bool (*)(JSContext*, jsbytecode*, HandleObject, HandlePropertyName,
                       HandleObject);
   callVM<Fn, InitPropGetterSetterOperation>(lir);
 }
 
-typedef bool (*CreateThisFn)(JSContext* cx, HandleObject callee,
-                             HandleObject newTarget, MutableHandleValue rval);
-static const VMFunction CreateThisInfoCodeGen =
-    FunctionInfo<CreateThisFn>(CreateThis, "CreateThis");
-
 void CodeGenerator::visitCreateThis(LCreateThis* lir) {
   const LAllocation* callee = lir->getCallee();
   const LAllocation* newTarget = lir->getNewTarget();
 
   if (newTarget->isConstant()) {
     pushArg(ImmGCPtr(&newTarget->toConstant()->toObject()));
   } else {
     pushArg(ToRegister(newTarget));
   }
 
   if (callee->isConstant()) {
     pushArg(ImmGCPtr(&callee->toConstant()->toObject()));
   } else {
     pushArg(ToRegister(callee));
   }
 
-  callVM(CreateThisInfoCodeGen, lir);
-}
-
-typedef JSObject* (*CreateThisWithProtoFn)(JSContext* cx, HandleFunction callee,
-                                           HandleObject newTarget,
-                                           HandleObject proto,
-                                           NewObjectKind newKind);
-static const VMFunction CreateThisWithProtoInfo =
-    FunctionInfo<CreateThisWithProtoFn>(CreateThisForFunctionWithProto,
-                                        "CreateThisForFunctionWithProto");
+  using Fn = bool (*)(JSContext * cx, HandleObject callee,
+                      HandleObject newTarget, MutableHandleValue rval);
+  callVM<Fn, jit::CreateThis>(lir);
+}
 
 void CodeGenerator::visitCreateThisWithProto(LCreateThisWithProto* lir) {
   const LAllocation* callee = lir->getCallee();
   const LAllocation* newTarget = lir->getNewTarget();
   const LAllocation* proto = lir->getPrototype();
 
   pushArg(Imm32(GenericObject));
 
@@ -7213,17 +7113,20 @@ void CodeGenerator::visitCreateThisWithP
   }
 
   if (callee->isConstant()) {
     pushArg(ImmGCPtr(&callee->toConstant()->toObject()));
   } else {
     pushArg(ToRegister(callee));
   }
 
-  callVM(CreateThisWithProtoInfo, lir);
+  using Fn = JSObject* (*)(JSContext * cx, HandleFunction callee,
+                           HandleObject newTarget, HandleObject proto,
+                           NewObjectKind newKind);
+  callVM<Fn, CreateThisForFunctionWithProto>(lir);
 }
 
 typedef JSObject* (*CreateThisWithTemplateFn)(JSContext*, HandleObject);
 static const VMFunction CreateThisWithTemplateInfo =
     FunctionInfo<CreateThisWithTemplateFn>(CreateThisWithTemplate,
                                            "CreateThisWithTemplate");
 
 void CodeGenerator::visitCreateThisWithTemplate(LCreateThisWithTemplate* lir) {
@@ -7240,24 +7143,16 @@ void CodeGenerator::visitCreateThisWithT
   bool initContents =
       !templateObj.isPlainObject() || ShouldInitFixedSlots(lir, templateObj);
   masm.createGCObject(objReg, tempReg, templateObj, lir->mir()->initialHeap(),
                       ool->entry(), initContents);
 
   masm.bind(ool->rejoin());
 }
 
-typedef JSObject* (*NewIonArgumentsObjectFn)(JSContext* cx,
-                                             JitFrameLayout* frame,
-                                             HandleObject);
-static const VMFunction NewIonArgumentsObjectInfo =
-    FunctionInfo<NewIonArgumentsObjectFn>(
-        (NewIonArgumentsObjectFn)ArgumentsObject::createForIon,
-        "ArgumentsObject::createForIon");
-
 void CodeGenerator::visitCreateArgumentsObject(LCreateArgumentsObject* lir) {
   // This should be getting constructed in the first block only, and not any OSR
   // entry blocks.
   MOZ_ASSERT(lir->mir()->block()->id() == 0);
 
   Register callObj = ToRegister(lir->getCallObject());
   Register temp = ToRegister(lir->temp0());
   Label done;
@@ -7299,17 +7194,19 @@ void CodeGenerator::visitCreateArguments
     masm.Pop(callObj);
   }
 
   masm.moveStackPtrTo(temp);
   masm.addPtr(Imm32(frameSize()), temp);
 
   pushArg(callObj);
   pushArg(temp);
-  callVM(NewIonArgumentsObjectInfo, lir);
+
+  using Fn = ArgumentsObject* (*)(JSContext*, JitFrameLayout*, HandleObject);
+  callVM<Fn, ArgumentsObject::createForIon>(lir);
 
   masm.bind(&done);
 }
 
 void CodeGenerator::visitGetArgumentsObjectArg(LGetArgumentsObjectArg* lir) {
   Register temp = ToRegister(lir->getTemp(0));
   Register argsObj = ToRegister(lir->getArgsObject());
   ValueOperand out = ToOutValue(lir);
@@ -8047,25 +7944,23 @@ void CodeGenerator::visitPowD(LPowD* ins
   masm.setupUnalignedABICall(temp);
   masm.passABIArg(value, MoveOp::DOUBLE);
   masm.passABIArg(power, MoveOp::DOUBLE);
   masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, ecmaPow), MoveOp::DOUBLE);
 
   MOZ_ASSERT(ToFloatRegister(ins->output()) == ReturnDoubleReg);
 }
 
-using PowFn = bool (*)(JSContext*, MutableHandleValue, MutableHandleValue,
-                       MutableHandleValue);
-static const VMFunction PowInfo =
-    FunctionInfo<PowFn>(js::PowValues, "PowValues");
-
 void CodeGenerator::visitPowV(LPowV* ins) {
   pushArg(ToValue(ins, LPowV::PowerInput));
   pushArg(ToValue(ins, LPowV::ValueInput));
-  callVM(PowInfo, ins);
+
+  using Fn = bool (*)(JSContext*, MutableHandleValue, MutableHandleValue,
+                      MutableHandleValue);
+  callVM<Fn, js::PowValues>(ins);
 }
 
 void CodeGenerator::visitSignI(LSignI* ins) {
   Register input = ToRegister(ins->input());
   Register output = ToRegister(ins->output());
 
   Label done;
   masm.move32(input, output);
@@ -8269,59 +8164,45 @@ void CodeGenerator::visitModD(LModD* ins
   } else {
     masm.setupUnalignedABICall(ToRegister(ins->temp()));
     masm.passABIArg(lhs, MoveOp::DOUBLE);
     masm.passABIArg(rhs, MoveOp::DOUBLE);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, NumberMod), MoveOp::DOUBLE);
   }
 }
 
-typedef bool (*BinaryFn)(JSContext*, MutableHandleValue, MutableHandleValue,
-                         MutableHandleValue);
-
-static const VMFunction AddInfo =
-    FunctionInfo<BinaryFn>(js::AddValues, "AddValues");
-static const VMFunction SubInfo =
-    FunctionInfo<BinaryFn>(js::SubValues, "SubValues");
-static const VMFunction MulInfo =
-    FunctionInfo<BinaryFn>(js::MulValues, "MulValues");
-static const VMFunction DivInfo =
-    FunctionInfo<BinaryFn>(js::DivValues, "DivValues");
-static const VMFunction ModInfo =
-    FunctionInfo<BinaryFn>(js::ModValues, "ModValues");
-static const VMFunction UrshInfo =
-    FunctionInfo<BinaryFn>(js::UrshValues, "UrshValues");
-
 void CodeGenerator::visitBinaryV(LBinaryV* lir) {
   pushArg(ToValue(lir, LBinaryV::RhsInput));
   pushArg(ToValue(lir, LBinaryV::LhsInput));
 
+  using Fn = bool (*)(JSContext*, MutableHandleValue, MutableHandleValue,
+                      MutableHandleValue);
   switch (lir->jsop()) {
     case JSOP_ADD:
-      callVM(AddInfo, lir);
+      callVM<Fn, js::AddValues>(lir);
       break;
 
     case JSOP_SUB:
-      callVM(SubInfo, lir);
+      callVM<Fn, js::SubValues>(lir);
       break;
 
     case JSOP_MUL:
-      callVM(MulInfo, lir);
+      callVM<Fn, js::MulValues>(lir);
       break;
 
     case JSOP_DIV:
-      callVM(DivInfo, lir);
+      callVM<Fn, js::DivValues>(lir);
       break;
 
     case JSOP_MOD:
-      callVM(ModInfo, lir);
+      callVM<Fn, js::ModValues>(lir);
       break;
 
     case JSOP_URSH:
-      callVM(UrshInfo, lir);
+      callVM<Fn, js::UrshValues>(lir);
       break;
 
     default:
       MOZ_CRASH("Unexpected binary op");
   }
 }
 
 void CodeGenerator::emitCompareS(LInstruction* lir, JSOp op, Register left,
@@ -8374,70 +8255,53 @@ void CodeGenerator::visitCompareS(LCompa
   JSOp op = lir->mir()->jsop();
   Register left = ToRegister(lir->left());
   Register right = ToRegister(lir->right());
   Register output = ToRegister(lir->output());
 
   emitCompareS(lir, op, left, right, output);
 }
 
-typedef bool (*CompareFn)(JSContext*, MutableHandleValue, MutableHandleValue,
-                          bool*);
-static const VMFunction EqInfo =
-    FunctionInfo<CompareFn>(jit::LooselyEqual<true>, "LooselyEqual");
-static const VMFunction NeInfo =
-    FunctionInfo<CompareFn>(jit::LooselyEqual<false>, "LooselyEqual");
-static const VMFunction StrictEqInfo =
-    FunctionInfo<CompareFn>(jit::StrictlyEqual<true>, "StrictlyEqual");
-static const VMFunction StrictNeInfo =
-    FunctionInfo<CompareFn>(jit::StrictlyEqual<false>, "StrictlyEqual");
-static const VMFunction LtInfo =
-    FunctionInfo<CompareFn>(jit::LessThan, "LessThan");
-static const VMFunction LeInfo =
-    FunctionInfo<CompareFn>(jit::LessThanOrEqual, "LessThanOrEqual");
-static const VMFunction GtInfo =
-    FunctionInfo<CompareFn>(jit::GreaterThan, "GreaterThan");
-static const VMFunction GeInfo =
-    FunctionInfo<CompareFn>(jit::GreaterThanOrEqual, "GreaterThanOrEqual");
-
 void CodeGenerator::visitCompareVM(LCompareVM* lir) {
   pushArg(ToValue(lir, LBinaryV::RhsInput));
   pushArg(ToValue(lir, LBinaryV::LhsInput));
 
+  using Fn =
+      bool (*)(JSContext*, MutableHandleValue, MutableHandleValue, bool*);
   switch (lir->mir()->jsop()) {
     case JSOP_EQ:
-      callVM(EqInfo, lir);
+      callVM<Fn, jit::LooselyEqual<true>>(lir);
       break;
 
     case JSOP_NE:
-      callVM(NeInfo, lir);
+      callVM<Fn, jit::LooselyEqual<false>>(lir);
       break;
 
     case JSOP_STRICTEQ:
-      callVM(StrictEqInfo, lir);
+      callVM<Fn, jit::StrictlyEqual<true>>(lir);
       break;
 
     case JSOP_STRICTNE:
-      callVM(StrictNeInfo, lir);
+      callVM<Fn, jit::StrictlyEqual<false>>(lir);
       break;
 
     case JSOP_LT:
-      callVM(LtInfo, lir);
+      callVM<Fn, jit::LessThan>(lir);
       break;
 
     case JSOP_LE:
-      callVM(LeInfo, lir);
+      callVM<Fn, jit::LessThanOrEqual>(lir);
       break;
 
     case JSOP_GT:
-      callVM(GtInfo, lir);
+      callVM<Fn, jit::GreaterThan>(lir);
       break;
 
     case JSOP_GE:
-      callVM(GeInfo, lir);
+      callVM<Fn, jit::GreaterThanOrEqual>(lir);
       break;
 
     default:
       MOZ_CRASH("Unexpected compare op");
   }
 }
 
 void CodeGenerator::visitIsNullOrLikeUndefinedV(LIsNullOrLikeUndefinedV* lir) {
@@ -8782,24 +8646,22 @@ void CodeGenerator::visitSameValueV(LSam
 
   Label nonDouble;
   masm.move32(Imm32(0), output);
   masm.ensureDouble(left, temp1, &nonDouble);
   emitSameValue(temp1, right, temp2, output);
   masm.bind(&nonDouble);
 }
 
-typedef bool (*SameValueFn)(JSContext*, HandleValue, HandleValue, bool*);
-static const VMFunction SameValueInfo =
-    FunctionInfo<SameValueFn>(js::SameValue, "SameValue");
-
 void CodeGenerator::visitSameValueVM(LSameValueVM* lir) {
   pushArg(ToValue(lir, LSameValueVM::RhsInput));
   pushArg(ToValue(lir, LSameValueVM::LhsInput));
-  callVM(SameValueInfo, lir);
+
+  using Fn = bool (*)(JSContext*, HandleValue, HandleValue, bool*);
+  callVM<Fn, js::SameValue>(lir);
 }
 
 void CodeGenerator::emitConcat(LInstruction* lir, Register lhs, Register rhs,
                                Register output) {
   OutOfLineCode* ool = oolCallVM(ConcatStringsInfo, lir, ArgList(lhs, rhs),
                                  StoreRegisterTo(output));
 
   const JitRealm* jitRealm = gen->realm->jitRealm();
@@ -9486,33 +9348,24 @@ void CodeGenerator::visitFromCodePoint(L
       // Null-terminate.
       masm.store16(Imm32(0), Address(temp1, 2 * sizeof(char16_t)));
     }
   }
 
   masm.bind(done);
 }
 
-typedef JSString* (*StringToLowerCaseFn)(JSContext*, HandleString);
-static const VMFunction StringToLowerCaseInfo =
-    FunctionInfo<StringToLowerCaseFn>(js::StringToLowerCase,
-                                      "StringToLowerCase");
-
-typedef JSString* (*StringToUpperCaseFn)(JSContext*, HandleString);
-static const VMFunction StringToUpperCaseInfo =
-    FunctionInfo<StringToUpperCaseFn>(js::StringToUpperCase,
-                                      "StringToUpperCase");
-
 void CodeGenerator::visitStringConvertCase(LStringConvertCase* lir) {
   pushArg(ToRegister(lir->string()));
 
+  using Fn = JSString* (*)(JSContext*, HandleString);
   if (lir->mir()->mode() == MStringConvertCase::LowerCase) {
-    callVM(StringToLowerCaseInfo, lir);
-  } else {
-    callVM(StringToUpperCaseInfo, lir);
+    callVM<Fn, js::StringToLowerCase>(lir);
+  } else {
+    callVM<Fn, js::StringToUpperCase>(lir);
   }
 }
 
 void CodeGenerator::visitSinCos(LSinCos* lir) {
   Register temp = ToRegister(lir->temp());
   Register params = ToRegister(lir->temp2());
   FloatRegister input = ToFloatRegister(lir->input());
   FloatRegister outputSin = ToFloatRegister(lir->outputSin());
@@ -9532,28 +9385,25 @@ void CodeGenerator::visitSinCos(LSinCos*
 
   masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, js::math_sincos_impl));
 
   masm.loadDouble(Address(masm.getStackPointer(), 0), outputCos);
   masm.loadDouble(Address(masm.getStackPointer(), sizeof(double)), outputSin);
   masm.freeStack(sizeof(double) * 2);
 }
 
-typedef ArrayObject* (*StringSplitFn)(JSContext*, HandleObjectGroup,
-                                      HandleString, HandleString, uint32_t);
-static const VMFunction StringSplitInfo =
-    FunctionInfo<StringSplitFn>(js::str_split_string, "str_split_string");
-
 void CodeGenerator::visitStringSplit(LStringSplit* lir) {
   pushArg(Imm32(INT32_MAX));
   pushArg(ToRegister(lir->separator()));
   pushArg(ToRegister(lir->string()));
   pushArg(ImmGCPtr(lir->mir()->group()));
 
-  callVM(StringSplitInfo, lir);
+  using Fn = ArrayObject* (*)(JSContext*, HandleObjectGroup, HandleString,
+                              HandleString, uint32_t);
+  callVM<Fn, js::StringSplitString>(lir);
 }
 
 void CodeGenerator::visitInitializedLength(LInitializedLength* lir) {
   Address initLength(ToRegister(lir->elements()),
                      ObjectElements::offsetOfInitializedLength());
   masm.load32(initLength, ToRegister(lir->output()));
 }
 
@@ -9963,21 +9813,16 @@ void CodeGenerator::visitStoreElementHol
 void CodeGenerator::visitFallibleStoreElementT(LFallibleStoreElementT* lir) {
   emitStoreElementHoleT(lir);
 }
 
 void CodeGenerator::visitFallibleStoreElementV(LFallibleStoreElementV* lir) {
   emitStoreElementHoleV(lir);
 }
 
-typedef bool (*SetDenseElementFn)(JSContext*, HandleNativeObject, int32_t,
-                                  HandleValue, bool strict);
-static const VMFunction SetDenseElementInfo =
-    FunctionInfo<SetDenseElementFn>(jit::SetDenseElement, "SetDenseElement");
-
 void CodeGenerator::visitOutOfLineStoreElementHole(
     OutOfLineStoreElementHole* ool) {
   Register object, elements;
   LInstruction* ins = ool->ins();
   const LAllocation* index;
   MIRType valueType;
   mozilla::Maybe<ConstantOrRegister> value;
   Register spectreTemp;
@@ -10092,17 +9937,20 @@ void CodeGenerator::visitOutOfLineStoreE
   pushArg(Imm32(ool->strict()));
   pushArg(value.ref());
   if (index->isConstant()) {
     pushArg(Imm32(ToInt32(index)));
   } else {
     pushArg(ToRegister(index));
   }
   pushArg(object);
-  callVM(SetDenseElementInfo, ins);
+
+  using Fn = bool (*)(JSContext*, HandleNativeObject, int32_t, HandleValue,
+                      bool strict);
+  callVM<Fn, jit::SetDenseElement>(ins);
 
   restoreLive(ins);
   masm.jump(ool->rejoin());
 }
 
 template <typename T>
 static void StoreUnboxedPointer(MacroAssembler& masm, T address, MIRType type,
                                 const LAllocation* value, bool preBarrier) {
@@ -10350,21 +10198,16 @@ void CodeGenerator::visitArrayPushT(LArr
   } else {
     value.emplace(TypedOrValueRegister(lir->mir()->value()->type(),
                                        ToAnyRegister(lir->value())));
   }
   Register spectreTemp = ToTempRegisterOrInvalid(lir->spectreTemp());
   emitArrayPush(lir, obj, value.ref(), elementsTemp, length, spectreTemp);
 }
 
-typedef JSObject* (*ArraySliceDenseFn)(JSContext*, HandleObject, int32_t,
-                                       int32_t, HandleObject);
-static const VMFunction ArraySliceDenseInfo =
-    FunctionInfo<ArraySliceDenseFn>(array_slice_dense, "array_slice_dense");
-
 void CodeGenerator::visitArraySlice(LArraySlice* lir) {
   Register object = ToRegister(lir->object());
   Register begin = ToRegister(lir->begin());
   Register end = ToRegister(lir->end());
   Register temp1 = ToRegister(lir->temp1());
   Register temp2 = ToRegister(lir->temp2());
 
   Label call, fail;
@@ -10383,22 +10226,21 @@ void CodeGenerator::visitArraySlice(LArr
     masm.movePtr(ImmPtr(nullptr), temp1);
   }
   masm.bind(&call);
 
   pushArg(temp1);
   pushArg(end);
   pushArg(begin);
   pushArg(object);
-  callVM(ArraySliceDenseInfo, lir);
-}
-
-typedef JSString* (*ArrayJoinFn)(JSContext*, HandleObject, HandleString);
-static const VMFunction ArrayJoinInfo =
-    FunctionInfo<ArrayJoinFn>(jit::ArrayJoin, "ArrayJoin");
+
+  using Fn =
+      JSObject* (*)(JSContext*, HandleObject, int32_t, int32_t, HandleObject);
+  callVM<Fn, ArraySliceDense>(lir);
+}
 
 void CodeGenerator::visitArrayJoin(LArrayJoin* lir) {
   Label skipCall;
 
   Register output = ToRegister(lir->output());
   Register sep = ToRegister(lir->separator());
   Register array = ToRegister(lir->array());
   if (lir->mir()->optimizeForArray()) {
@@ -10428,17 +10270,19 @@ void CodeGenerator::visitArrayJoin(LArra
     // guaranteed to succeed.
     masm.unboxString(elem0, output);
     masm.jump(&skipCall);
     masm.bind(&notSingleString);
   }
 
   pushArg(sep);
   pushArg(array);
-  callVM(ArrayJoinInfo, lir);
+
+  using Fn = JSString* (*)(JSContext*, HandleObject, HandleString);
+  callVM<Fn, jit::ArrayJoin>(lir);
   masm.bind(&skipCall);
 }
 
 void CodeGenerator::visitGetIteratorCache(LGetIteratorCache* lir) {
   LiveRegisterSet liveRegs = lir->safepoint()->liveRegs();
   TypedOrValueRegister val = toConstantOrRegister(lir, LGetIteratorCache::Value,
                                                   lir->mir()->value()->type())
                                  .reg();
@@ -10530,21 +10374,16 @@ void CodeGenerator::visitSetFrameArgumen
 
 void CodeGenerator::visitSetFrameArgumentV(LSetFrameArgumentV* lir) {
   const ValueOperand val = ToValue(lir, LSetFrameArgumentV::Input);
   size_t argOffset = frameSize() + JitFrameLayout::offsetOfActualArgs() +
                      (sizeof(Value) * lir->mir()->argno());
   masm.storeValue(val, Address(masm.getStackPointer(), argOffset));
 }
 
-typedef JSObject* (*InitRestParameterFn)(JSContext*, uint32_t, Value*,
-                                         HandleObject, HandleObject);
-static const VMFunction InitRestParameterInfo =
-    FunctionInfo<InitRestParameterFn>(InitRestParameter, "InitRestParameter");
-
 void CodeGenerator::emitRest(LInstruction* lir, Register array,
                              Register numActuals, Register temp0,
                              Register temp1, unsigned numFormals,
                              JSObject* templateObject, bool saveAndRestore,
                              Register resultreg) {
   // Compute actuals() + numFormals.
   size_t actualsOffset = frameSize() + JitFrameLayout::offsetOfActualArgs();
   masm.moveStackPtrTo(temp1);
@@ -10567,17 +10406,19 @@ void CodeGenerator::emitRest(LInstructio
     saveLive(lir);
   }
 
   pushArg(array);
   pushArg(ImmGCPtr(templateObject));
   pushArg(temp1);
   pushArg(temp0);
 
-  callVM(InitRestParameterInfo, lir);
+  using Fn =
+      JSObject* (*)(JSContext*, uint32_t, Value*, HandleObject, HandleObject);
+  callVM<Fn, InitRestParameter>(lir);
 
   if (saveAndRestore) {
     storePointerResultTo(resultreg);
     restoreLive(lir);
   }
 }
 
 void CodeGenerator::visitRest(LRest* lir) {
@@ -11448,74 +11289,63 @@ void CodeGenerator::visitOutOfLineUnboxF
 
 void CodeGenerator::visitCallBindVar(LCallBindVar* lir) {
   pushArg(ToRegister(lir->environmentChain()));
 
   using Fn = JSObject* (*)(JSContext*, JSObject*);
   callVM<Fn, BindVarOperation>(lir);
 }
 
-typedef bool (*GetPropertyFn)(JSContext*, HandleValue, HandlePropertyName,
-                              MutableHandleValue);
-static const VMFunction GetPropertyInfo =
-    FunctionInfo<GetPropertyFn>(GetProperty, "GetProperty");
-
 void CodeGenerator::visitCallGetProperty(LCallGetProperty* lir) {
   pushArg(ImmGCPtr(lir->mir()->name()));
   pushArg(ToValue(lir, LCallGetProperty::Value));
 
-  callVM(GetPropertyInfo, lir);
-}
-
-typedef bool (*GetOrCallElementFn)(JSContext*, MutableHandleValue, HandleValue,
-                                   MutableHandleValue);
-static const VMFunction GetElementInfo =
-    FunctionInfo<GetOrCallElementFn>(js::GetElement, "GetElement");
-static const VMFunction CallElementInfo =
-    FunctionInfo<GetOrCallElementFn>(js::CallElement, "CallElement");
+  using Fn =
+      bool (*)(JSContext*, HandleValue, HandlePropertyName, MutableHandleValue);
+  callVM<Fn, GetValueProperty>(lir);
+}
 
 void CodeGenerator::visitCallGetElement(LCallGetElement* lir) {
   pushArg(ToValue(lir, LCallGetElement::RhsInput));
   pushArg(ToValue(lir, LCallGetElement::LhsInput));
 
   JSOp op = JSOp(*lir->mir()->resumePoint()->pc());
-
-  if (op == JSOP_GETELEM) {
-    callVM(GetElementInfo, lir);
-  } else {
-    MOZ_ASSERT(op == JSOP_CALLELEM);
-    callVM(CallElementInfo, lir);
-  }
+  pushArg(Imm32(op));
+
+  using Fn =
+      bool (*)(JSContext*, JSOp, HandleValue, HandleValue, MutableHandleValue);
+  callVM<Fn, GetElementOperation>(lir);
 }
 
 void CodeGenerator::visitCallSetElement(LCallSetElement* lir) {
   Register obj = ToRegister(lir->getOperand(0));
   pushArg(Imm32(lir->mir()->strict()));
   pushArg(TypedOrValueRegister(MIRType::Object, AnyRegister(obj)));
   pushArg(ToValue(lir, LCallSetElement::Value));
   pushArg(ToValue(lir, LCallSetElement::Index));
   pushArg(obj);
-  callVM(SetObjectElementInfo, lir);
-}
-
-typedef bool (*InitElementArrayFn)(JSContext*, jsbytecode*, HandleObject,
-                                   uint32_t, HandleValue);
-static const VMFunction InitElementArrayInfo =
-    FunctionInfo<InitElementArrayFn>(js::InitElementArray, "InitElementArray");
+
+  using Fn = bool (*)(JSContext*, HandleObject, HandleValue, HandleValue,
+                      HandleValue, bool);
+  callVM<Fn, js::SetObjectElementWithReceiver>(lir);
+}
 
 void CodeGenerator::visitCallInitElementArray(LCallInitElementArray* lir) {
   pushArg(ToValue(lir, LCallInitElementArray::Value));
   if (lir->index()->isConstant()) {
     pushArg(Imm32(ToInt32(lir->index())));
   } else {
     pushArg(ToRegister(lir->index()));
   }
   pushArg(ToRegister(lir->object()));
   pushArg(ImmPtr(lir->mir()->resumePoint()->pc()));
-  callVM(InitElementArrayInfo, lir);
+
+  using Fn =
+      bool (*)(JSContext*, jsbytecode*, HandleObject, uint32_t, HandleValue);
+  callVM<Fn, js::InitElementArray>(lir);
 }
 
 void CodeGenerator::visitLoadFixedSlotV(LLoadFixedSlotV* ins) {
   const Register obj = ToRegister(ins->getOperand(0));
   size_t slot = ins->mir()->slot();
   ValueOperand result = ToOutValue(ins);
 
   masm.loadValue(Address(obj, NativeObject::getFixedSlotOffset(slot)), result);
@@ -11771,72 +11601,55 @@ void CodeGenerator::visitHasOwnCache(LHa
       toConstantOrRegister(ins, LHasOwnCache::Id, ins->mir()->idval()->type())
           .reg();
   Register output = ToRegister(ins->output());
 
   IonHasOwnIC cache(liveRegs, value, id, output);
   addIC(ins, allocateIC(cache));
 }
 
-typedef bool (*SetPropertyFn)(JSContext*, HandleObject, HandlePropertyName,
-                              const HandleValue, bool, jsbytecode*);
-static const VMFunction SetPropertyInfo =
-    FunctionInfo<SetPropertyFn>(SetProperty, "SetProperty");
-
 void CodeGenerator::visitCallSetProperty(LCallSetProperty* ins) {
   ConstantOrRegister value =
       TypedOrValueRegister(ToValue(ins, LCallSetProperty::Value));
 
   const Register objReg = ToRegister(ins->getOperand(0));
 
   pushArg(ImmPtr(ins->mir()->resumePoint()->pc()));
   pushArg(Imm32(ins->mir()->strict()));
 
   pushArg(value);
   pushArg(ImmGCPtr(ins->mir()->name()));
   pushArg(objReg);
 
-  callVM(SetPropertyInfo, ins);
-}
-
-typedef bool (*DeletePropertyFn)(JSContext*, HandleValue, HandlePropertyName,
-                                 bool*);
-static const VMFunction DeletePropertyStrictInfo =
-    FunctionInfo<DeletePropertyFn>(DeletePropertyJit<true>,
-                                   "DeletePropertyStrict");
-static const VMFunction DeletePropertyNonStrictInfo =
-    FunctionInfo<DeletePropertyFn>(DeletePropertyJit<false>,
-                                   "DeletePropertyNonStrict");
+  using Fn = bool (*)(JSContext*, HandleObject, HandlePropertyName,
+                      const HandleValue, bool, jsbytecode*);
+  callVM<Fn, jit::SetProperty>(ins);
+}
 
 void CodeGenerator::visitCallDeleteProperty(LCallDeleteProperty* lir) {
   pushArg(ImmGCPtr(lir->mir()->name()));
   pushArg(ToValue(lir, LCallDeleteProperty::Value));
 
+  using Fn = bool (*)(JSContext*, HandleValue, HandlePropertyName, bool*);
   if (lir->mir()->strict()) {
-    callVM(DeletePropertyStrictInfo, lir);
-  } else {
-    callVM(DeletePropertyNonStrictInfo, lir);
-  }
-}
-
-typedef bool (*DeleteElementFn)(JSContext*, HandleValue, HandleValue, bool*);
-static const VMFunction DeleteElementStrictInfo = FunctionInfo<DeleteElementFn>(
-    DeleteElementJit<true>, "DeleteElementStrict");
-static const VMFunction DeleteElementNonStrictInfo =
-    FunctionInfo<DeleteElementFn>(DeleteElementJit<false>,
-                                  "DeleteElementNonStrict");
+    callVM<Fn, DeletePropertyJit<true>>(lir);
+  } else {
+    callVM<Fn, DeletePropertyJit<false>>(lir);
+  }
+}
 
 void CodeGenerator::visitCallDeleteElement(LCallDeleteElement* lir) {
   pushArg(ToValue(lir, LCallDeleteElement::Index));
   pushArg(ToValue(lir, LCallDeleteElement::Value));
 
+  using Fn = bool (*)(JSContext*, HandleValue, HandleValue, bool*);
   if (lir->mir()->strict()) {
-    callVM(DeleteElementStrictInfo, lir);
-  } else {
-    callVM(DeleteElementNonStrictInfo, lir);
+    callVM<Fn, DeleteElementJit<true>>(lir);
+  } else {
+    callVM<Fn, DeleteElementJit<false>>(lir);
   }
 }
 
 void CodeGenerator::visitSetPropertyCache(LSetPropertyCache* ins) {
   LiveRegisterSet liveRegs = ins->safepoint()->liveRegs();
   Register objReg = ToRegister(ins->getOperand(0));
   Register temp = ToRegister(ins->temp());
   FloatRegister tempDouble = ToTempFloatRegisterOrInvalid(ins->tempDouble());
@@ -11855,51 +11668,44 @@ void CodeGenerator::visitSetPropertyCach
 
 void CodeGenerator::visitThrow(LThrow* lir) {
   pushArg(ToValue(lir, LThrow::Value));
 
   using Fn = bool (*)(JSContext*, HandleValue);
   callVM<Fn, js::ThrowOperation>(lir);
 }
 
-typedef bool (*BitNotFn)(JSContext*, MutableHandleValue, MutableHandleValue);
-static const VMFunction BitNotInfo = FunctionInfo<BitNotFn>(BitNot, "BitNot");
-
 void CodeGenerator::visitBitNotV(LBitNotV* lir) {
   pushArg(ToValue(lir, LBitNotV::Input));
-  callVM(BitNotInfo, lir);
-}
-
-typedef bool (*BitopFn)(JSContext*, MutableHandleValue, MutableHandleValue,
-                        MutableHandleValue);
-static const VMFunction BitAndInfo = FunctionInfo<BitopFn>(BitAnd, "BitAnd");
-static const VMFunction BitOrInfo = FunctionInfo<BitopFn>(BitOr, "BitOr");
-static const VMFunction BitXorInfo = FunctionInfo<BitopFn>(BitXor, "BitXor");
-static const VMFunction BitLhsInfo = FunctionInfo<BitopFn>(BitLsh, "BitLsh");
-static const VMFunction BitRhsInfo = FunctionInfo<BitopFn>(BitRsh, "BitRsh");
+
+  using Fn = bool (*)(JSContext*, MutableHandleValue, MutableHandleValue);
+  callVM<Fn, BitNot>(lir);
+}
 
 void CodeGenerator::visitBitOpV(LBitOpV* lir) {
   pushArg(ToValue(lir, LBitOpV::RhsInput));
   pushArg(ToValue(lir, LBitOpV::LhsInput));
 
+  using Fn = bool (*)(JSContext*, MutableHandleValue, MutableHandleValue,
+                      MutableHandleValue);
   switch (lir->jsop()) {
     case JSOP_BITAND:
-      callVM(BitAndInfo, lir);
+      callVM<Fn, BitAnd>(lir);
       break;
     case JSOP_BITOR:
-      callVM(BitOrInfo, lir);
+      callVM<Fn, BitOr>(lir);
       break;
     case JSOP_BITXOR:
-      callVM(BitXorInfo, lir);
+      callVM<Fn, BitXor>(lir);
       break;
     case JSOP_LSH:
-      callVM(BitLhsInfo, lir);
+      callVM<Fn, BitLsh>(lir);
       break;
     case JSOP_RSH:
-      callVM(BitRhsInfo, lir);
+      callVM<Fn, BitRsh>(lir);
       break;
     default:
       MOZ_CRASH("unexpected bitop");
   }
 }
 
 class OutOfLineTypeOfV : public OutOfLineCodeBase<CodeGenerator> {
   LTypeOfV* ins_;
@@ -13465,24 +13271,21 @@ void CodeGenerator::visitGuardToClass(LG
 
   masm.branchTestObjClass(Assembler::NotEqual, lhs, ins->mir()->getClass(),
                           temp, spectreRegToZero, &notEqual);
 
   // Can't return null-return here, so bail.
   bailoutFrom(&notEqual, ins->snapshot());
 }
 
-typedef JSString* (*ObjectClassToStringFn)(JSContext*, HandleObject);
-static const VMFunction ObjectClassToStringInfo =
-    FunctionInfo<ObjectClassToStringFn>(js::ObjectClassToString,
-                                        "ObjectClassToString");
-
 void CodeGenerator::visitObjectClassToString(LObjectClassToString* lir) {
   pushArg(ToRegister(lir->object()));
-  callVM(ObjectClassToStringInfo, lir);
+
+  using Fn = JSString* (*)(JSContext*, HandleObject);
+  callVM<Fn, js::ObjectClassToString>(lir);
 }
 
 void CodeGenerator::visitWasmParameter(LWasmParameter* lir) {}
 
 void CodeGenerator::visitWasmParameterI64(LWasmParameterI64* lir) {}
 
 void CodeGenerator::visitWasmReturn(LWasmReturn* lir) {
   // Don't emit a jump to the return label if this is the last block.
@@ -13838,25 +13641,22 @@ void CodeGenerator::visitLexicalCheck(LL
 void CodeGenerator::visitThrowRuntimeLexicalError(
     LThrowRuntimeLexicalError* ins) {
   pushArg(Imm32(ins->mir()->errorNumber()));
 
   using Fn = bool (*)(JSContext*, unsigned);
   callVM<Fn, jit::ThrowRuntimeLexicalError>(ins);
 }
 
-typedef bool (*GlobalNameConflictsCheckFromIonFn)(JSContext*, HandleScript);
-static const VMFunction GlobalNameConflictsCheckFromIonInfo =
-    FunctionInfo<GlobalNameConflictsCheckFromIonFn>(
-        GlobalNameConflictsCheckFromIon, "GlobalNameConflictsCheckFromIon");
-
 void CodeGenerator::visitGlobalNameConflictsCheck(
     LGlobalNameConflictsCheck* ins) {
   pushArg(ImmGCPtr(ins->mirRaw()->block()->info().script()));
-  callVM(GlobalNameConflictsCheckFromIonInfo, ins);
+
+  using Fn = bool (*)(JSContext*, HandleScript);
+  callVM<Fn, GlobalNameConflictsCheckFromIon>(ins);
 }
 
 void CodeGenerator::visitDebugger(LDebugger* ins) {
   Register cx = ToRegister(ins->getTemp(0));
   Register temp = ToRegister(ins->getTemp(1));
 
   masm.loadJSContext(cx);
   masm.setupUnalignedABICall(temp);
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -970,17 +970,17 @@ bool RStringSplit::recover(JSContext* cx
   RootedString str(cx, iter.read().toString());
   RootedString sep(cx, iter.read().toString());
   RootedObjectGroup group(cx, ObjectGroupRealm::getStringSplitStringGroup(cx));
   if (!group) {
     return false;
   }
   RootedValue result(cx);
 
-  JSObject* res = str_split_string(cx, group, str, sep, INT32_MAX);
+  JSObject* res = StringSplitString(cx, group, str, sep, INT32_MAX);
   if (!res) {
     return false;
   }
 
   result.setObject(*res);
   iter.storeInstructionResult(result);
   return true;
 }
@@ -1209,17 +1209,17 @@ bool MNewTypedArray::writeRecoverData(Co
 RNewTypedArray::RNewTypedArray(CompactBufferReader& reader) {}
 
 bool RNewTypedArray::recover(JSContext* cx, SnapshotIterator& iter) const {
   RootedObject templateObject(cx, &iter.read().toObject());
   RootedValue result(cx);
 
   uint32_t length = templateObject.as<TypedArrayObject>()->length();
   JSObject* resultObject =
-      TypedArrayCreateWithTemplate(cx, templateObject, length);
+      NewTypedArrayWithTemplateAndLength(cx, templateObject, length);
   if (!resultObject) {
     return false;
   }
 
   result.setObject(*resultObject);
   iter.storeInstructionResult(result);
   return true;
 }
@@ -1605,17 +1605,17 @@ RStringReplace::RStringReplace(CompactBu
 
 bool RStringReplace::recover(JSContext* cx, SnapshotIterator& iter) const {
   RootedString string(cx, iter.read().toString());
   RootedString pattern(cx, iter.read().toString());
   RootedString replace(cx, iter.read().toString());
 
   JSString* result =
       isFlatReplacement_
-          ? js::str_flat_replace_string(cx, string, pattern, replace)
+          ? js::StringFlatReplaceString(cx, string, pattern, replace)
           : js::str_replace_string_raw(cx, string, pattern, replace);
 
   if (!result) {
     return false;
   }
 
   iter.storeInstructionResult(StringValue(result));
   return true;
--- a/js/src/jit/VMFunctionList-inl.h
+++ b/js/src/jit/VMFunctionList-inl.h
@@ -1,129 +1,194 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "builtin/Eval.h"
+#include "builtin/RegExp.h"
 #include "jit/BaselineIC.h"
 #include "jit/IonIC.h"
 #include "jit/JitRealm.h"
 #include "jit/VMFunctions.h"
 #include "vm/AsyncFunction.h"
 #include "vm/AsyncIteration.h"
+#include "vm/EqualityOperations.h"
 #include "vm/Interpreter.h"
+#include "vm/TypedArrayObject.h"
 
 #include "jit/BaselineFrame-inl.h"
 #include "vm/Interpreter-inl.h"
 
 namespace js {
 namespace jit {
 
 // List of all VM functions to be used with callVM. Each entry stores the name
 // (must be unique, used for the VMFunctionId enum and profiling) and the C++
 // function to be called. This list must be sorted on the name field.
 #define VMFUNCTION_LIST(_)                                                     \
+  _(AddValues, js::AddValues)                                                  \
+  _(ArgumentsObjectCreateForIon, js::ArgumentsObject::createForIon)            \
+  _(ArrayJoin, js::jit::ArrayJoin)                                             \
+  _(ArraySliceDense, js::ArraySliceDense)                                      \
   _(AsyncFunctionAwait, js::AsyncFunctionAwait)                                \
   _(AsyncFunctionResolve, js::AsyncFunctionResolve)                            \
   _(BaselineDebugPrologue, js::jit::DebugPrologue)                             \
   _(BaselineGetFunctionThis, js::jit::BaselineGetFunctionThis)                 \
   _(BaselineThrowInitializedThis, js::jit::BaselineThrowInitializedThis)       \
   _(BaselineThrowUninitializedThis, js::jit::BaselineThrowUninitializedThis)   \
   _(BindVarOperation, js::BindVarOperation)                                    \
+  _(BitAnd, js::BitAnd)                                                        \
+  _(BitLsh, js::BitLsh)                                                        \
+  _(BitNot, js::BitNot)                                                        \
+  _(BitOr, js::BitOr)                                                          \
+  _(BitRsh, js::BitRsh)                                                        \
+  _(BitXor, js::BitXor)                                                        \
   _(BuiltinProtoOperation, js::BuiltinProtoOperation)                          \
   _(CheckClassHeritageOperation, js::CheckClassHeritageOperation)              \
   _(CheckGlobalOrEvalDeclarationConflicts,                                     \
     js::CheckGlobalOrEvalDeclarationConflicts)                                 \
   _(CheckIsCallable, js::jit::CheckIsCallable)                                 \
+  _(CheckOverRecursed, js::jit::CheckOverRecursed)                             \
   _(CheckOverRecursedBaseline, js::jit::CheckOverRecursedBaseline)             \
   _(CloneRegExpObject, js::CloneRegExpObject)                                  \
+  _(CopyLexicalEnvironmentObject, js::jit::CopyLexicalEnvironmentObject)       \
   _(CreateAsyncFromSyncIterator, js::CreateAsyncFromSyncIterator)              \
+  _(CreateDerivedTypedObj, js::jit::CreateDerivedTypedObj)                     \
   _(CreateGenerator, js::jit::CreateGenerator)                                 \
+  _(CreateThis, js::jit::CreateThis)                                           \
+  _(CreateThisForFunctionWithProto, js::CreateThisForFunctionWithProto)        \
   _(DebugAfterYield, js::jit::DebugAfterYield)                                 \
   _(DebugEpilogueOnBaselineReturn, js::jit::DebugEpilogueOnBaselineReturn)     \
   _(DebugLeaveLexicalEnv, js::jit::DebugLeaveLexicalEnv)                       \
   _(DebugLeaveThenFreshenLexicalEnv, js::jit::DebugLeaveThenFreshenLexicalEnv) \
   _(DebugLeaveThenPopLexicalEnv, js::jit::DebugLeaveThenPopLexicalEnv)         \
   _(DebugLeaveThenRecreateLexicalEnv,                                          \
     js::jit::DebugLeaveThenRecreateLexicalEnv)                                 \
   _(Debug_CheckSelfHosted, js::Debug_CheckSelfHosted)                          \
+  _(DeepCloneObjectLiteral, js::DeepCloneObjectLiteral)                        \
   _(DefFunOperation, js::DefFunOperation)                                      \
   _(DefLexicalOperation, js::DefLexicalOperation)                              \
   _(DefVarOperation, js::DefVarOperation)                                      \
   _(DeleteElementNonStrict, js::DeleteElementJit<false>)                       \
   _(DeleteElementStrict, js::DeleteElementJit<true>)                           \
   _(DeleteNameOperation, js::DeleteNameOperation)                              \
   _(DeletePropertyNonStrict, js::DeletePropertyJit<false>)                     \
   _(DeletePropertyStrict, js::DeletePropertyJit<true>)                         \
+  _(DirectEvalStringFromIon, js::DirectEvalStringFromIon)                      \
+  _(DivValues, js::DivValues)                                                  \
   _(DoToNumber, js::jit::DoToNumber)                                           \
   _(DoToNumeric, js::jit::DoToNumeric)                                         \
   _(EnterWith, js::jit::EnterWith)                                             \
   _(FinalSuspend, js::jit::FinalSuspend)                                       \
   _(FreshenLexicalEnv, js::jit::FreshenLexicalEnv)                             \
   _(FunWithProtoOperation, js::FunWithProtoOperation)                          \
   _(GetAndClearException, js::GetAndClearException)                            \
+  _(GetElementOperation, js::GetElementOperation)                              \
   _(GetImportOperation, js::GetImportOperation)                                \
+  _(GetIntrinsicValue, js::jit::GetIntrinsicValue)                             \
   _(GetNonSyntacticGlobalThis, js::GetNonSyntacticGlobalThis)                  \
   _(GetOrCreateModuleMetaObject, js::GetOrCreateModuleMetaObject)              \
+  _(GetValueProperty, js::GetValueProperty)                                    \
+  _(GlobalNameConflictsCheckFromIon, js::jit::GlobalNameConflictsCheckFromIon) \
+  _(GreaterThan, js::jit::GreaterThan)                                         \
+  _(GreaterThanOrEqual, js::jit::GreaterThanOrEqual)                           \
   _(HomeObjectSuperBase, js::HomeObjectSuperBase)                              \
   _(ImplicitThisOperation, js::ImplicitThisOperation)                          \
   _(ImportMetaOperation, js::ImportMetaOperation)                              \
   _(InitElemGetterSetterOperation, js::InitElemGetterSetterOperation)          \
+  _(InitElemOperation, js::InitElemOperation)                                  \
+  _(InitElementArray, js::InitElementArray)                                    \
   _(InitFunctionEnvironmentObjects, js::jit::InitFunctionEnvironmentObjects)   \
   _(InitPropGetterSetterOperation, js::InitPropGetterSetterOperation)          \
+  _(InitRestParameter, js::jit::InitRestParameter)                             \
   _(InterpretResume, js::jit::InterpretResume)                                 \
   _(InterruptCheck, js::jit::InterruptCheck)                                   \
+  _(InvokeFunction, js::jit::InvokeFunction)                                   \
+  _(InvokeFunctionShuffleNewTarget, js::jit::InvokeFunctionShuffleNewTarget)   \
   _(IonBinaryArithICUpdate, js::jit::IonBinaryArithIC::update)                 \
   _(IonBindNameICUpdate, js::jit::IonBindNameIC::update)                       \
   _(IonCompareICUpdate, js::jit::IonCompareIC::update)                         \
   _(IonCompileScriptForBaseline, js::jit::IonCompileScriptForBaseline)         \
   _(IonGetIteratorICUpdate, js::jit::IonGetIteratorIC::update)                 \
   _(IonGetNameICUpdate, js::jit::IonGetNameIC::update)                         \
   _(IonGetPropSuperICUpdate, js::jit::IonGetPropSuperIC::update)               \
   _(IonGetPropertyICUpdate, js::jit::IonGetPropertyIC::update)                 \
   _(IonHasOwnICUpdate, js::jit::IonHasOwnIC::update)                           \
   _(IonInICUpdate, js::jit::IonInIC::update)                                   \
   _(IonInstanceOfICUpdate, js::jit::IonInstanceOfIC::update)                   \
   _(IonSetPropertyICUpdate, js::jit::IonSetPropertyIC::update)                 \
   _(IonUnaryArithICUpdate, js::jit::IonUnaryArithIC::update)                   \
   _(Lambda, js::Lambda)                                                        \
   _(LambdaArrow, js::LambdaArrow)                                              \
   _(LeaveWith, js::jit::LeaveWith)                                             \
+  _(LessThan, js::jit::LessThan)                                               \
+  _(LessThanOrEqual, js::jit::LessThanOrEqual)                                 \
+  _(LexicalEnvironmentObjectCreate, js::LexicalEnvironmentObject::create)      \
+  _(LooselyEqual, js::jit::LooselyEqual<true>)                                 \
+  _(LooselyNotEqual, js::jit::LooselyEqual<false>)                             \
   _(MakeDefaultConstructor, js::MakeDefaultConstructor)                        \
+  _(ModValues, js::ModValues)                                                  \
+  _(MulValues, js::MulValues)                                                  \
   _(MutatePrototype, js::jit::MutatePrototype)                                 \
   _(NewArgumentsObject, js::jit::NewArgumentsObject)                           \
   _(NewArrayCopyOnWriteOperation, js::NewArrayCopyOnWriteOperation)            \
+  _(NewArrayOperation, js::NewArrayOperation)                                  \
+  _(NewArrayWithGroup, js::NewArrayWithGroup)                                  \
   _(NewDenseCopyOnWriteArray, js::NewDenseCopyOnWriteArray)                    \
+  _(NewObjectOperation, js::NewObjectOperation)                                \
+  _(NewObjectOperationWithTemplate, js::NewObjectOperationWithTemplate)        \
+  _(NewTypedArrayWithTemplateAndArray, js::NewTypedArrayWithTemplateAndArray)  \
+  _(NewTypedArrayWithTemplateAndBuffer,                                        \
+    js::NewTypedArrayWithTemplateAndBuffer)                                    \
   _(NormalSuspend, js::jit::NormalSuspend)                                     \
+  _(ObjectClassToString, js::ObjectClassToString)                              \
+  _(ObjectCreateWithTemplate, js::ObjectCreateWithTemplate)                    \
   _(ObjectWithProtoOperation, js::ObjectWithProtoOperation)                    \
   _(OnDebuggerStatement, js::jit::OnDebuggerStatement)                         \
   _(OptimizeSpreadCall, js::OptimizeSpreadCall)                                \
   _(PopLexicalEnv, js::jit::PopLexicalEnv)                                     \
   _(PopVarEnv, js::jit::PopVarEnv)                                             \
+  _(PowValues, js::PowValues)                                                  \
   _(ProcessCallSiteObjOperation, js::ProcessCallSiteObjOperation)              \
   _(PushLexicalEnv, js::jit::PushLexicalEnv)                                   \
   _(PushVarEnv, js::jit::PushVarEnv)                                           \
   _(RecreateLexicalEnv, js::jit::RecreateLexicalEnv)                           \
+  _(RegExpMatcherRaw, js::RegExpMatcherRaw)                                    \
+  _(RegExpSearcherRaw, js::RegExpSearcherRaw)                                  \
+  _(RegExpTesterRaw, js::RegExpTesterRaw)                                      \
+  _(SameValue, js::SameValue)                                                  \
+  _(SetDenseElement, js::jit::SetDenseElement)                                 \
   _(SetFunctionName, js::SetFunctionName)                                      \
   _(SetIntrinsicOperation, js::SetIntrinsicOperation)                          \
   _(SetObjectElementWithReceiver, js::SetObjectElementWithReceiver)            \
+  _(SetProperty, js::jit::SetProperty)                                         \
   _(SetPropertySuper, js::SetPropertySuper)                                    \
   _(SingletonObjectLiteralOperation, js::SingletonObjectLiteralOperation)      \
   _(StartDynamicModuleImport, js::StartDynamicModuleImport)                    \
+  _(StrictlyEqual, js::jit::StrictlyEqual<true>)                               \
+  _(StrictlyNotEqual, js::jit::StrictlyEqual<false>)                           \
+  _(StringFlatReplaceString, js::StringFlatReplaceString)                      \
+  _(StringReplace, js::jit::StringReplace)                                     \
+  _(StringSplitString, js::StringSplitString)                                  \
+  _(StringToLowerCase, js::StringToLowerCase)                                  \
+  _(StringToUpperCase, js::StringToUpperCase)                                  \
+  _(SubValues, js::SubValues)                                                  \
   _(SuperFunOperation, js::SuperFunOperation)                                  \
   _(ThrowBadDerivedReturn, js::jit::ThrowBadDerivedReturn)                     \
   _(ThrowCheckIsObject, js::ThrowCheckIsObject)                                \
   _(ThrowMsgOperation, js::ThrowMsgOperation)                                  \
   _(ThrowObjectCoercible, js::jit::ThrowObjectCoercible)                       \
   _(ThrowOperation, js::ThrowOperation)                                        \
   _(ThrowRuntimeLexicalError, js::jit::ThrowRuntimeLexicalError)               \
   _(ToIdOperation, js::ToIdOperation)                                          \
   _(ToStringSlow, js::ToStringSlow<CanGC>)                                     \
-  _(TrySkipAwait, js::jit::TrySkipAwait)
+  _(TrySkipAwait, js::jit::TrySkipAwait)                                       \
+  _(UrshValues, js::UrshValues)
 
 enum class VMFunctionId {
 #define DEF_ID(name, fp) name,
   VMFUNCTION_LIST(DEF_ID)
 #undef DEF_ID
       Count
 };
 
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -391,17 +391,17 @@ typedef bool (*StringCompareFn)(JSContex
 const VMFunction StringsEqualInfo =
     FunctionInfo<StringCompareFn>(jit::StringsEqual<true>, "StringsEqual");
 const VMFunction StringsNotEqualInfo =
     FunctionInfo<StringCompareFn>(jit::StringsEqual<false>, "StringsEqual");
 
 bool StringSplitHelper(JSContext* cx, HandleString str, HandleString sep,
                        HandleObjectGroup group, uint32_t limit,
                        MutableHandleValue result) {
-  JSObject* resultObj = str_split_string(cx, group, str, sep, limit);
+  JSObject* resultObj = StringSplitString(cx, group, str, sep, limit);
   if (!resultObj) {
     return false;
   }
 
   result.setObject(*resultObj);
   return true;
 }
 
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -13,16 +13,17 @@
 #include "jspubtd.h"
 
 #include "jit/CompileInfo.h"
 #include "jit/JitFrames.h"
 #include "vm/Interpreter.h"
 
 namespace js {
 
+class ArgumentsObject;
 class NamedLambdaObject;
 class WithScope;
 class InlineTypedObject;
 class AbstractGeneratorObject;
 class AsyncFunctionGeneratorObject;
 class RegExpObject;
 class TypedArrayObject;
 
@@ -434,16 +435,20 @@ template <>
 struct TypeToDataType<NamedLambdaObject*> {
   static const DataType result = Type_Object;
 };
 template <>
 struct TypeToDataType<LexicalEnvironmentObject*> {
   static const DataType result = Type_Object;
 };
 template <>
+struct TypeToDataType<ArgumentsObject*> {
+  static const DataType result = Type_Object;
+};
+template <>
 struct TypeToDataType<ArrayObject*> {
   static const DataType result = Type_Object;
 };
 template <>
 struct TypeToDataType<TypedArrayObject*> {
   static const DataType result = Type_Object;
 };
 template <>
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -938,17 +938,17 @@ LexicalEnvironmentObject* LexicalEnviron
     env->initSlot(slot, MagicValue(JS_UNINITIALIZED_LEXICAL));
   }
 
   env->initScopeUnchecked(scope);
   return env;
 }
 
 /* static */
-LexicalEnvironmentObject* LexicalEnvironmentObject::create(
+LexicalEnvironmentObject* LexicalEnvironmentObject::createForFrame(
     JSContext* cx, Handle<LexicalScope*> scope, AbstractFramePtr frame) {
   RootedObject enclosing(cx, frame.environmentChain());
   return create(cx, scope, enclosing, gc::DefaultHeap);
 }
 
 /* static */
 LexicalEnvironmentObject* LexicalEnvironmentObject::createGlobal(
     JSContext* cx, Handle<GlobalObject*> global) {
--- a/js/src/vm/EnvironmentObject.h
+++ b/js/src/vm/EnvironmentObject.h
@@ -522,19 +522,19 @@ class LexicalEnvironmentObject : public 
     initScopeUnchecked(scope);
   }
 
  public:
   static LexicalEnvironmentObject* create(JSContext* cx,
                                           Handle<LexicalScope*> scope,
                                           HandleObject enclosing,
                                           gc::InitialHeap heap);
-  static LexicalEnvironmentObject* create(JSContext* cx,
-                                          Handle<LexicalScope*> scope,
-                                          AbstractFramePtr frame);
+  static LexicalEnvironmentObject* createForFrame(JSContext* cx,
+                                                  Handle<LexicalScope*> scope,
+                                                  AbstractFramePtr frame);
   static LexicalEnvironmentObject* createGlobal(JSContext* cx,
                                                 Handle<GlobalObject*> global);
   static LexicalEnvironmentObject* createNonSyntactic(JSContext* cx,
                                                       HandleObject enclosing,
                                                       HandleObject thisv);
   static LexicalEnvironmentObject* createHollowForDebug(
       JSContext* cx, Handle<LexicalScope*> scope);
 
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -291,17 +291,17 @@ JSFunction* js::MakeDefaultConstructor(J
           /* nargs = */ !!derived, proto, TenuredObject, &ctor)) {
     return nullptr;
   }
 
   ctor->setIsConstructor();
   ctor->setIsClassConstructor();
 
   // Create the script now, so we can fix up its source span below.
-  JSScript* ctorScript = JSFunction::getOrCreateScript(cx, ctor);
+  RootedScript ctorScript(cx, JSFunction::getOrCreateScript(cx, ctor));
   if (!ctorScript) {
     return nullptr;
   }
 
   // This function's frames are fine to expose to JS; it should not be treated
   // as an opaque self-hosted builtin. But the script cloning code naturally
   // expects to be applied to self-hosted functions, so do the clone first,
   // and clear this afterwards.
@@ -312,16 +312,18 @@ JSFunction* js::MakeDefaultConstructor(J
   // (self-hosted) constructor function.
   uint32_t classStartOffset = GetSrcNoteOffset(classNote, 0);
   uint32_t classEndOffset = GetSrcNoteOffset(classNote, 1);
   unsigned column;
   unsigned line = PCToLineNumber(script, pc, &column);
   ctorScript->setDefaultClassConstructorSpan(
       script->sourceObject(), classStartOffset, classEndOffset, line, column);
 
+  Debugger::onNewScript(cx, ctorScript);
+
   return ctor;
 }
 
 bool js::ReportIsNotFunction(JSContext* cx, HandleValue v, int numToSkip,
                              MaybeConstruct construct) {
   unsigned error = construct ? JSMSG_NOT_CONSTRUCTOR : JSMSG_NOT_FUNCTION;
   int spIndex = numToSkip >= 0 ? -(numToSkip + 1) : JSDVG_SEARCH_STACK;
 
@@ -4462,16 +4464,21 @@ bool js::GetProperty(JSContext* cx, Hand
   RootedObject obj(cx, ToObjectFromStack(cx, v));
   if (!obj) {
     return false;
   }
 
   return GetProperty(cx, obj, receiver, name, vp);
 }
 
+bool js::GetValueProperty(JSContext* cx, HandleValue value,
+                          HandlePropertyName name, MutableHandleValue vp) {
+  return GetProperty(cx, value, name, vp);
+}
+
 JSObject* js::Lambda(JSContext* cx, HandleFunction fun, HandleObject parent) {
   MOZ_ASSERT(!fun->isArrow());
 
   JSFunction* clone;
   if (fun->isNative()) {
     MOZ_ASSERT(IsAsmJSModule(fun));
     clone = CloneAsmJSModuleFunction(cx, fun);
   } else {
@@ -4777,26 +4784,16 @@ bool js::DeleteElementJit(JSContext* cx,
   return true;
 }
 
 template bool js::DeleteElementJit<true>(JSContext*, HandleValue, HandleValue,
                                          bool* succeeded);
 template bool js::DeleteElementJit<false>(JSContext*, HandleValue, HandleValue,
                                           bool* succeeded);
 
-bool js::GetElement(JSContext* cx, MutableHandleValue lref, HandleValue rref,
-                    MutableHandleValue vp) {
-  return GetElementOperation(cx, JSOP_GETELEM, lref, rref, vp);
-}
-
-bool js::CallElement(JSContext* cx, MutableHandleValue lref, HandleValue rref,
-                     MutableHandleValue res) {
-  return GetElementOperation(cx, JSOP_CALLELEM, lref, rref, res);
-}
-
 bool js::SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index,
                           HandleValue value, bool strict) {
   RootedId id(cx);
   if (!ToPropertyKey(cx, index, &id)) {
     return false;
   }
   RootedValue receiver(cx, ObjectValue(*obj));
   return SetObjectElementOperation(cx, obj, id, value, receiver, strict);
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -425,27 +425,24 @@ bool HandleClosingGeneratorReturn(JSCont
 
 /************************************************************************/
 
 bool ThrowOperation(JSContext* cx, HandleValue v);
 
 bool GetProperty(JSContext* cx, HandleValue value, HandlePropertyName name,
                  MutableHandleValue vp);
 
+bool GetValueProperty(JSContext* cx, HandleValue value, HandlePropertyName name,
+                      MutableHandleValue vp);
+
 JSObject* Lambda(JSContext* cx, HandleFunction fun, HandleObject parent);
 
 JSObject* LambdaArrow(JSContext* cx, HandleFunction fun, HandleObject parent,
                       HandleValue newTargetv);
 
-bool GetElement(JSContext* cx, MutableHandleValue lref, HandleValue rref,
-                MutableHandleValue res);
-
-bool CallElement(JSContext* cx, MutableHandleValue lref, HandleValue rref,
-                 MutableHandleValue res);
-
 bool SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index,
                       HandleValue value, bool strict);
 bool SetObjectElement(JSContext* cx, HandleObject obj, HandleValue index,
                       HandleValue value, bool strict, HandleScript script,
                       jsbytecode* pc);
 
 bool SetObjectElementWithReceiver(JSContext* cx, HandleObject obj,
                                   HandleValue index, HandleValue value,
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1819,17 +1819,17 @@ bool js::intrinsic_StringSplitString(JSC
   RootedString string(cx, args[0].toString());
   RootedString sep(cx, args[1].toString());
 
   RootedObjectGroup group(cx, ObjectGroupRealm::getStringSplitStringGroup(cx));
   if (!group) {
     return false;
   }
 
-  JSObject* aobj = str_split_string(cx, group, string, sep, INT32_MAX);
+  JSObject* aobj = StringSplitString(cx, group, string, sep, INT32_MAX);
   if (!aobj) {
     return false;
   }
 
   args.rval().setObject(*aobj);
   return true;
 }
 
@@ -1847,17 +1847,17 @@ static bool intrinsic_StringSplitStringL
   MOZ_ASSERT(limit > 0,
              "Zero limit case is already handled in self-hosted code.");
 
   RootedObjectGroup group(cx, ObjectGroupRealm::getStringSplitStringGroup(cx));
   if (!group) {
     return false;
   }
 
-  JSObject* aobj = str_split_string(cx, group, string, sep, limit);
+  JSObject* aobj = StringSplitString(cx, group, string, sep, limit);
   if (!aobj) {
     return false;
   }
 
   args.rval().setObject(*aobj);
   return true;
 }
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -284,17 +284,17 @@ bool InterpreterFrame::checkReturn(JSCon
 
 bool InterpreterFrame::pushVarEnvironment(JSContext* cx, HandleScope scope) {
   return js::PushVarEnvironmentObject(cx, scope, this);
 }
 
 bool InterpreterFrame::pushLexicalEnvironment(JSContext* cx,
                                               Handle<LexicalScope*> scope) {
   LexicalEnvironmentObject* env =
-      LexicalEnvironmentObject::create(cx, scope, this);
+      LexicalEnvironmentObject::createForFrame(cx, scope, this);
   if (!env) {
     return false;
   }
 
   pushOnEnvironmentChain(*env);
   return true;
 }
 
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -975,57 +975,53 @@ class TypedArrayObjectTemplate : public 
 
 #define CREATE_TYPE_FOR_TYPED_ARRAY(T, N) \
   typedef TypedArrayObjectTemplate<T> N##Array;
 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPE_FOR_TYPED_ARRAY)
 #undef CREATE_TYPE_FOR_TYPED_ARRAY
 
 } /* anonymous namespace */
 
-TypedArrayObject* js::TypedArrayCreateWithTemplate(JSContext* cx,
-                                                   HandleObject templateObj,
-                                                   int32_t len) {
+TypedArrayObject* js::NewTypedArrayWithTemplateAndLength(
+    JSContext* cx, HandleObject templateObj, int32_t len) {
   MOZ_ASSERT(templateObj->is<TypedArrayObject>());
   TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
 
   switch (tobj->type()) {
 #define CREATE_TYPED_ARRAY(T, N)                                             \
   case Scalar::N:                                                            \
     return TypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate(cx, tobj, \
                                                                    len);
     JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY)
 #undef CREATE_TYPED_ARRAY
     default:
       MOZ_CRASH("Unsupported TypedArray type");
   }
 }
 
-TypedArrayObject* js::TypedArrayCreateWithTemplate(JSContext* cx,
-                                                   HandleObject templateObj,
-                                                   HandleObject array) {
+TypedArrayObject* js::NewTypedArrayWithTemplateAndArray(
+    JSContext* cx, HandleObject templateObj, HandleObject array) {
   MOZ_ASSERT(templateObj->is<TypedArrayObject>());
   TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
 
   switch (tobj->type()) {
 #define CREATE_TYPED_ARRAY(T, N)                                             \
   case Scalar::N:                                                            \
     return TypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate(cx, tobj, \
                                                                    array);
     JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY)
 #undef CREATE_TYPED_ARRAY
     default:
       MOZ_CRASH("Unsupported TypedArray type");
   }
 }
 
-TypedArrayObject* js::TypedArrayCreateWithTemplate(JSContext* cx,
-                                                   HandleObject templateObj,
-                                                   HandleObject arrayBuffer,
-                                                   HandleValue byteOffset,
-                                                   HandleValue length) {
+TypedArrayObject* js::NewTypedArrayWithTemplateAndBuffer(
+    JSContext* cx, HandleObject templateObj, HandleObject arrayBuffer,
+    HandleValue byteOffset, HandleValue length) {
   MOZ_ASSERT(templateObj->is<TypedArrayObject>());
   TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
 
   switch (tobj->type()) {
 #define CREATE_TYPED_ARRAY(T, N)                                    \
   case Scalar::N:                                                   \
     return TypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate( \
         cx, tobj, arrayBuffer, byteOffset, length);
--- a/js/src/vm/TypedArrayObject.h
+++ b/js/src/vm/TypedArrayObject.h
@@ -188,29 +188,25 @@ class TypedArrayObject : public ArrayBuf
 
  private:
   static bool set_impl(JSContext* cx, const CallArgs& args);
 };
 
 MOZ_MUST_USE bool TypedArray_bufferGetter(JSContext* cx, unsigned argc,
                                           Value* vp);
 
-extern TypedArrayObject* TypedArrayCreateWithTemplate(JSContext* cx,
-                                                      HandleObject templateObj,
-                                                      int32_t len);
+extern TypedArrayObject* NewTypedArrayWithTemplateAndLength(
+    JSContext* cx, HandleObject templateObj, int32_t len);
 
-extern TypedArrayObject* TypedArrayCreateWithTemplate(JSContext* cx,
-                                                      HandleObject templateObj,
-                                                      HandleObject array);
+extern TypedArrayObject* NewTypedArrayWithTemplateAndArray(
+    JSContext* cx, HandleObject templateObj, HandleObject array);
 
-extern TypedArrayObject* TypedArrayCreateWithTemplate(JSContext* cx,
-                                                      HandleObject templateObj,
-                                                      HandleObject arrayBuffer,
-                                                      HandleValue byteOffset,
-                                                      HandleValue length);
+extern TypedArrayObject* NewTypedArrayWithTemplateAndBuffer(
+    JSContext* cx, HandleObject templateObj, HandleObject arrayBuffer,
+    HandleValue byteOffset, HandleValue length);
 
 inline bool IsTypedArrayClass(const Class* clasp) {
   return &TypedArrayObject::classes[0] <= clasp &&
          clasp < &TypedArrayObject::classes[Scalar::MaxTypedArrayViewType];
 }
 
 inline Scalar::Type GetTypedArrayClassType(const Class* clasp) {
   MOZ_ASSERT(IsTypedArrayClass(clasp));
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -201,16 +201,17 @@ CompartmentPrivate::CompartmentPrivate(J
       wasShutdown(false),
       mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH)) {
   MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
 }
 
 CompartmentPrivate::~CompartmentPrivate() {
   MOZ_COUNT_DTOR(xpc::CompartmentPrivate);
   delete mWrappedJSMap;
+  delete scope;
 }
 
 void CompartmentPrivate::SystemIsBeingShutDown() {
   // We may call this multiple times when the compartment contains more than one
   // realm.
   if (!wasShutdown) {
     mWrappedJSMap->ShutdownMarker();
     wasShutdown = true;
@@ -738,17 +739,17 @@ void XPCJSRuntime::TraceNativeBlackRoots
       roots->TraceJSAll(trc);
     }
   }
 
   dom::TraceBlackJS(trc, nsIXPConnect::XPConnect()->GetIsShuttingDown());
 }
 
 void XPCJSRuntime::TraceAdditionalNativeGrayRoots(JSTracer* trc) {
-  XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(trc);
+  XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(this, trc);
 
   for (XPCRootSetElem* e = mVariantRoots; e; e = e->GetNextRoot()) {
     static_cast<XPCTraceableVariant*>(e)->TraceJS(trc);
   }
 
   for (XPCRootSetElem* e = mWrappedJSRoots; e; e = e->GetNextRoot()) {
     static_cast<nsXPCWrappedJS*>(e)->TraceJS(trc);
   }
@@ -880,19 +881,16 @@ void XPCJSRuntime::FinalizeCallback(JSFr
       MOZ_ASSERT(self->mDoingFinalization, "bad state");
 
       MOZ_ASSERT(self->mGCIsRunning, "bad state");
       self->mGCIsRunning = false;
 
       break;
     }
     case JSFINALIZE_GROUP_END: {
-      // Sweep scopes needing cleanup
-      XPCWrappedNativeScope::KillDyingScopes();
-
       MOZ_ASSERT(self->mDoingFinalization, "bad state");
       self->mDoingFinalization = false;
 
       break;
     }
     case JSFINALIZE_COLLECTION_END: {
       MOZ_ASSERT(!self->mGCIsRunning, "bad state");
       self->mGCIsRunning = true;
@@ -966,18 +964,16 @@ void XPCJSRuntime::FinalizeCallback(JSFr
 void XPCJSRuntime::WeakPointerZonesCallback(JSContext* cx, void* data) {
   // Called before each sweeping slice -- after processing any final marking
   // triggered by barriers -- to clear out any references to things that are
   // about to be finalized and update any pointers to moved GC things.
   XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data);
 
   self->mWrappedJSMap->UpdateWeakPointersAfterGC();
   self->mUAWidgetScopeMap.sweep();
-
-  XPCWrappedNativeScope::UpdateWeakPointersInAllScopesAfterGC();
 }
 
 /* static */
 void XPCJSRuntime::WeakPointerCompartmentCallback(JSContext* cx,
                                                   JS::Compartment* comp,
                                                   void* data) {
   // Called immediately after the ZoneGroup weak pointer callback, but only
   // once for each compartment that is being swept.
@@ -985,16 +981,17 @@ void XPCJSRuntime::WeakPointerCompartmen
   if (xpcComp) {
     xpcComp->UpdateWeakPointersAfterGC();
   }
 }
 
 void CompartmentPrivate::UpdateWeakPointersAfterGC() {
   mRemoteProxies.sweep();
   mWrappedJSMap->UpdateWeakPointersAfterGC();
+  scope->UpdateWeakPointersAfterGC();
 }
 
 void XPCJSRuntime::CustomOutOfMemoryCallback() {
   if (!Preferences::GetBool("memory.dump_reports_on_oom")) {
     return;
   }
 
   nsCOMPtr<nsIMemoryInfoDumper> dumper =
@@ -1161,16 +1158,19 @@ void XPCJSRuntime::Shutdown(JSContext* c
   mClassInfo2NativeSetMap = nullptr;
 
   delete mNativeSetMap;
   mNativeSetMap = nullptr;
 
   delete mDyingWrappedNativeProtoMap;
   mDyingWrappedNativeProtoMap = nullptr;
 
+  // Prevent ~LinkedList assertion failures if we leaked things.
+  mWrappedNativeScopes.clear();
+
   CycleCollectedJSRuntime::Shutdown(cx);
 }
 
 XPCJSRuntime::~XPCJSRuntime() {
   MOZ_COUNT_DTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime);
 }
 
 // If |*anonymizeID| is non-zero and this is a user realm, the name will
@@ -2937,16 +2937,17 @@ XPCJSRuntime::XPCJSRuntime(JSContext* aC
       mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH)),
       mWrappedJSClassMap(
           IID2WrappedJSClassMap::newMap(XPC_JS_CLASS_MAP_LENGTH)),
       mIID2NativeInterfaceMap(
           IID2NativeInterfaceMap::newMap(XPC_NATIVE_INTERFACE_MAP_LENGTH)),
       mClassInfo2NativeSetMap(
           ClassInfo2NativeSetMap::newMap(XPC_NATIVE_SET_MAP_LENGTH)),
       mNativeSetMap(NativeSetMap::newMap(XPC_NATIVE_SET_MAP_LENGTH)),
+      mWrappedNativeScopes(),
       mDyingWrappedNativeProtoMap(
           XPCWrappedNativeProtoMap::newMap(XPC_DYING_NATIVE_PROTO_MAP_LENGTH)),
       mGCIsRunning(false),
       mNativesToReleaseArray(),
       mDoingFinalization(false),
       mVariantRoots(nullptr),
       mWrappedJSRoots(nullptr),
       mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()) {
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -9,29 +9,31 @@
 #include "xpcprivate.h"
 #include "XPCWrapper.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionNoteRootCallback.h"
 #include "ExpandedPrincipal.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
 #include "nsIXULRuntime.h"
 #include "mozJSComponentLoader.h"
 
 #include "mozilla/dom/BindingUtils.h"
 
 using namespace mozilla;
 using namespace xpc;
 using namespace JS;
 
 /***************************************************************************/
 
-XPCWrappedNativeScope* XPCWrappedNativeScope::gScopes = nullptr;
-XPCWrappedNativeScope* XPCWrappedNativeScope::gDyingScopes = nullptr;
+static XPCWrappedNativeScopeList& AllScopes() {
+  return XPCJSRuntime::Get()->GetWrappedNativeScopes();
+}
 
 static bool RemoteXULForbidsXBLScopeForPrincipal(nsIPrincipal* aPrincipal) {
   // AllowXULXBLForPrincipal will return true for system principal, but we
   // don't want that here.
   MOZ_ASSERT(nsContentUtils::IsInitialized());
   if (aPrincipal->IsSystemPrincipal()) {
     return false;
   }
@@ -61,50 +63,37 @@ static bool RemoteXULForbidsXBLScope(Han
 }
 
 XPCWrappedNativeScope::XPCWrappedNativeScope(JS::Compartment* aCompartment,
                                              JS::HandleObject aFirstGlobal)
     : mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_LENGTH)),
       mWrappedNativeProtoMap(
           ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_LENGTH)),
       mComponents(nullptr),
-      mNext(nullptr),
       mCompartment(aCompartment) {
 #ifdef DEBUG
-  for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
     MOZ_ASSERT(aCompartment != cur->Compartment(), "dup object");
   }
 #endif
 
-  // add ourselves to the scopes list
-  mNext = gScopes;
-  gScopes = this;
+  AllScopes().insertBack(this);
 
   MOZ_COUNT_CTOR(XPCWrappedNativeScope);
 
   // Determine whether we would allow an XBL scope in this situation.
   // In addition to being pref-controlled, we also disable XBL scopes for
   // remote XUL domains, _except_ if we have an additional pref override set.
   //
   // Note that we can't quite remove this yet, even though we never actually
   // use XBL scopes, because some code (including the security manager) uses
   // this boolean to make decisions that we rely on in our test infrastructure.
   mAllowContentXBLScope = !RemoteXULForbidsXBLScope(aFirstGlobal);
 }
 
-// static
-bool XPCWrappedNativeScope::IsDyingScope(XPCWrappedNativeScope* scope) {
-  for (XPCWrappedNativeScope* cur = gDyingScopes; cur; cur = cur->mNext) {
-    if (scope == cur) {
-      return true;
-    }
-  }
-  return false;
-}
-
 bool XPCWrappedNativeScope::GetComponentsJSObject(JS::MutableHandleObject obj) {
   AutoJSContext cx;
   if (!mComponents) {
     bool system = AccessCheck::isChrome(mCompartment);
     mComponents =
         system ? new nsXPCComponents(this) : new nsXPCComponentsBase(this);
   }
 
@@ -258,17 +247,18 @@ JSObject* GetUAWidgetScope(JSContext* cx
   scope = js::UncheckedUnwrap(scope);
   JS::ExposeObjectToActiveJS(scope);
   return scope;
 }
 
 bool AllowContentXBLScope(JS::Realm* realm) {
   JS::Compartment* comp = GetCompartmentForRealm(realm);
   XPCWrappedNativeScope* scope = CompartmentPrivate::Get(comp)->scope;
-  return scope && scope->AllowContentXBLScope(realm);
+  MOZ_ASSERT(scope);
+  return scope->AllowContentXBLScope(realm);
 }
 
 } /* namespace xpc */
 
 XPCWrappedNativeScope::~XPCWrappedNativeScope() {
   MOZ_COUNT_DTOR(XPCWrappedNativeScope);
 
   // We can do additional cleanup assertions here...
@@ -284,91 +274,75 @@ XPCWrappedNativeScope::~XPCWrappedNative
   if (mComponents) {
     mComponents->mScope = nullptr;
   }
 
   // XXX we should assert that we are dead or that xpconnect has shutdown
   // XXX might not want to do this at xpconnect shutdown time???
   mComponents = nullptr;
 
-  if (mXrayExpandos.initialized()) {
-    mXrayExpandos.destroy();
-  }
+  MOZ_RELEASE_ASSERT(!mXrayExpandos.initialized());
 
-  JSContext* cx = dom::danger::GetJSContext();
-  mIDProto.finalize(cx);
-  mIIDProto.finalize(cx);
-  mCIDProto.finalize(cx);
   mCompartment = nullptr;
 }
 
 // static
-void XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(JSTracer* trc) {
+void XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(XPCJSRuntime* xpcrt,
+                                                           JSTracer* trc) {
   // Do JS::TraceEdge for all wrapped natives with external references, as
   // well as any DOM expando objects.
-  for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
+  //
+  // Note: the GC can call this from a JS helper thread. We don't use
+  // AllScopes() because that asserts we're on the main thread.
+
+  for (XPCWrappedNativeScope* cur : xpcrt->GetWrappedNativeScopes()) {
     for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
       auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
       XPCWrappedNative* wrapper = entry->value;
       if (wrapper->HasExternalReference() && !wrapper->IsWrapperExpired()) {
         wrapper->TraceSelf(trc);
       }
     }
   }
 }
 
 // static
 void XPCWrappedNativeScope::SuspectAllWrappers(
     nsCycleCollectionNoteRootCallback& cb) {
-  for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
     for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
       static_cast<Native2WrappedNativeMap::Entry*>(i.Get())->value->Suspect(cb);
     }
   }
 }
 
-// static
-void XPCWrappedNativeScope::UpdateWeakPointersInAllScopesAfterGC() {
-  // If this is called from the finalization callback in JSGC_MARK_END then
-  // JSGC_FINALIZE_END must always follow it calling
-  // FinishedFinalizationPhaseOfGC and clearing gDyingScopes in
-  // KillDyingScopes.
-  MOZ_ASSERT(!gDyingScopes, "JSGC_MARK_END without JSGC_FINALIZE_END");
-
-  XPCWrappedNativeScope** scopep = &gScopes;
-  while (*scopep) {
-    XPCWrappedNativeScope* cur = *scopep;
-    cur->UpdateWeakPointersAfterGC();
-    if (cur->Compartment()) {
-      scopep = &cur->mNext;
-    } else {
-      // The scope's global is dead so move it to the dying scopes list.
-      *scopep = cur->mNext;
-      cur->mNext = gDyingScopes;
-      gDyingScopes = cur;
-    }
-  }
-}
-
 void XPCWrappedNativeScope::UpdateWeakPointersAfterGC() {
   // Sweep waivers.
   if (mWaiverWrapperMap) {
     mWaiverWrapperMap->Sweep();
   }
 
   if (!js::IsCompartmentZoneSweepingOrCompacting(mCompartment)) {
     return;
   }
 
-  // Update our pointer to the compartment in case we finalized all globals.
   if (!js::CompartmentHasLiveGlobal(mCompartment)) {
-    CompartmentPrivate::Get(mCompartment)->scope = nullptr;
-    mCompartment = nullptr;
     GetWrappedNativeMap()->Clear();
     mWrappedNativeProtoMap->Clear();
+
+    // The fields below are traced only if there's a live global in the
+    // compartment, see TraceXPCGlobal. The compartment has no live globals so
+    // clear these pointers here.
+    if (mXrayExpandos.initialized()) {
+      mXrayExpandos.destroy();
+    }
+    JSContext* cx = dom::danger::GetJSContext();
+    mIDProto.finalize(cx);
+    mIIDProto.finalize(cx);
+    mCIDProto.finalize(cx);
     return;
   }
 
   // Sweep mWrappedNativeMap for dying flat JS objects. Moving has already
   // been handled by XPCWrappedNative::FlatJSObjectMoved.
   for (auto iter = GetWrappedNativeMap()->Iter(); !iter.Done(); iter.Next()) {
     auto entry = static_cast<Native2WrappedNativeMap::Entry*>(iter.Get());
     XPCWrappedNative* wrapper = entry->value;
@@ -392,66 +366,48 @@ void XPCWrappedNativeScope::UpdateWeakPo
     if (!obj) {
       i.Remove();
     }
   }
 }
 
 // static
 void XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs() {
-  for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
     for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
       auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
       entry->value->SweepTearOffs();
     }
   }
 }
 
 // static
-void XPCWrappedNativeScope::KillDyingScopes() {
-  XPCWrappedNativeScope* cur = gDyingScopes;
-  while (cur) {
-    XPCWrappedNativeScope* next = cur->mNext;
-    if (cur->Compartment()) {
-      CompartmentPrivate::Get(cur->Compartment())->scope = nullptr;
-    }
-    delete cur;
-    cur = next;
-  }
-  gDyingScopes = nullptr;
-}
-
-// static
 void XPCWrappedNativeScope::SystemIsBeingShutDown() {
-  int liveScopeCount = 0;
-
-  XPCWrappedNativeScope* cur;
-
-  // First move all the scopes to the dying list.
-
-  cur = gScopes;
-  while (cur) {
-    XPCWrappedNativeScope* next = cur->mNext;
-    cur->mNext = gDyingScopes;
-    gDyingScopes = cur;
-    cur = next;
-    liveScopeCount++;
-  }
-  gScopes = nullptr;
-
   // We're forcibly killing scopes, rather than allowing them to go away
   // when they're ready. As such, we need to do some cleanup before they
   // can safely be destroyed.
 
-  for (cur = gDyingScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
     // Give the Components object a chance to try to clean up.
     if (cur->mComponents) {
       cur->mComponents->SystemIsBeingShutDown();
     }
 
+    // Null out these pointers to prevent ~ObjectPtr assertion failures if we
+    // leaked things at shutdown.
+    JSContext* cx = dom::danger::GetJSContext();
+    cur->mIDProto.finalize(cx);
+    cur->mIIDProto.finalize(cx);
+    cur->mCIDProto.finalize(cx);
+
+    // Similarly, destroy mXrayExpandos to prevent assertion failures.
+    if (cur->mXrayExpandos.initialized()) {
+      cur->mXrayExpandos.destroy();
+    }
+
     // Walk the protos first. Wrapper shutdown can leave dangling
     // proto pointers in the proto map.
     for (auto i = cur->mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
       auto entry =
           static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
       entry->value->SystemIsBeingShutDown();
       i.Remove();
     }
@@ -462,19 +418,16 @@ void XPCWrappedNativeScope::SystemIsBein
         wrapper->SystemIsBeingShutDown();
       }
       i.Remove();
     }
 
     CompartmentPrivate* priv = CompartmentPrivate::Get(cur->Compartment());
     priv->SystemIsBeingShutDown();
   }
-
-  // Now it is safe to kill all the scopes.
-  KillDyingScopes();
 }
 
 /***************************************************************************/
 
 JSObject* XPCWrappedNativeScope::GetExpandoChain(HandleObject target) {
   MOZ_ASSERT(ObjectScope(target) == this);
   if (!mXrayExpandos.initialized()) {
     return nullptr;
@@ -505,39 +458,38 @@ bool XPCWrappedNativeScope::SetExpandoCh
 
 // static
 void XPCWrappedNativeScope::DebugDumpAllScopes(int16_t depth) {
 #ifdef DEBUG
   depth--;
 
   // get scope count.
   int count = 0;
-  XPCWrappedNativeScope* cur;
-  for (cur = gScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
+    mozilla::Unused << cur;
     count++;
   }
 
   XPC_LOG_ALWAYS(("chain of %d XPCWrappedNativeScope(s)", count));
   XPC_LOG_INDENT();
-  XPC_LOG_ALWAYS(("gDyingScopes @ %p", gDyingScopes));
   if (depth) {
-    for (cur = gScopes; cur; cur = cur->mNext) {
+    for (XPCWrappedNativeScope* cur : AllScopes()) {
       cur->DebugDump(depth);
     }
   }
   XPC_LOG_OUTDENT();
 #endif
 }
 
 void XPCWrappedNativeScope::DebugDump(int16_t depth) {
 #ifdef DEBUG
   depth--;
   XPC_LOG_ALWAYS(("XPCWrappedNativeScope @ %p", this));
   XPC_LOG_INDENT();
-  XPC_LOG_ALWAYS(("mNext @ %p", mNext));
+  XPC_LOG_ALWAYS(("next @ %p", getNext()));
   XPC_LOG_ALWAYS(("mComponents @ %p", mComponents.get()));
   XPC_LOG_ALWAYS(("mCompartment @ %p", mCompartment));
 
   XPC_LOG_ALWAYS(("mWrappedNativeMap @ %p with %d wrappers(s)",
                   mWrappedNativeMap, mWrappedNativeMap->Count()));
   // iterate contexts...
   if (depth && mWrappedNativeMap->Count()) {
     XPC_LOG_INDENT();
@@ -561,17 +513,17 @@ void XPCWrappedNativeScope::DebugDump(in
     XPC_LOG_OUTDENT();
   }
   XPC_LOG_OUTDENT();
 #endif
 }
 
 void XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(
     JSContext* cx, ScopeSizeInfo* scopeSizeInfo) {
-  for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
     cur->AddSizeOfIncludingThis(cx, scopeSizeInfo);
   }
 }
 
 void XPCWrappedNativeScope::AddSizeOfIncludingThis(
     JSContext* cx, ScopeSizeInfo* scopeSizeInfo) {
   scopeSizeInfo->mScopeAndMapSize += scopeSizeInfo->mMallocSizeOf(this);
   scopeSizeInfo->mScopeAndMapSize +=
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -412,21 +412,20 @@ static inline T UnexpectedFailure(T rv) 
 }
 
 void xpc::TraceXPCGlobal(JSTracer* trc, JSObject* obj) {
   if (js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL) {
     mozilla::dom::TraceProtoAndIfaceCache(trc, obj);
   }
 
   // We might be called from a GC during the creation of a global, before we've
-  // been able to set up the compartment private or the XPCWrappedNativeScope,
-  // so we need to null-check those.
-  xpc::CompartmentPrivate* compPrivate = xpc::CompartmentPrivate::Get(obj);
-  if (compPrivate && compPrivate->scope) {
-    compPrivate->scope->TraceInside(trc);
+  // been able to set up the compartment private.
+  if (xpc::CompartmentPrivate* priv = xpc::CompartmentPrivate::Get(obj)) {
+    MOZ_ASSERT(priv->scope);
+    priv->scope->TraceInside(trc);
   }
 }
 
 namespace xpc {
 
 JSObject* CreateGlobalObject(JSContext* cx, const JSClass* clasp,
                              nsIPrincipal* principal,
                              JS::RealmOptions& aOptions) {
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -73,16 +73,17 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/DefineEnum.h"
 #include "mozilla/GuardObjects.h"
+#include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 
 #include "mozilla/dom/ScriptSettings.h"
 
@@ -300,16 +301,19 @@ MOZ_DEFINE_ENUM(WatchdogTimestampCategor
     TimestampWatchdogWakeup,
     TimestampWatchdogHibernateStart,
     TimestampWatchdogHibernateStop,
     TimestampContextStateChange
 ));
 // clang-format on
 
 class AsyncFreeSnowWhite;
+class XPCWrappedNativeScope;
+
+using XPCWrappedNativeScopeList = mozilla::LinkedList<XPCWrappedNativeScope>;
 
 class XPCJSContext final : public mozilla::CycleCollectedJSContext,
                            public mozilla::LinkedListElement<XPCJSContext> {
  public:
   static void InitTLS();
   static XPCJSContext* NewXPCJSContext(XPCJSContext* aPrimaryContext);
   static XPCJSContext* Get();
 
@@ -497,16 +501,20 @@ class XPCJSRuntime final : public mozill
   }
 
   NativeSetMap* GetNativeSetMap() const { return mNativeSetMap; }
 
   XPCWrappedNativeProtoMap* GetDyingWrappedNativeProtoMap() const {
     return mDyingWrappedNativeProtoMap;
   }
 
+  XPCWrappedNativeScopeList& GetWrappedNativeScopes() {
+    return mWrappedNativeScopes;
+  }
+
   bool InitializeStrings(JSContext* cx);
 
   virtual bool DescribeCustomObjects(JSObject* aObject, const js::Class* aClasp,
                                      char (&aName)[72]) const override;
   virtual bool NoteCustomGCThingXPCOMChildren(
       const js::Class* aClasp, JSObject* aObj,
       nsCycleCollectionTraversalCallback& aCb) const override;
 
@@ -617,16 +625,17 @@ class XPCJSRuntime final : public mozill
       Principal2JSObjectMap;
 
   JSObject2WrappedJSMap* mWrappedJSMap;
   IID2WrappedJSClassMap* mWrappedJSClassMap;
   IID2NativeInterfaceMap* mIID2NativeInterfaceMap;
   ClassInfo2NativeSetMap* mClassInfo2NativeSetMap;
   NativeSetMap* mNativeSetMap;
   Principal2JSObjectMap mUAWidgetScopeMap;
+  XPCWrappedNativeScopeList mWrappedNativeScopes;
   XPCWrappedNativeProtoMap* mDyingWrappedNativeProtoMap;
   bool mGCIsRunning;
   nsTArray<nsISupports*> mNativesToReleaseArray;
   bool mDoingFinalization;
   XPCRootSetElem* mVariantRoots;
   XPCRootSetElem* mWrappedJSRoots;
   nsTArray<xpcGCCallback> extraGCCallbacks;
   JS::GCSliceCallback mPrevGCSliceCallback;
@@ -802,17 +811,18 @@ extern const js::Class XPC_WN_NoHelper_P
 extern bool XPC_WN_CallMethod(JSContext* cx, unsigned argc, JS::Value* vp);
 
 extern bool XPC_WN_GetterSetter(JSContext* cx, unsigned argc, JS::Value* vp);
 
 /***************************************************************************/
 // XPCWrappedNativeScope is one-to-one with a JS compartment.
 
 class nsXPCComponentsBase;
-class XPCWrappedNativeScope final {
+class XPCWrappedNativeScope final
+    : public mozilla::LinkedListElement<XPCWrappedNativeScope> {
  public:
   XPCJSRuntime* GetRuntime() const { return XPCJSRuntime::Get(); }
 
   Native2WrappedNativeMap* GetWrappedNativeMap() const {
     return mWrappedNativeMap;
   }
 
   ClassInfo2WrappedNativeProtoMap* GetWrappedNativeProtoMap() const {
@@ -834,17 +844,18 @@ class XPCWrappedNativeScope final {
 
   JSObject* DetachExpandoChain(JS::HandleObject target);
 
   bool SetExpandoChain(JSContext* cx, JS::HandleObject target,
                        JS::HandleObject chain);
 
   static void SystemIsBeingShutDown();
 
-  static void TraceWrappedNativesInAllScopes(JSTracer* trc);
+  static void TraceWrappedNativesInAllScopes(XPCJSRuntime* xpcrt,
+                                             JSTracer* trc);
 
   void TraceInside(JSTracer* trc) {
     if (mXrayExpandos.initialized()) {
       mXrayExpandos.trace(trc);
     }
     if (mIDProto) {
       mIDProto.trace(trc, "XPCWrappedNativeScope::mIDProto");
     }
@@ -855,22 +866,18 @@ class XPCWrappedNativeScope final {
       mCIDProto.trace(trc, "XPCWrappedNativeScope::mCIDProto");
     }
   }
 
   static void SuspectAllWrappers(nsCycleCollectionNoteRootCallback& cb);
 
   static void SweepAllWrappedNativeTearOffs();
 
-  static void UpdateWeakPointersInAllScopesAfterGC();
-
   void UpdateWeakPointersAfterGC();
 
-  static void KillDyingScopes();
-
   static void DebugDumpAllScopes(int16_t depth);
 
   void DebugDump(int16_t depth);
 
   struct ScopeSizeInfo {
     explicit ScopeSizeInfo(mozilla::MallocSizeOf mallocSizeOf)
         : mMallocSizeOf(mallocSizeOf),
           mScopeAndMapSize(0),
@@ -881,31 +888,30 @@ class XPCWrappedNativeScope final {
     size_t mProtoAndIfaceCacheSize;
   };
 
   static void AddSizeOfAllScopesIncludingThis(JSContext* cx,
                                               ScopeSizeInfo* scopeSizeInfo);
 
   void AddSizeOfIncludingThis(JSContext* cx, ScopeSizeInfo* scopeSizeInfo);
 
-  static bool IsDyingScope(XPCWrappedNativeScope* scope);
-
   // Gets the appropriate scope object for XBL in this compartment. This method
   // relies on compartment-per-global still (and release-asserts this). The
   // context must be same-realm with this compartment's single global upon
   // entering, and the scope object is wrapped into this compartment.
   JSObject* EnsureContentXBLScope(JSContext* cx);
 
   // Check whether our mAllowContentXBLScope state matches the given
   // principal.  This is used to avoid sharing compartments on
   // mismatch.
   bool XBLScopeStateMatches(nsIPrincipal* aPrincipal);
 
   XPCWrappedNativeScope(JS::Compartment* aCompartment,
                         JS::HandleObject aFirstGlobal);
+  virtual ~XPCWrappedNativeScope();
 
   nsAutoPtr<JSObject2JSObjectMap> mWaiverWrapperMap;
 
   JS::Compartment* Compartment() const { return mCompartment; }
 
   // Returns the global to use for new WrappedNative objects allocated in this
   // compartment. This is better than using arbitrary globals we happen to be in
   // because it prevents leaks (objects keep their globals alive).
@@ -919,28 +925,22 @@ class XPCWrappedNativeScope final {
   bool AllowContentXBLScope(JS::Realm* aRealm);
 
   // ID Object prototype caches.
   JS::ObjectPtr mIDProto;
   JS::ObjectPtr mIIDProto;
   JS::ObjectPtr mCIDProto;
 
  protected:
-  virtual ~XPCWrappedNativeScope();
-
   XPCWrappedNativeScope() = delete;
 
  private:
-  static XPCWrappedNativeScope* gScopes;
-  static XPCWrappedNativeScope* gDyingScopes;
-
   Native2WrappedNativeMap* mWrappedNativeMap;
   ClassInfo2WrappedNativeProtoMap* mWrappedNativeProtoMap;
   RefPtr<nsXPCComponentsBase> mComponents;
-  XPCWrappedNativeScope* mNext;
   JS::Compartment* mCompartment;
 
   JS::WeakMapPtr<JSObject*, JSObject*> mXrayExpandos;
 
   // For remote XUL domains, we run all XBL in the content scope for compat
   // reasons (though we sometimes pref this off for automation). We
   // track the result of this decision (mAllowContentXBLScope) for now.
   bool mAllowContentXBLScope;
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -7778,26 +7778,23 @@ nsresult PresShell::EventHandler::Handle
 
     if (!mPresShell->mTouchManager.PreHandleEvent(
             aEvent, aEventStatus, touchIsNew, isHandlingUserInput,
             mPresShell->mCurrentEventContent)) {
       return NS_OK;
     }
   }
 
-  if (aEvent->mMessage == eContextMenu) {
-    WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
-    if (mouseEvent->IsContextMenuKeyEvent() &&
-        !AdjustContextMenuKeyEvent(mouseEvent)) {
-      return NS_OK;
-    }
-    if (mouseEvent->IsShift()) {
-      aEvent->mFlags.mOnlyChromeDispatch = true;
-      aEvent->mFlags.mRetargetToNonNativeAnonymous = true;
-    }
+  // If we cannot open context menu even though eContextMenu is fired, we
+  // should stop dispatching it into the DOM.
+  // XXX Can it be untrusted eContextMenu event here?  If not, we can do
+  //     this in the above block's switch statement.
+  if (aEvent->mMessage == eContextMenu &&
+      !PrepareToDispatchContextMenuEvent(aEvent)) {
+    return NS_OK;
   }
 
   AutoHandlingUserInputStatePusher userInpStatePusher(isHandlingUserInput,
                                                       aEvent, GetDocument());
 
   if (aEvent->IsTrusted() && aEvent->mMessage == eMouseMove) {
     nsIPresShell::AllowMouseCapture(
         EventStateManager::GetActiveEventStateManager() == manager);
@@ -7930,16 +7927,38 @@ nsresult PresShell::EventHandler::Handle
     }
     default:
       break;
   }
   RecordEventHandlingResponsePerformance(aEvent);
   return rv;
 }
 
+bool PresShell::EventHandler::PrepareToDispatchContextMenuEvent(
+    WidgetEvent* aEvent) {
+  MOZ_ASSERT(aEvent);
+  MOZ_ASSERT(aEvent->mMessage == eContextMenu);
+
+  WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+  if (mouseEvent->IsContextMenuKeyEvent() &&
+      !AdjustContextMenuKeyEvent(mouseEvent)) {
+    return false;
+  }
+
+  // If "Shift" state is active, context menu should be forcibly opened even
+  // if web apps want to prevent it since we respect our users' intention.
+  // In this case, we don't fire "contextmenu" event on web content because
+  // of not cancelable.
+  if (mouseEvent->IsShift()) {
+    aEvent->mFlags.mOnlyChromeDispatch = true;
+    aEvent->mFlags.mRetargetToNonNativeAnonymous = true;
+  }
+  return true;
+}
+
 void PresShell::EventHandler::RecordEventHandlingResponsePerformance(
     const WidgetEvent* aEvent) {
   if (!Telemetry::CanRecordBase() || aEvent->mTimeStamp.IsNull() ||
       aEvent->mTimeStamp <= mPresShell->mLastOSWake ||
       !aEvent->AsInputEvent()) {
     return;
   }
 
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -1072,16 +1072,26 @@ class PresShell final : public nsIPresSh
      * RecordEventHandlingResponsePerformance() records event handling response
      * performance with telemetry.
      *
      * @param aEvent            The handled event.
      */
     void RecordEventHandlingResponsePerformance(const WidgetEvent* aEvent);
 
     /**
+     * PrepareToDispatchContextMenuEvent() prepares to dispatch aEvent into
+     * the DOM.
+     *
+     * @param aEvent            Must be eContextMenu event.
+     * @return                  true if it can be dispatched into the DOM.
+     *                          Otherwise, false.
+     */
+    bool PrepareToDispatchContextMenuEvent(WidgetEvent* aEvent);
+
+    /**
      * This and the next two helper methods are used to target and position the
      * context menu when the keyboard shortcut is used to open it.
      *
      * If another menu is open, the context menu is opened relative to the
      * active menuitem within the menu, or the menu itself if no item is active.
      * Otherwise, if the caret is visible, the menu is opened near the caret.
      * Otherwise, if a selectable list such as a listbox is focused, the
      * current item within the menu is opened relative to this item.
--- a/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
@@ -17,16 +17,17 @@ import android.util.Log;
 
 import org.json.JSONException;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Engaged;
 import org.mozilla.gecko.fxa.login.State;
+import org.mozilla.gecko.mma.MmaDelegate;
 import org.mozilla.gecko.restrictions.Restrictable;
 import org.mozilla.gecko.restrictions.Restrictions;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
@@ -140,16 +141,19 @@ public class AccountsHelper implements B
                      UnsupportedEncodingException e) {
                 Log.w(LOGTAG, "Got exception creating Firefox Account from JSON; ignoring.", e);
                 if (callback != null) {
                     callback.sendError("Could not create Firefox Account from JSON: " +
                                        e.toString());
                     return;
                 }
             }
+
+            MmaDelegate.track(MmaDelegate.USER_SIGNED_IN_TO_FXA);
+
             if (callback != null) {
                 callback.sendSuccess(fxAccount != null);
             }
 
         } else if ("Accounts:UpdateFirefoxAccountFromJSON".equals(event)) {
             final Account account = FirefoxAccounts.getFirefoxAccount(mContext);
             if (account == null) {
                 if (callback != null) {
@@ -195,16 +199,18 @@ public class AccountsHelper implements B
             fxAccount.updateFirstRunScope(mContext);
             // This will force a device registration later.
             fxAccount.resetDeviceRegistrationVersion();
             fxAccount.setDeviceRegistrationTimestamp(0L);
             // Trigger a sync to try to update the device registration and
             // upload a fresh client record.
             fxAccount.requestImmediateSync(null, null, false);
 
+            MmaDelegate.track(MmaDelegate.USER_SIGNED_IN_TO_FXA);
+
             if (callback != null) {
                 callback.sendSuccess(true);
             }
 
         } else if ("Accounts:Create".equals(event)) {
             // Do exactly the same thing as if you tapped 'Sync' in Settings.
             final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
--- a/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/mma/MmaDelegate.java
@@ -9,16 +9,17 @@ package org.mozilla.gecko.mma;
 import android.app.Activity;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.text.TextUtils;
 
 import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.Experiments;
 import org.mozilla.gecko.MmaConstants;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.activitystream.homepanel.ActivityStreamConfiguration;
 import org.mozilla.gecko.firstrun.PanelConfig;
@@ -28,16 +29,18 @@ import org.mozilla.gecko.switchboard.Swi
 import org.mozilla.gecko.util.ContextUtils;
 import org.mozilla.gecko.util.PackageUtil;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
 
+import static android.content.Context.MODE_PRIVATE;
+
 
 public class MmaDelegate {
 
     public static final String READER_AVAILABLE = "E_Reader_Available";
     public static final String DOWNLOAD_MEDIA_SAVED_IMAGE = "E_Download_Media_Saved_Image";
     public static final String CLEARED_PRIVATE_DATA = "E_Cleared_Private_Data";
     public static final String SAVED_BOOKMARK = "E_Saved_Bookmark";
     public static final String OPENED_BOOKMARK = "E_Opened_Bookmark";
@@ -45,16 +48,19 @@ public class MmaDelegate {
     public static final String SCREENSHOT = "E_Screenshot";
     public static final String SAVED_LOGIN_AND_PASSWORD = "E_Saved_Login_And_Password";
     public static final String RESUMED_FROM_BACKGROUND = "E_Resumed_From_Background";
     public static final String NEW_TAB = "E_Opened_New_Tab";
     public static final String DISMISS_ONBOARDING = "E_Dismiss_Onboarding";
     public static final String ONBOARDING_DEFAULT_VALUES = "E_Onboarding_With_Default_Values";
     public static final String ONBOARDING_REMOTE_VALUES = "E_Onboarding_With_Remote_Values";
 
+    public static final String USER_SIGNED_IN_TO_FXA = "E_User_Signed_In_To_FxA";
+    public static final String USER_FINISHED_SYNC = "E_User_Finished_Sync";
+
     private static final String LAUNCH_BUT_NOT_DEFAULT_BROWSER = "E_Launch_But_Not_Default_Browser";
     private static final String LAUNCH_BROWSER = "E_Launch_Browser";
     private static final String CHANGED_DEFAULT_TO_FENNEC = "E_Changed_Default_To_Fennec";
     private static final String INSTALLED_FOCUS = "E_Just_Installed_Focus";
     private static final String INSTALLED_KLAR = "E_Just_Installed_Klar";
 
     private static final String USER_ATT_FOCUS_INSTALLED = "Focus Installed";
     private static final String USER_ATT_KLAR_INSTALLED = "Klar Installed";
@@ -276,25 +282,34 @@ public class MmaDelegate {
             return false;
         }
     }
 
     public static PanelConfig getPanelConfig(@NonNull Context context, PanelConfig.TYPE panelConfigType, final boolean useLocalValues) {
         return mmaHelper.getPanelConfig(context, panelConfigType, useLocalValues);
     }
 
-    public static String getDeviceId(Activity activity) {
+    private static String getDeviceId(Activity activity) {
         if (SwitchBoard.isInExperiment(activity, Experiments.LEANPLUM_DEBUG)) {
             return DEBUG_LEANPLUM_DEVICE_ID;
         }
 
         final SharedPreferences prefs = activity.getPreferences(Context.MODE_PRIVATE);
         return prefs.getString(KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID, null);
     }
 
+    public static String getDeviceId(Context context) {
+        if (SwitchBoard.isInExperiment(context, Experiments.LEANPLUM_DEBUG)) {
+            return DEBUG_LEANPLUM_DEVICE_ID;
+        }
+
+        //MMA preferences are stored in the initialising activity's preferences, which in our case is BrowserApp.
+        return context.getSharedPreferences(BrowserApp.class.getName(), MODE_PRIVATE).getString(MmaDelegate.KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID, null);
+    }
+
     private static void setDeviceId(Activity activity, String deviceId) {
         final SharedPreferences prefs = activity.getPreferences(Context.MODE_PRIVATE);
         prefs.edit().putString(KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID, deviceId).apply();
     }
 
     private static void registerInstalledPackagesReceiver(@NonNull final Activity activity) {
         packageAddedReceiver = new PackageAddedReceiver();
         activity.registerReceiver(packageAddedReceiver, PackageAddedReceiver.getIntentFilter());
--- a/mobile/android/docs/mma.rst
+++ b/mobile/android/docs/mma.rst
@@ -151,16 +151,24 @@ List of current Events related data that
 * General app start event
 {
   "event" : "E_Launch_Browser"
 }
 * The user just dismissed on-boarding
 {
   "event" : "E_Dismiss_Onboarding"
 }
+* Sign in Firefox Account
+{
+  "event" : "E_User_Signed_In_To_FxA"
+}
+* Firefox Sync finished event
+{
+  "event" : "E_User_Finished_Sync"
+}
 * The user just resumed the app from background
 {
   "event" : "E_Resumed_From_Background"
 }
 * User set Fennec as default browser and resumed the app
 {
   "event" : "E_Changed_Default_To_Fennec"
 }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncStatusHelper.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncStatusHelper.java
@@ -5,16 +5,17 @@
 package org.mozilla.gecko.fxa.sync;
 
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.WeakHashMap;
 
 import org.mozilla.gecko.fxa.SyncStatusListener;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.mma.MmaDelegate;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.ContentResolver;
 import android.content.SyncStatusObserver;
 
 /**
  * Abstract away some details of Android's SyncStatusObserver.
  * <p>
@@ -62,16 +63,17 @@ public class FxAccountSyncStatusHelper i
       }
 
       if (!active && wasActiveLastTime) {
         // We've finished a sync.
         ThreadUtils.postToUiThread(new Runnable() {
           @Override
           public void run() {
             delegate.onSyncFinished();
+            MmaDelegate.track(MmaDelegate.USER_FINISHED_SYNC);
           }
         });
       }
     }
   }
 
   protected void addListener() {
     final int mask = ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
--- a/taskcluster/ci/build/linux.yml
+++ b/taskcluster/ci/build/linux.yml
@@ -38,16 +38,17 @@ linux64-plain/opt:
         product: firefox
         job-name: linux64-plain-opt
     treeherder:
         platform: linux64/opt
         symbol: Bp
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
+        docker-image: {in-tree: debian9-amd64-build}
         max-run-time: 5400
         env:
             PERFHERDER_EXTRA_OPTIONS: plain
     run:
         using: mozharness
         actions: [build]
         config:
             - builds/releng_base_firefox.py
@@ -183,16 +184,17 @@ linux64-plain/debug:
         product: firefox
         job-name: linux64-plain-debug
     treeherder:
         platform: linux64/debug
         symbol: Bp
         tier: 2
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
+        docker-image: {in-tree: debian9-amd64-build}
         max-run-time: 5400
         env:
             PERFHERDER_EXTRA_OPTIONS: plain
     run:
         using: mozharness
         actions: [build]
         config:
             - builds/releng_base_firefox.py
--- a/taskcluster/ci/test/raptor.yml
+++ b/taskcluster/ci/test/raptor.yml
@@ -324,16 +324,33 @@ raptor-tp6m-8-geckoview:
     target: geckoview_example.apk
     tier: 2
     mozharness:
         extra-options:
             - --test=raptor-tp6m-8
             - --app=geckoview
             - --binary=org.mozilla.geckoview_example
 
+raptor-tp6m-6-geckoview:
+    description: "Raptor tp6m-6 on Geckoview"
+    try-name: raptor-tp6m-6-geckoview
+    treeherder-symbol: Rap(tp6m-6)
+    run-on-projects:
+        by-test-platform:
+            android-hw-p2-8-0-arm7.*: ['try', 'mozilla-central']
+            android-hw-g5.*: ['try', 'mozilla-central']
+            default: ['try', 'trunk', 'mozilla-beta']
+    target: geckoview_example.apk
+    tier: 2
+    mozharness:
+        extra-options:
+            - --test=raptor-tp6m-6
+            - --app=geckoview
+            - --binary=org.mozilla.geckoview_example
+
 raptor-speedometer-firefox:
     description: "Raptor Speedometer on Firefox"
     try-name: raptor-speedometer-firefox
     treeherder-symbol: Rap(sp)
     mozharness:
         extra-options:
             - --test=raptor-speedometer
 
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -409,23 +409,25 @@ android-hw-arm7-debug-unittests:
 android-hw-aarch64-opt-unittests:
     - jittest
 
 android-hw-arm7-raptor:
     - raptor-speedometer-geckoview
     - raptor-tp6m-1-geckoview
     - raptor-tp6m-2-geckoview
     - raptor-tp6m-5-geckoview
+    - raptor-tp6m-6-geckoview
     - raptor-tp6m-8-geckoview
 
 android-hw-aarch64-raptor:
     - raptor-speedometer-geckoview
     - raptor-tp6m-1-geckoview
     - raptor-tp6m-2-geckoview
     - raptor-tp6m-5-geckoview
+    - raptor-tp6m-6-geckoview
     - raptor-tp6m-8-geckoview
 
 android-hw-arm7-raptor-power:
     - raptor-speedometer-geckoview-power
 
 android-hw-aarch64-raptor-power:
     - raptor-speedometer-geckoview-power
 
--- a/taskcluster/ci/webrender/kind.yml
+++ b/taskcluster/ci/webrender/kind.yml
@@ -230,8 +230,42 @@ jobs:
             symbol: WR(wrench)
         when:
             files-changed:
                 - 'build/win64/mozconfig.vs2017'
                 - 'browser/config/tooltool-manifests/win64/webrender.manifest'
                 - 'gfx/wr/**'
                 - 'taskcluster/scripts/misc/tooltool-download.sh'
                 - 'taskcluster/scripts/misc/wrench-windows-tests.sh'
+
+    wrench-android-build:
+        description: Build wrench for Android
+        worker-type: aws-provisioner-v1/gecko-{level}-b-android
+        worker:
+            max-run-time: 5400
+            docker-image: {in-tree: webrender}
+            artifacts:
+                - type: file
+                  name: public/build/wrench-debug.apk
+                  path: /builds/worker/checkouts/gecko/gfx/wr/target/android-artifacts/app/build/outputs/apk/app-debug.apk
+        run:
+            using: run-task
+            command: >-
+                cd $GECKO_PATH &&
+                ./mach artifact toolchain -v $MOZ_TOOLCHAINS &&
+                export PATH=$PATH:$PWD/rustc/bin:$PWD/android-gradle-dependencies/gradle-dist/bin &&
+                export ANDROID_HOME=$PWD/android-sdk-linux &&
+                export NDK_HOME=$PWD/android-ndk &&
+                mv wrench-deps/{vendor,.cargo,cargo-apk} gfx/wr &&
+                cd gfx/wr/wrench &&
+                ../cargo-apk/bin/cargo-apk build --frozen --verbose
+        toolchains:
+            - android-gradle-dependencies
+            - android-ndk-linux
+            - android-sdk-linux
+            - linux64-rust-android
+            - wrench-deps
+        treeherder:
+            platform: android-4-0-armv7-api16/debug
+            symbol: WR(B)
+        when:
+            files-changed:
+                - 'gfx/wr/**'
--- a/taskcluster/scripts/misc/wrench-deps-vendoring.sh
+++ b/taskcluster/scripts/misc/wrench-deps-vendoring.sh
@@ -14,12 +14,19 @@ cd $WORKSPACE
 . $SRCDIR/taskcluster/scripts/misc/tooltool-download.sh
 export PATH=$PATH:$SRCDIR/rustc/bin
 cargo install --version 0.1.21 cargo-vendor
 cd $SRCDIR/gfx/wr/
 mkdir .cargo
 cargo vendor --relative-path --sync ./Cargo.lock > .cargo/config
 mkdir wrench-deps
 mv vendor .cargo wrench-deps/
+mkdir wrench-deps/cargo-apk
+# Until there's a version of cargo-apk published on crates.io that has
+# https://github.com/tomaka/android-rs-glue/pull/205 and
+# https://github.com/tomaka/android-rs-glue/pull/171 (see also
+# https://github.com/tomaka/android-rs-glue/issues/204), we need to use
+# an unpublished version.
+cargo install --git https://github.com/staktrace/android-rs-glue --rev 6b2be25bd536a453e233cd7aea9d22974db63a49 --root wrench-deps/cargo-apk cargo-apk
 tar caf wrench-deps.tar.bz2 wrench-deps
 
 mkdir -p $UPLOAD_DIR
 mv wrench-deps.tar.bz2 $UPLOAD_DIR/
new file mode 100644
--- /dev/null
+++ b/testing/raptor/raptor/playback/mitmproxy-recordings-raptor-tp6m-bbc.manifest
@@ -0,0 +1,10 @@
+[
+  {
+    "size": 2047073,
+    "visibility": "public",
+    "digest": "abdfd7581b34d8af436291e0c22c36f33f2d9ff9efbd49854312d7dce3aab20895040715f00bb8bb7f92efe411795aae73d960d8c37c35810bbb6bc538da1f9f",
+    "algorithm": "sha512",
+    "filename": "mitmproxy-recordings-raptor-tp6m-bbc.zip",
+    "unpack": true
+  }
+]
new file mode 100644
--- /dev/null
+++ b/testing/raptor/raptor/playback/mitmproxy-recordings-raptor-tp6m-reddit.manifest
@@ -0,0 +1,10 @@
+[
+  {
+    "size": 5310402,
+    "visibility": "public",
+    "digest": "c6e4cc7e9c7e600e65a56a690201d7a9f6e803c42f537d2691f038ab39a0b95c37ff33d84f0f981b5f9943b4041203b8c2493b12e37f72e4dfd439074d17dbcc",
+    "algorithm": "sha512",
+    "filename": "mitmproxy-recordings-raptor-tp6m-reddit.zip",
+    "unpack": true
+  }
+]
new file mode 100644
--- /dev/null
+++ b/testing/raptor/raptor/playback/mitmproxy-recordings-raptor-tp6m-stackoverflow.manifest
@@ -0,0 +1,10 @@
+[
+  {
+    "size": 720295,
+    "visibility": "public",
+    "digest": "2ddbe6e68df1a78ce5983a70ba47f4125afb6209cfceedae964a4a86dc68d30480e7c10493e77b55aefc0c4a5e79706f6126c6684d29d5f41aaffc0463944009",
+    "algorithm": "sha512",
+    "filename": "mitmproxy-recordings-raptor-tp6m-stackoverflow.zip",
+    "unpack": true
+  }
+]
--- a/testing/raptor/raptor/raptor.ini
+++ b/testing/raptor/raptor/raptor.ini
@@ -12,16 +12,17 @@
 
 # raptor pageload binast tests desktop
 [include:tests/raptor-tp6-binast-1.ini]
 
 # raptor pageload tests mobile
 [include:tests/raptor-tp6m-1.ini]
 [include:tests/raptor-tp6m-2.ini]
 [include:tests/raptor-tp6m-5.ini]
+[include:tests/raptor-tp6m-6.ini]
 [include:tests/raptor-tp6m-8.ini]
 
 # raptor benchmark tests
 [include:tests/raptor-assorted-dom.ini]
 [include:tests/raptor-motionmark-animometer.ini]
 [include:tests/raptor-motionmark-htmlsuite.ini]
 [include:tests/raptor-speedometer.ini]
 [include:tests/raptor-stylebench.ini]
new file mode 100644
--- /dev/null
+++ b/testing/raptor/raptor/tests/raptor-tp6m-6.ini
@@ -0,0 +1,38 @@
+# 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/.
+
+# raptor tp6m-6
+
+[DEFAULT]
+type =  pageload
+playback = mitmproxy-android
+playback_binary_manifest = mitmproxy-rel-bin-{platform}.manifest
+page_cycles = 15
+unit = ms
+lower_is_better = true
+alert_threshold = 2.0
+page_timeout = 60000
+alert_on = fcp, loadtime
+
+[raptor-tp6m-reddit-geckoview]
+apps = geckoview
+test_url = https://www.reddit.com
+playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-reddit.manifest
+playback_recordings = mitmproxy-recordings-raptor-tp6m-reddit.mp
+measure = fnbpaint, fcp, dcf, ttfi, loadtime
+
+[raptor-tp6m-bbc-geckoview]
+apps = geckoview
+test_url = https://www.bbc.com/news/business-47245877
+playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-bbc.manifest
+playback_recordings = mitmproxy-recordings-raptor-tp6m-bbc.mp
+measure = fnbpaint, fcp, dcf, ttfi, loadtime
+disabled = Bug 1533304 Investigating tp6m-6 raptor test for raptor-tp6m-bbc-geckoview
+
+[raptor-tp6m-stackoverflow-geckoview]
+apps = geckoview
+test_url = https://stackoverflow.com/
+playback_pageset_manifest = mitmproxy-recordings-raptor-tp6m-stackoverflow.manifest
+playback_recordings = mitmproxy-recordings-raptor-tp6m-stackoverflow.mp
+measure = fnbpaint, fcp, dcf, ttfi, loadtime
--- a/testing/raptor/webext/raptor/manifest.json
+++ b/testing/raptor/webext/raptor/manifest.json
@@ -12,29 +12,31 @@
     "scripts": ["auto_gen_test_config.js", "runner.js"]
   },
   "content_scripts": [
     {
       "matches": [
                   "*://*.allrecipes.com/*",
                   "*://*.apple.com/*",
                   "*://*.amazon.com/*",
+                  "*://*.bbc.com/*",
                   "*://*.bing.com/*",
                   "*://*.ebay.com/*",
                   "*://*.ebay-kleinanzeigen.de/*",
                   "*://*.espn.com/*",
                   "*://*.facebook.com/*",
                   "*://*.google.com/*",
                   "*://*.imdb.com/*",
                   "*://*.imgur.com/*",
                   "*://*.instagram.com/*",
                   "*://*.microsoft.com/*",
                   "*://*.paypal.com/*",
                   "*://*.pinterest.com/*",
                   "*://*.reddit.com/*",
+                  "*://*.stackoverflow.com/*",
                   "*://*.tumblr.com/*",
                   "*://*.twitter.com/*",
                   "*://*.vice.com/*",
                   "*://*.web.de/*",
                   "*://*.wikia.com/*",
                   "*://*.wikipedia.org/*",
                   "*://*.yahoo.com/*",
                   "*://*.youtube.com/*",
--- a/testing/web-platform/meta/webrtc/RTCPeerConnection-getStats.https.html.ini
+++ b/testing/web-platform/meta/webrtc/RTCPeerConnection-getStats.https.html.ini
@@ -20,18 +20,18 @@
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1225720
 
   [getStats() with no argument should return stats for no-stream tracks]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1225720
 
   [getStats() on track associated with RtpSender should return stats report containing outbound-rtp stats]
     expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1225720, https://bugzilla.mozilla.org/show_bug.cgi?id=1531094
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1225720
 
   [getStats() on track associated with RtpReceiver should return stats report containing inbound-rtp stats]
     expected: FAIL
-    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1531094
+    bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1225723
 
   [getStats() with connected peer connections having tracks and data channel should return all mandatory to implement stats]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1225720
 
--- a/testing/web-platform/tests/webrtc/RTCPeerConnection-getStats.https.html
+++ b/testing/web-platform/tests/webrtc/RTCPeerConnection-getStats.https.html
@@ -168,50 +168,53 @@
   /*
     8.5.  The stats selection algorithm
       3.  If selector is an RTCRtpSender, gather stats for and add the following objects
           to result:
         - All RTCOutboundRTPStreamStats objects corresponding to selector.
         - All stats objects referenced directly or indirectly by the RTCOutboundRTPStreamStats
           objects added.
    */
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-    t.add_cleanup(() => pc.close());
-    return getTrackFromUserMedia('audio')
-    .then(([track, mediaStream]) => {
-      pc.addTrack(track, mediaStream);
+  promise_test(async t => {
+    const pc = createPeerConnectionWithCleanup(t);
+    const pc2 = createPeerConnectionWithCleanup(t);
 
-      return pc.getStats(track)
-      .then(statsReport => {
-        validateStatsReport(statsReport);
-        assert_stats_report_has_stats(statsReport, ['outbound-rtp']);
-      });
-    });
+    let [track, mediaStream] = await getTrackFromUserMedia('audio');
+    pc.addTrack(track, mediaStream);
+    exchangeIceCandidates(pc, pc2);
+    await doSignalingHandshake(pc, pc2);
+    await listenToIceConnected(pc);
+    const stats = await pc.getStats(track);
+    validateStatsReport(stats);
+    assert_stats_report_has_stats(stats, ['outbound-rtp']);
   }, `getStats() on track associated with RtpSender should return stats report containing outbound-rtp stats`);
 
 
   /*
     8.5.  The stats selection algorithm
       4.  If selector is an RTCRtpReceiver, gather stats for and add the following objects
           to result:
         - All RTCInboundRTPStreamStats objects corresponding to selector.
         - All stats objects referenced directly or indirectly by the RTCInboundRTPStreamStats
           added.
    */
-  promise_test(t => {
-    const pc = new RTCPeerConnection();
-    t.add_cleanup(() => pc.close());
-    const transceiver = pc.addTransceiver('audio');
+  promise_test(async t => {
+    const pc = createPeerConnectionWithCleanup(t);
+    const pc2 = createPeerConnectionWithCleanup(t);
 
-    return pc.getStats(transceiver.receiver.track)
-    .then(statsReport => {
-      validateStatsReport(statsReport);
-      assert_stats_report_has_stats(statsReport, ['inbound-rtp']);
+    let [track, mediaStream] = await getTrackFromUserMedia('audio');
+    pc.addTrack(track, mediaStream);
+    exchangeIceCandidates(pc, pc2);
+    await doSignalingHandshake(pc, pc2);
+    await new Promise(resolve => {
+      pc2.getReceivers()[0].track.addEventListener('unmute', resolve);
     });
+    const stats = await pc2.getStats(track);
+    validateStatsReport(stats);
+    assert_stats_report_has_stats(stats, ['inbound-rtp']);
   }, `getStats() on track associated with RtpReceiver should return stats report containing inbound-rtp stats`);
 
   /*
     8.6   Mandatory To Implement Stats
       An implementation MUST support generating statistics of the following types
       when the corresponding objects exist on a PeerConnection, with the attributes
       that are listed when they are valid for that object.
    */
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
@@ -21,17 +21,18 @@ function waitForClear() {
 
     Services.mm.addMessageListener(MSG, listener, true);
   });
 }
 
 add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({
     set: [["extensions.webapi.testing", true],
-          ["extensions.install.requireBuiltInCerts", false]],
+          ["extensions.install.requireBuiltInCerts", false],
+          ["extensions.allowPrivateBrowsingByDefault", false]],
   });
   info("added preferences");
 });
 
 // Wrapper around a common task to run in the content process to test
 // the mozAddonManager API.  Takes a URL for the XPI to install and an
 // array of steps, each of which can either be an action to take
 // (i.e., start or cancel the install) or an install event to wait for.
@@ -198,17 +199,17 @@ function makeRegularTest(options, what) 
       },
     ];
 
     let installPromptPromise =
       promisePopupNotificationShown("addon-webext-permissions").then(panel => {
         panel.button.click();
       });
 
-    let promptPromise = acceptAppMenuNotificationWhenShown("addon-installed");
+    let promptPromise = acceptAppMenuNotificationWhenShown("addon-installed", "extension");
 
     await testInstall(browser, options, steps, what);
 
     await installPromptPromise;
 
     await promptPromise;
 
     // Sanity check to ensure that the test in makeInstallTest() that
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_theme.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_theme.js
@@ -2,30 +2,31 @@
 "use strict";
 
 const TESTPAGE = `${SECURE_TESTROOT}webapi_checkavailable.html`;
 const URL = `${SECURE_TESTROOT}addons/browser_theme.xpi`;
 
 add_task(async function test_theme_install() {
   await SpecialPowers.pushPrefEnv({
     set: [["extensions.webapi.testing", true],
-          ["extensions.install.requireBuiltInCerts", false]],
+          ["extensions.install.requireBuiltInCerts", false],
+          ["extensions.allowPrivateBrowsingByDefault", false]],
   });
 
   await BrowserTestUtils.withNewTab(TESTPAGE, async (browser) => {
     let updates = [];
     function observer(subject, topic, data) {
       updates.push(data);
     }
     Services.obs.addObserver(observer, "lightweight-theme-styling-update");
     registerCleanupFunction(() => {
       Services.obs.removeObserver(observer, "lightweight-theme-styling-update");
     });
 
-    let promptPromise = acceptAppMenuNotificationWhenShown("addon-installed");
+    let promptPromise = acceptAppMenuNotificationWhenShown("addon-installed", "theme");
 
     let installPromise = ContentTask.spawn(browser, URL, async (url) => {
       let install = await content.navigator.mozAddonManager.createInstall({url});
       return install.install();
     });
 
     await promptPromise;
     await installPromise;
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -1394,28 +1394,34 @@ function promisePopupNotificationShown(n
 
       PopupNotifications.panel.removeEventListener("popupshown", popupshown);
       resolve(PopupNotifications.panel.firstChild);
     }
     PopupNotifications.panel.addEventListener("popupshown", popupshown);
   });
 }
 
-function acceptAppMenuNotificationWhenShown(id) {
+function acceptAppMenuNotificationWhenShown(id, type) {
   const {AppMenuNotifications} = ChromeUtils.import("resource://gre/modules/AppMenuNotifications.jsm");
   return new Promise(resolve => {
     function popupshown() {
       let notification = AppMenuNotifications.activeNotification;
       if (!notification) { return; }
 
       is(notification.id, id, `${id} notification shown`);
       ok(PanelUI.isNotificationPanelOpen, "notification panel open");
 
       PanelUI.notificationPanel.removeEventListener("popupshown", popupshown);
 
+      if (id == "addon-installed" && type) {
+        let hidden = type !== "extension" ||
+                     Services.prefs.getBoolPref("extensions.allowPrivateBrowsingByDefault", true);
+        let checkbox = document.getElementById("addon-incognito-checkbox");
+        is(checkbox.hidden, hidden, "checkbox visibility is correct");
+      }
       let popupnotificationID = PanelUI._getPopupId(notification);
       let popupnotification = document.getElementById(popupnotificationID);
       popupnotification.button.click();
 
       resolve();
     }
     PanelUI.notificationPanel.addEventListener("popupshown", popupshown);
   });
--- a/tools/profiler/tests/head_profiler.js
+++ b/tools/profiler/tests/head_profiler.js
@@ -1,14 +1,56 @@
 /* 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/. */
 
 var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+var {setTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm");
+
+/**
+ * Get the payloads of a type recursively, including from all subprocesses.
+ *
+ * @param {Object} profile The gecko profile.
+ * @param {string} type The marker payload type, e.g. "DiskIO".
+ * @param {Array} payloadTarget The recursive list of payloads.
+ * @return {Array} The final payloads.
+ */
+function getAllPayloadsOfType(profile, type, payloadTarget = []) {
+  for (const {markers} of profile.threads) {
+    for (const markerTuple of markers.data) {
+      const payload = markerTuple[markers.schema.data];
+      if (payload && payload.type === type) {
+        payloadTarget.push(payload);
+      }
+    }
+  }
+
+  for (const subProcess of profile.processes) {
+    getAllPayloadsOfType(subProcess, type, payloadTarget);
+  }
+
+  return payloadTarget;
+}
+
+
+/**
+ * This is a helper function be able to run `await wait(500)`. Unfortunately this
+ * is needed as the act of collecting functions relies on the periodic sampling of
+ * the threads. See: https://bugzilla.mozilla.org/show_bug.cgi?id=1529053
+ *
+ * @param {number} time
+ * @returns {Promise}
+ */
+function wait(time) {
+  return new Promise(resolve => {
+    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+    setTimeout(resolve, time);
+  });
+}
 
 function getInflatedStackLocations(thread, sample) {
   let stackTable = thread.stackTable;
   let frameTable = thread.frameTable;
   let stringTable = thread.stringTable;
   let SAMPLE_STACK_SLOT = thread.samples.schema.stack;
   let STACK_PREFIX_SLOT = stackTable.schema.prefix;
   let STACK_FRAME_SLOT = stackTable.schema.frame;
new file mode 100644
--- /dev/null
+++ b/tools/profiler/tests/test_feature_mainthreadio.js
@@ -0,0 +1,109 @@
+/* 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/. */
+
+const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
+
+/**
+ * Test that the IOInterposer is working correctly to capture main thread IO.
+ */
+add_task(async () => {
+  if (!AppConstants.MOZ_GECKO_PROFILER) {
+    return;
+  }
+
+  // Let the test harness settle, in order to avoid extraneous FileIO operations. This
+  // helps avoid false positives that we are actually triggering FileIO.
+  await wait(10);
+
+  {
+    const filename = "profiler-mainthreadio-test-firstrun";
+    const payloads = await startProfilerAndgetFileIOPayloads(["mainthreadio"], filename);
+
+    greater(payloads.length, 0,
+       "FileIO markers were found when using the mainthreadio feature on the profiler.");
+
+    // It would be better to check on the filename, but Linux does not currently include
+    // it. See https://bugzilla.mozilla.org/show_bug.cgi?id=1533531
+    // ok(hasWritePayload(payloads, filename),
+    //    "A FileIO marker is found when using the mainthreadio feature on the profiler.");
+  }
+
+  {
+    const filename = "profiler-mainthreadio-test-no-instrumentation";
+    const payloads = await startProfilerAndgetFileIOPayloads([], filename);
+
+    equal(payloads.length, 0,
+          "No FileIO markers are found when the mainthreadio feature is not turned on " +
+          "in the profiler.");
+  }
+
+  {
+    const filename = "profiler-mainthreadio-test-secondrun";
+    const payloads = await startProfilerAndgetFileIOPayloads(["mainthreadio"], filename);
+
+    greater(payloads.length, 0,
+       "FileIO markers were found when re-starting the mainthreadio feature on the " +
+       "profiler.");
+    // It would be better to check on the filename, but Linux does not currently include
+    // it. See https://bugzilla.mozilla.org/show_bug.cgi?id=1533531
+    // ok(hasWritePayload(payloads, filename),
+    //    "Re-enabling the mainthreadio re-installs the interposer, and we can capture " +
+    //    "another FileIO payload.");
+  }
+});
+
+/**
+ * Start the profiler and get FileIO payloads.
+ * @param {Array} features The list of profiler features
+ * @param {string} filename A filename to trigger a write operation
+ */
+async function startProfilerAndgetFileIOPayloads(features, filename) {
+  const entries = 10000;
+  const interval = 10;
+  const threads = [];
+  Services.profiler.StartProfiler(entries, interval, features, features.length,
+                                  threads, threads.length);
+
+
+  const file = FileUtils.getFile("TmpD", [filename]);
+  if (file.exists()) {
+    console.warn(
+      "This test is triggering FileIO by writing to a file. However, the test found an " +
+      "existing file at the location it was trying to write to. This could happen " +
+      "because a previous run of the test failed to clean up after itself. This test " +
+      " will now clean up that file before running the test again."
+    );
+    file.remove(false);
+  }
+
+  const outputStream = FileUtils.openSafeFileOutputStream(file);
+
+  const data = "Test data.";
+  outputStream.write(data, data.length);
+  FileUtils.closeSafeFileOutputStream(outputStream);
+
+  file.remove(false);
+
+  // Wait for the profiler to collect a sample.
+  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1529053
+  await wait(500);
+
+  // Pause the profiler as we don't need to collect more samples as we retrieve
+  // and serialize the profile.
+  Services.profiler.PauseSampling();
+
+  const profile = await Services.profiler.getProfileDataAsync();
+  Services.profiler.StopProfiler();
+  return getAllPayloadsOfType(profile, "FileIO");
+}
+
+/**
+ * See if a list of payloads has a write operation from a file.
+ *
+ * @param {Array<Object>} payloads The payloads captured from the profiler.
+ * @param {string} filename The filename used to test a write operation.
+ */
+function hasWritePayload(payloads, filename) {
+  return payloads.some(payload => payload.filename.endsWith(filename));
+}
--- a/tools/profiler/tests/xpcshell.ini
+++ b/tools/profiler/tests/xpcshell.ini
@@ -10,8 +10,9 @@ skip-if = true
 skip-if = true
 [test_pause.js]
 [test_enterjit_osr.js]
 [test_enterjit_osr_disabling.js]
 skip-if = !debug
 [test_enterjit_osr_enabling.js]
 skip-if = !debug
 [test_asm.js]
+[test_feature_mainthreadio.js]
--- a/xpcom/build/NSPRInterposer.cpp
+++ b/xpcom/build/NSPRInterposer.cpp
@@ -7,87 +7,132 @@
 #include "IOInterposer.h"
 #include "NSPRInterposer.h"
 
 #include "prio.h"
 #include "private/pprio.h"
 #include "nsDebug.h"
 #include "nscore.h"
 
+#include <sys/param.h>
+#ifdef XP_MACOSX
+#include <fcntl.h>
+#else
+#include "prprf.h"
+#include <unistd.h>
+#endif
+
 namespace {
 
 using namespace mozilla;
 
 /* Original IO methods */
 PRCloseFN sCloseFn = nullptr;
 PRReadFN sReadFn = nullptr;
 PRWriteFN sWriteFn = nullptr;
 PRFsyncFN sFSyncFn = nullptr;
 PRFileInfoFN sFileInfoFn = nullptr;
 PRFileInfo64FN sFileInfo64Fn = nullptr;
 
+static int32_t GetPathFromFd(int32_t aFd, char *aBuf, size_t aBufSize) {
+#ifdef XP_MACOSX
+  NS_ASSERTION(aBufSize >= MAXPATHLEN,
+               "aBufSize should be a least MAXPATHLEN long");
+
+  return fcntl(aFd, F_GETPATH, aBuf);
+#else
+  char procPath[32];
+  if (PR_snprintf(procPath, sizeof(procPath),
+                  "/proc/self/fd/%i", aFd) == (PRUint32)-1) {
+    return -1;
+  }
+
+  int32_t ret = readlink(procPath, aBuf, aBufSize - 1);
+  if (ret > -1) {
+    aBuf[ret] = '\0';
+  }
+
+  return ret;
+#endif
+}
+
 /**
  * RAII class for timing the duration of an NSPR I/O call and reporting the
  * result to the IOInterposeObserver API.
  */
 class NSPRIOAutoObservation : public IOInterposeObserver::Observation {
  public:
-  explicit NSPRIOAutoObservation(IOInterposeObserver::Operation aOp)
-      : IOInterposeObserver::Observation(aOp, "NSPRIOInterposer") {}
+  explicit NSPRIOAutoObservation(IOInterposeObserver::Operation aOp,
+                                 PRFileDesc *aFd)
+      : IOInterposeObserver::Observation(aOp, "NSPRIOInterposer") {
+    char filename[MAXPATHLEN];
+    if (mShouldReport && aFd &&
+        GetPathFromFd(PR_FileDesc2NativeHandle(aFd), filename,
+                      sizeof(filename)) != -1) {
+      mFilename = NS_ConvertUTF8toUTF16(filename);
+    } else {
+      mFilename.Truncate();
+    }
+  }
+
+  void Filename(nsAString& aFilename) override { aFilename = mFilename; }
 
   ~NSPRIOAutoObservation() override { Report(); }
+
+ private:
+  nsString mFilename;
 };
 
 PRStatus PR_CALLBACK interposedClose(PRFileDesc* aFd) {
   // If we don't have a valid original function pointer something is very wrong.
   NS_ASSERTION(sCloseFn, "NSPR IO Interposing: sCloseFn is NULL");
 
-  NSPRIOAutoObservation timer(IOInterposeObserver::OpClose);
+  NSPRIOAutoObservation timer(IOInterposeObserver::OpClose, aFd);
   return sCloseFn(aFd);
 }
 
 int32_t PR_CALLBACK interposedRead(PRFileDesc* aFd, void* aBuf, int32_t aAmt) {
   // If we don't have a valid original function pointer something is very wrong.
   NS_ASSERTION(sReadFn, "NSPR IO Interposing: sReadFn is NULL");
 
-  NSPRIOAutoObservation timer(IOInterposeObserver::OpRead);
+  NSPRIOAutoObservation timer(IOInterposeObserver::OpRead, aFd);
   return sReadFn(aFd, aBuf, aAmt);
 }
 
 int32_t PR_CALLBACK interposedWrite(PRFileDesc* aFd, const void* aBuf,
                                     int32_t aAmt) {
   // If we don't have a valid original function pointer something is very wrong.
   NS_ASSERTION(sWriteFn, "NSPR IO Interposing: sWriteFn is NULL");
 
-  NSPRIOAutoObservation timer(IOInterposeObserver::OpWrite);
+  NSPRIOAutoObservation timer(IOInterposeObserver::OpWrite, aFd);
   return sWriteFn(aFd, aBuf, aAmt);
 }
 
 PRStatus PR_CALLBACK interposedFSync(PRFileDesc* aFd) {
   // If we don't have a valid original function pointer something is very wrong.
   NS_ASSERTION(sFSyncFn, "NSPR IO Interposing: sFSyncFn is NULL");
 
-  NSPRIOAutoObservation timer(IOInterposeObserver::OpFSync);
+  NSPRIOAutoObservation timer(IOInterposeObserver::OpFSync, aFd);
   return sFSyncFn(aFd);
 }
 
 PRStatus PR_CALLBACK interposedFileInfo(PRFileDesc* aFd, PRFileInfo* aInfo) {
   // If we don't have a valid original function pointer something is very wrong.
   NS_ASSERTION(sFileInfoFn, "NSPR IO Interposing: sFileInfoFn is NULL");
 
-  NSPRIOAutoObservation timer(IOInterposeObserver::OpStat);
+  NSPRIOAutoObservation timer(IOInterposeObserver::OpStat, aFd);
   return sFileInfoFn(aFd, aInfo);
 }
 
 PRStatus PR_CALLBACK interposedFileInfo64(PRFileDesc* aFd,
                                           PRFileInfo64* aInfo) {
   // If we don't have a valid original function pointer something is very wrong.
   NS_ASSERTION(sFileInfo64Fn, "NSPR IO Interposing: sFileInfo64Fn is NULL");
 
-  NSPRIOAutoObservation timer(IOInterposeObserver::OpStat);
+  NSPRIOAutoObservation timer(IOInterposeObserver::OpStat, aFd);
   return sFileInfo64Fn(aFd, aInfo);
 }
 
 }  // namespace
 
 namespace mozilla {
 
 void InitNSPRIOInterposing() {
--- a/xpcom/build/PoisonIOInterposerWin.cpp
+++ b/xpcom/build/PoisonIOInterposerWin.cpp
@@ -128,18 +128,21 @@ class WinIOAutoObservation : public IOIn
   WinIOAutoObservation(IOInterposeObserver::Operation aOp, nsAString& aFilename)
       : IOInterposeObserver::Observation(aOp, sReference),
         mFileHandle(nullptr),
         mHasQueriedFilename(false) {
     if (mShouldReport) {
       nsAutoString dosPath;
       if (NtPathToDosPath(aFilename, dosPath)) {
         mFilename = dosPath;
-        mHasQueriedFilename = true;
+      } else {
+        // If we can't get a dosPath, what we have is better than nothing.
+        mFilename = aFilename;
       }
+      mHasQueriedFilename = true;
       mOffset.QuadPart = 0;
     }
   }
 
   // Custom implementation of IOInterposeObserver::Observation::Filename
   void Filename(nsAString& aFilename) override;
 
   ~WinIOAutoObservation() { Report(); }
@@ -155,20 +158,21 @@ class WinIOAutoObservation : public IOIn
 const char* WinIOAutoObservation::sReference = "PoisonIOInterposer";
 
 // Get filename for this observation
 void WinIOAutoObservation::Filename(nsAString& aFilename) {
   // If mHasQueriedFilename is true, then filename is already stored in
   // mFilename
   if (mHasQueriedFilename) {
     aFilename = mFilename;
+    return;
   }
 
   nsAutoString filename;
-  if (HandleToFilename(mFileHandle, mOffset, filename)) {
+  if (mFileHandle && HandleToFilename(mFileHandle, mOffset, filename)) {
     mFilename = filename;
   }
   mHasQueriedFilename = true;
 
   aFilename = mFilename;
 }
 
 /*************************** IO Interposing Methods ***************************/