Merge mozilla-inbound to mozilla-central a=merge
authorDorel Luca <dluca@mozilla.com>
Thu, 15 Feb 2018 00:23:02 +0200
changeset 456169 27fd083ed7ee5782e52a5eaf0286c5ffa8b35a9e
parent 456118 9284d8cfa1e7618836fb630288d780eca3d07a9c (current diff)
parent 456168 04b90b86ce92b4f2859e968e61fc7d1a3bcbd6ec (diff)
child 456249 223a0551c4e0a4e1a4243c8d7b6de0a774aec829
child 456370 0cd7406c124f4f11eca9cc898bd42a3531456c0b
push id8799
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 16:46:23 +0000
treeherdermozilla-beta@15334014dc67 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
27fd083ed7ee / 60.0a1 / 20180214224814 / files
nightly linux64
27fd083ed7ee / 60.0a1 / 20180214224814 / files
nightly mac
27fd083ed7ee / 60.0a1 / 20180214224814 / files
nightly win32
27fd083ed7ee / 60.0a1 / 20180214224814 / files
nightly win64
27fd083ed7ee / 60.0a1 / 20180214224814 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central a=merge
toolkit/modules/LightweightThemeConsumer.jsm
new file mode 100644
--- /dev/null
+++ b/browser/base/content/aboutRobots.css
@@ -0,0 +1,26 @@
+#errorPageContainer {
+  background-image: none;
+}
+
+#errorPageContainer:before {
+  content: url('chrome://browser/content/aboutRobots-icon.png');
+  position: absolute;
+}
+
+body[dir=rtl] #icon,
+body[dir=rtl] #errorPageContainer:before {
+  transform: scaleX(-1);
+}
+
+#widget1 {
+  position: absolute;
+  bottom: -12px;
+  left: -10px;
+}
+
+#widget2 {
+  position: absolute;
+  bottom: -12px;
+  right: -10px;
+  transform: scaleX(-1);
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/aboutRobots.js
@@ -0,0 +1,11 @@
+var buttonClicked = false;
+var button = document.getElementById("errorTryAgain");
+button.onclick = function() {
+  if (buttonClicked) {
+    button.style.visibility = "hidden";
+  } else {
+    var newLabel = button.getAttribute("label2");
+    button.textContent = newLabel;
+    buttonClicked = true;
+  }
+};
--- a/browser/base/content/aboutRobots.xhtml
+++ b/browser/base/content/aboutRobots.xhtml
@@ -17,49 +17,21 @@
   %globalDTD;
   <!ENTITY % aboutrobotsDTD
     SYSTEM "chrome://browser/locale/aboutRobots.dtd">
   %aboutrobotsDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
+    <meta http-equiv="Content-Security-Policy" content="default-src chrome:" />
     <title>&robots.pagetitle;</title>
     <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
     <link rel="icon" type="image/png" id="favicon" href="chrome://browser/content/robot.ico"/>
-
-    <script type="application/javascript"><![CDATA[
-      var buttonClicked = false;
-      function robotButton() {
-        var button = document.getElementById("errorTryAgain");
-        if (buttonClicked) {
-          button.style.visibility = "hidden";
-        } else {
-          var newLabel = button.getAttribute("label2");
-          button.textContent = newLabel;
-          buttonClicked = true;
-        }
-      }
-    ]]></script>
-
-    <style type="text/css"><![CDATA[
-      #errorPageContainer {
-        background-image: none;
-      }
-
-      #errorPageContainer:before {
-        content: url('chrome://browser/content/aboutRobots-icon.png');
-        position: absolute;
-      }
-
-      body[dir=rtl] #icon,
-      body[dir=rtl] #errorPageContainer:before {
-        transform: scaleX(-1);
-      }
-    ]]></style>
+    <link rel="stylesheet" href="chrome://browser/content/aboutRobots.css" type="text/css"/>
   </head>
 
   <body dir="&locale.dir;">
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
     <div id="errorPageContainer">
 
       <!-- Error Title -->
@@ -89,19 +61,16 @@
         <div id="errorTrailerDesc">
           <p id="errorTrailerDescText">&robots.errorTrailerDescText;</p>
         </div>
 
       </div>
 
       <!-- Button -->
       <button id="errorTryAgain"
-              label2="&robots.dontpress;"
-              onclick="robotButton();">&retry.label;</button>
+              label2="&robots.dontpress;">&retry.label;</button>
 
-      <img src="chrome://browser/content/aboutRobots-widget-left.png"
-           style="position: absolute; bottom: -12px; left: -10px;"/>
-      <img src="chrome://browser/content/aboutRobots-widget-left.png"
-           style="position: absolute; bottom: -12px; right: -10px; transform: scaleX(-1);"/>
+      <img id="widget1" src="chrome://browser/content/aboutRobots-widget-left.png"/>
+      <img id="widget2" src="chrome://browser/content/aboutRobots-widget-left.png"/>
     </div>
-
   </body>
+  <script type="application/javascript" src="chrome://browser/content/aboutRobots.js"/>
 </html>
--- a/browser/base/content/aboutTabCrashed.xhtml
+++ b/browser/base/content/aboutTabCrashed.xhtml
@@ -16,16 +16,17 @@
   %brandDTD;
   <!ENTITY % tabCrashedDTD
     SYSTEM "chrome://browser/locale/aboutTabCrashed.dtd">
   %tabCrashedDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
+    <meta http-equiv="Content-Security-Policy" content="default-src chrome:" />
     <link rel="stylesheet" type="text/css" media="all"
           href="chrome://global/skin/in-content/info-pages.css"/>
     <link rel="stylesheet" type="text/css" media="all"
           href="chrome://browser/content/aboutTabCrashed.css"/>
     <link rel="stylesheet" type="text/css" media="all"
           href="chrome://browser/skin/aboutTabCrashed.css"/>
 
     <title>&tabCrashed.title;</title>
--- a/browser/base/content/abouthome/aboutHome.xhtml
+++ b/browser/base/content/abouthome/aboutHome.xhtml
@@ -14,16 +14,17 @@
   <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
   %aboutHomeDTD;
   <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
   %browserDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
+    <meta http-equiv="Content-Security-Policy" content="default-src chrome:" />
     <title>&abouthome.pageTitle;</title>
 
     <link rel="icon" type="image/png" id="favicon"
           href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" type="text/css" media="all"
           href="chrome://browser/content/contentSearchUI.css"/>
     <link rel="stylesheet" type="text/css" media="all" defer="defer"
           href="chrome://browser/content/abouthome/aboutHome.css"/>
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -6,16 +6,18 @@ browser.jar:
 %  overlay chrome://global/content/viewSource.xul chrome://browser/content/viewSourceOverlay.xul
 %  overlay chrome://global/content/viewPartialSource.xul chrome://browser/content/viewSourceOverlay.xul
 
         content/browser/aboutDialog-appUpdater.js     (content/aboutDialog-appUpdater.js)
 *       content/browser/aboutDialog.xul               (content/aboutDialog.xul)
         content/browser/aboutDialog.js                (content/aboutDialog.js)
         content/browser/aboutDialog.css               (content/aboutDialog.css)
         content/browser/aboutRobots.xhtml             (content/aboutRobots.xhtml)
+        content/browser/aboutRobots.js                (content/aboutRobots.js)
+        content/browser/aboutRobots.css               (content/aboutRobots.css)
 *       content/browser/abouthome/aboutHome.xhtml     (content/abouthome/aboutHome.xhtml)
         content/browser/abouthome/aboutHome.js        (content/abouthome/aboutHome.js)
 *       content/browser/abouthome/aboutHome.css       (content/abouthome/aboutHome.css)
         content/browser/abouthome/snippet1.png        (content/abouthome/snippet1.png)
         content/browser/abouthome/snippet2.png        (content/abouthome/snippet2.png)
         content/browser/abouthome/downloads.png       (content/abouthome/downloads.png)
         content/browser/abouthome/bookmarks.png       (content/abouthome/bookmarks.png)
         content/browser/abouthome/history.png         (content/abouthome/history.png)
--- a/browser/components/feeds/content/subscribe.js
+++ b/browser/components/feeds/content/subscribe.js
@@ -16,8 +16,18 @@ var SubscribeHandler = {
   writeContent: function SH_writeContent() {
     this._feedWriter.writeContent();
   },
 
   uninit: function SH_uninit() {
     this._feedWriter.close();
   }
 };
+
+SubscribeHandler.init();
+
+window.onload = function() {
+  SubscribeHandler.writeContent();
+};
+
+window.onunload = function() {
+  SubscribeHandler.uninit();
+};
--- a/browser/components/feeds/content/subscribe.xhtml
+++ b/browser/components/feeds/content/subscribe.xhtml
@@ -17,25 +17,24 @@
   %feedDTD;
 ]>
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 
 <html id="feedHandler"
       xmlns="http://www.w3.org/1999/xhtml">
   <head>
+    <meta http-equiv="Content-Security-Policy" content="default-src chrome:; img-src *; media-src *" />
     <title>&feedPage.title;</title>
     <link rel="stylesheet"
           href="chrome://browser/skin/feeds/subscribe.css"
           type="text/css"
           media="all"/>
-    <script type="application/javascript"
-            src="chrome://browser/content/feeds/subscribe.js"/>
   </head>
-  <body onload="SubscribeHandler.writeContent();" onunload="SubscribeHandler.uninit();">
+  <body>
     <div id="feedHeaderContainer">
       <div id="feedHeader" dir="&locale.dir;">
         <div id="feedIntroText">
           <p id="feedSubscriptionInfo1" />
           <p id="feedSubscriptionInfo2" />
         </div>
         <div id="feedSubscribeLine">
           <label id="subscribeUsingDescription">
@@ -47,28 +46,23 @@
           <label id="checkboxText">
             <input type="checkbox" id="alwaysUse" class="alwaysUse" checked="false"/>
           </label>
           <button id="subscribeButton">&feedSubscribeNow;</button>
         </div>
       </div>
       <div id="feedHeaderContainerSpacer"/>
     </div>
-
-    <script type="application/javascript">
-      /* import-globals-from subscribe.js */
-      SubscribeHandler.init();
-    </script>
-
     <div id="feedBody">
       <div id="feedTitle">
         <a id="feedTitleLink">
           <img id="feedTitleImage"/>
         </a>
         <div id="feedTitleContainer">
           <h1 id="feedTitleText"/>
           <h2 id="feedSubtitleText"/>
         </div>
       </div>
       <div id="feedContent"/>
     </div>
   </body>
+  <script type="application/javascript" src="chrome://browser/content/feeds/subscribe.js"/>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/bookmarklet_windowOpen_dummy.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Bookmarklet windowOpen Dummy</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Bookmarklet windowOpen Dummy</p>
+</body>
+</html>
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -17,16 +17,18 @@ support-files =
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_bookmark_change_location.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_bookmark_folder_moveability.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_bookmark_remove_tags.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_bookmarklet_windowOpen.js]
+support-files =
+  bookmarklet_windowOpen_dummy.html
 [browser_bookmarks_change_title.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 [browser_bookmarks_sidebar_search.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
 support-files =
   pageopeningwindow.html
 [browser_bookmarkProperties_addFolderDefaultButton.js]
 skip-if = (os == 'win' && ccov) # Bug 1423667
--- a/browser/components/places/tests/browser/browser_bookmarklet_windowOpen.js
+++ b/browser/components/places/tests/browser/browser_bookmarklet_windowOpen.js
@@ -1,11 +1,14 @@
 "use strict";
 
-const TEST_URL = "http://example.com/browser/browser/components/places/tests/browser/pageopeningwindow.html";
+let BASE_URL = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
+                                                   "http://example.com/");
+const TEST_URL = BASE_URL + "pageopeningwindow.html";
+const DUMMY_URL = BASE_URL + "bookmarklet_windowOpen_dummy.html";
 
 function makeBookmarkFor(url, keyword) {
   return Promise.all([
     PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
                                    title: "bookmarklet",
                                    url }),
     PlacesUtils.keywords.insert({url,
                                  keyword})
@@ -16,17 +19,17 @@ function makeBookmarkFor(url, keyword) {
 add_task(async function openKeywordBookmarkWithWindowOpen() {
   // This is the current default, but let's not assume that...
   await SpecialPowers.pushPrefEnv({"set": [
     [ "browser.link.open_newwindow", 3 ],
     [ "dom.disable_open_during_load", true ]
   ]});
 
   let moztab;
-  let tabOpened = BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla")
+  let tabOpened = BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_URL)
                     .then((tab) => { moztab = tab; });
   let keywordForBM = "openmeatab";
 
   let bookmarkInfo;
   let bookmarkCreated =
     makeBookmarkFor("javascript:void open('" + TEST_URL + "')", keywordForBM)
         .then((values) => {
           bookmarkInfo = values[0];
--- a/devtools/client/framework/components/toolbox-controller.js
+++ b/devtools/client/framework/components/toolbox-controller.js
@@ -18,17 +18,17 @@ class ToolboxController extends Componen
     super(props, context);
 
     // See the ToolboxToolbar propTypes for documentation on each of these items in
     // state, and for the definitions of the props that are expected to be passed in.
     this.state = {
       focusedButton: ELEMENT_PICKER_ID,
       currentToolId: null,
       canRender: false,
-      highlightedTool: "",
+      highlightedTools: new Set(),
       areDockButtonsEnabled: true,
       panelDefinitions: [],
       hostTypes: [],
       canCloseToolbox: true,
       toolboxButtons: [],
       buttonIds: [],
       checkedButtonsUpdated: () => {
         this.forceUpdate();
@@ -113,22 +113,26 @@ class ToolboxController extends Componen
   }
 
   setOptionsPanel(optionsPanel) {
     this.setState({ optionsPanel });
     this.updateButtonIds();
   }
 
   highlightTool(highlightedTool) {
-    this.setState({ highlightedTool });
+    let { highlightedTools } = this.state;
+    highlightedTools.add(highlightedTool);
+    this.setState({ highlightedTools });
   }
 
   unhighlightTool(id) {
-    if (this.state.highlightedTool === id) {
-      this.setState({ highlightedTool: "" });
+    let { highlightedTools } = this.state;
+    if (highlightedTools.has(id)) {
+      highlightedTools.delete(id);
+      this.setState({ highlightedTools });
     }
   }
 
   setDockButtonsEnabled(areDockButtonsEnabled) {
     this.setState({ areDockButtonsEnabled });
     this.updateButtonIds();
   }
 
--- a/devtools/client/framework/components/toolbox-tab.js
+++ b/devtools/client/framework/components/toolbox-tab.js
@@ -10,17 +10,17 @@ const {img, button, span} = dom;
 
 class ToolboxTab extends Component {
   // See toolbox-toolbar propTypes for details on the props used here.
   static get propTypes() {
     return {
       currentToolId: PropTypes.string,
       focusButton: PropTypes.func,
       focusedButton: PropTypes.string,
-      highlightedTool: PropTypes.string,
+      highlightedTools: PropTypes.object.isRequired,
       panelDefinition: PropTypes.object,
       selectTool: PropTypes.func,
     };
   }
 
   constructor(props) {
     super(props);
     this.renderIcon = this.renderIcon.bind(this);
@@ -34,25 +34,25 @@ class ToolboxTab extends Component {
     return [
       img({
         src: icon
       }),
     ];
   }
 
   render() {
-    const {panelDefinition, currentToolId, highlightedTool, selectTool,
+    const {panelDefinition, currentToolId, highlightedTools, selectTool,
            focusedButton, focusButton} = this.props;
     const {id, tooltip, label, iconOnly} = panelDefinition;
     const isHighlighted = id === currentToolId;
 
     const className = [
       "devtools-tab",
       currentToolId === id ? "selected" : "",
-      highlightedTool === id ? "highlighted" : "",
+      highlightedTools.has(id) ? "highlighted" : "",
       iconOnly ? "devtools-tab-icon-only" : ""
     ].join(" ");
 
     return button(
       {
         className,
         id: `toolbox-tab-${id}`,
         "data-id": id,
--- a/devtools/client/framework/components/toolbox-tabs.js
+++ b/devtools/client/framework/components/toolbox-tabs.js
@@ -15,17 +15,17 @@ const ToolboxTab = createFactory(require
 
 class ToolboxTabs extends Component {
   // See toolbox-toolbar propTypes for details on the props used here.
   static get propTypes() {
     return {
       currentToolId: PropTypes.string,
       focusButton: PropTypes.func,
       focusedButton: PropTypes.string,
-      highlightedTool: PropTypes.string,
+      highlightedTools: PropTypes.object,
       panelDefinitions: PropTypes.array,
       selectTool: PropTypes.func,
       toolbox: PropTypes.object,
       L10N: PropTypes.object,
     };
   }
 
   constructor(props) {
@@ -83,26 +83,26 @@ class ToolboxTabs extends Component {
    * a toolbox tab for each of them. Will render an all-tabs button if the
    * container has an overflow.
    */
   render() {
     let {
       currentToolId,
       focusButton,
       focusedButton,
-      highlightedTool,
+      highlightedTools,
       panelDefinitions,
       selectTool,
     } = this.props;
 
     let tabs = panelDefinitions.map(panelDefinition => ToolboxTab({
       currentToolId,
       focusButton,
       focusedButton,
-      highlightedTool,
+      highlightedTools,
       panelDefinition,
       selectTool,
     }));
 
     // A wrapper is needed to get flex sizing correct in XUL.
     return div(
       {
         className: "toolbox-tabs-wrapper"
--- a/devtools/client/framework/components/toolbox-toolbar.js
+++ b/devtools/client/framework/components/toolbox-toolbar.js
@@ -22,18 +22,19 @@ class ToolboxToolbar extends Component {
     return {
       // The currently focused item (for arrow keyboard navigation)
       // This ID determines the tabindex being 0 or -1.
       focusedButton: PropTypes.string,
       // List of command button definitions.
       toolboxButtons: PropTypes.array,
       // The id of the currently selected tool, e.g. "inspector"
       currentToolId: PropTypes.string,
-      // An optionally highlighted tool, e.g. "inspector"
-      highlightedTool: PropTypes.string,
+      // An optionally highlighted tools, e.g. "inspector".
+      // Note: highlightedTools must be an instance of Set.
+      highlightedTools: PropTypes.object,
       // List of tool panel definitions.
       panelDefinitions: PropTypes.array,
       // Function to select a tool based on its id.
       selectTool: PropTypes.func,
       // Keep a record of what button is focused.
       focusButton: PropTypes.func,
       // The options button definition.
       optionsPanel: PropTypes.object,
@@ -144,23 +145,25 @@ function renderToolboxButtons({toolboxBu
  * The options button is a ToolboxTab just like in the ToolboxTabs component. However
  * it is separate from the normal tabs, so deal with it separately here.
  *
  * @param {Object}   optionsPanel - A single panel definition for the options panel.
  * @param {String}   currentToolId - The currently selected tool's id; e.g. "inspector".
  * @param {Function} selectTool - Function to select a tool in the toolbox.
  * @param {String}   focusedButton - The id of the focused button.
  * @param {Function} focusButton - Keep a record of the currently focused button.
+ * @param {Object}   highlightedTools - Optionally highlighted tools, e.g. "inspector".
  */
 function renderOptions({optionsPanel, currentToolId, selectTool, focusedButton,
-                        focusButton}) {
+                        focusButton, highlightedTools}) {
   return div({id: "toolbox-option-container"}, ToolboxTab({
     panelDefinition: optionsPanel,
     currentToolId,
     selectTool,
+    highlightedTools,
     focusedButton,
     focusButton,
   }));
 }
 
 /**
  * Render a separator.
  */
--- a/devtools/client/framework/test/browser_toolbox_highlight.js
+++ b/devtools/client/framework/test/browser_toolbox_highlight.js
@@ -1,43 +1,59 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 var {Toolbox} = require("devtools/client/framework/toolbox");
 
 var toolbox = null;
 
 function test() {
   Task.spawn(function* () {
     const URL = "data:text/plain;charset=UTF-8,Nothing to see here, move along";
 
     const TOOL_ID_1 = "jsdebugger";
     const TOOL_ID_2 = "webconsole";
     yield addTab(URL);
 
     const target = TargetFactory.forTab(gBrowser.selectedTab);
-    toolbox = yield gDevTools.showToolbox(target, TOOL_ID_1, Toolbox.HostType.BOTTOM)
+    toolbox = yield gDevTools.showToolbox(target, TOOL_ID_1, Toolbox.HostType.BOTTOM);
 
     // select tool 2
-    yield toolbox.selectTool(TOOL_ID_2)
+    yield toolbox.selectTool(TOOL_ID_2);
     // and highlight the first one
     yield highlightTab(TOOL_ID_1);
     // to see if it has the proper class.
     yield checkHighlighted(TOOL_ID_1);
     // Now switch back to first tool
     yield toolbox.selectTool(TOOL_ID_1);
     // to check again. But there is no easy way to test if
     // it is showing orange or not.
     yield checkNoHighlightWhenSelected(TOOL_ID_1);
     // Switch to tool 2 again
     yield toolbox.selectTool(TOOL_ID_2);
     // and check again.
     yield checkHighlighted(TOOL_ID_1);
+    // Highlight another tool
+    yield highlightTab(TOOL_ID_2);
+    // Check that both tools are highlighted.
+    yield checkHighlighted(TOOL_ID_1);
+    // Check second tool being both highlighted and selected.
+    yield checkNoHighlightWhenSelected(TOOL_ID_2);
+    // Select tool 1
+    yield toolbox.selectTool(TOOL_ID_1);
+    // Check second tool is still highlighted
+    yield checkHighlighted(TOOL_ID_2);
+    // Unhighlight the second tool
+    yield unhighlightTab(TOOL_ID_2);
+    // to see the classes gone.
+    yield checkNoHighlight(TOOL_ID_2);
     // Now unhighlight the tool
     yield unhighlightTab(TOOL_ID_1);
     // to see the classes gone.
     yield checkNoHighlight(TOOL_ID_1);
 
     // Now close the toolbox and exit.
     executeSoon(() => {
       toolbox.destroy().then(() => {
--- a/devtools/client/shared/components/VirtualizedTree.js
+++ b/devtools/client/shared/components/VirtualizedTree.js
@@ -190,16 +190,19 @@ class Tree extends Component {
       // Optional props
 
       // The currently focused item, if any such item exists.
       focused: PropTypes.any,
 
       // Handle when a new item is focused.
       onFocus: PropTypes.func,
 
+      // Handle when item is activated with a keyboard (using Space or Enter)
+      onActivate: PropTypes.func,
+
       // The depth to which we should automatically expand new items.
       autoExpandDepth: PropTypes.number,
 
       // Note: the two properties below are mutually exclusive. Only one of the
       // label properties is necessary.
       // ID of an element whose textual content serves as an accessible label for
       // a tree.
       labelledby: PropTypes.string,
@@ -238,16 +241,19 @@ class Tree extends Component {
     };
 
     this._onExpand = oncePerAnimationFrame(this._onExpand).bind(this);
     this._onCollapse = oncePerAnimationFrame(this._onCollapse).bind(this);
     this._onScroll = oncePerAnimationFrame(this._onScroll).bind(this);
     this._focusPrevNode = oncePerAnimationFrame(this._focusPrevNode).bind(this);
     this._focusNextNode = oncePerAnimationFrame(this._focusNextNode).bind(this);
     this._focusParentNode = oncePerAnimationFrame(this._focusParentNode).bind(this);
+    this._focusFirstNode = oncePerAnimationFrame(this._focusFirstNode).bind(this);
+    this._focusLastNode = oncePerAnimationFrame(this._focusLastNode).bind(this);
+    this._activateNode = oncePerAnimationFrame(this._activateNode).bind(this);
 
     this._autoExpand = this._autoExpand.bind(this);
     this._preventArrowKeyScrolling = this._preventArrowKeyScrolling.bind(this);
     this._updateHeight = this._updateHeight.bind(this);
     this._dfs = this._dfs.bind(this);
     this._dfsFromRoots = this._dfsFromRoots.bind(this);
     this._focus = this._focus.bind(this);
     this._onBlur = this._onBlur.bind(this);
@@ -318,19 +324,22 @@ class Tree extends Component {
         }
     }
   }
 
   /**
    * Updates the state's height based on clientHeight.
    */
   _updateHeight() {
-    this.setState({
-      height: this.refs.tree.clientHeight
-    });
+    if (this.refs.tree.clientHeight &&
+        this.refs.tree.clientHeight !== this.state.height) {
+      this.setState({
+        height: this.refs.tree.clientHeight
+      });
+    }
   }
 
   /**
    * Perform a pre-order depth-first search from item.
    */
   _dfs(item, maxDepth = Infinity, traversal = [], _depth = 0) {
     traversal.push({ item, depth: _depth });
 
@@ -489,19 +498,49 @@ class Tree extends Component {
 
       case "ArrowRight":
         if (!this.props.isExpanded(this.props.focused)) {
           this._onExpand(this.props.focused);
         } else {
           this._focusNextNode();
         }
         break;
+
+      case "Home":
+        this._focusFirstNode();
+        break;
+
+      case "End":
+        this._focusLastNode();
+        break;
+
+      case "Enter":
+      case " ":
+        this._activateNode();
+        break;
     }
   }
 
+  _activateNode() {
+    if (this.props.onActivate) {
+      this.props.onActivate(this.props.focused);
+    }
+  }
+
+  _focusFirstNode() {
+    const traversal = this._dfsFromRoots();
+    this._focus(0, traversal[0].item);
+  }
+
+  _focusLastNode() {
+    const traversal = this._dfsFromRoots();
+    const lastIndex = traversal.length - 1;
+    this._focus(lastIndex, traversal[lastIndex].item);
+  }
+
   /**
    * Sets the previous node relative to the currently focused item, to focused.
    */
   _focusPrevNode() {
     // Start a depth first search and keep going until we reach the currently
     // focused node. Focus the previous node in the DFS, if it exists. If it
     // doesn't exist, we're at the first node already.
 
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -21,8 +21,9 @@ support-files =
 [test_tree_04.html]
 [test_tree_05.html]
 [test_tree_06.html]
 [test_tree_07.html]
 [test_tree_08.html]
 [test_tree_09.html]
 [test_tree_10.html]
 [test_tree_11.html]
+[test_tree_12.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_tree_12.html
@@ -0,0 +1,258 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!DOCTYPE HTML>
+<html>
+<!--
+Test keyboard navigation/activation with the VirtualizedTree component.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Tree component test</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
+
+"use strict";
+
+window.onload = async function () {
+  try {
+    const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
+    const { createFactory } = browserRequire("devtools/client/shared/vendor/react");
+    const { Simulate } =
+      browserRequire("devtools/client/shared/vendor/react-dom-test-utils");
+    const Tree =
+      createFactory(browserRequire("devtools/client/shared/components/VirtualizedTree"));
+
+    function renderTree(props) {
+      const treeProps = {
+        ...TEST_TREE_INTERFACE,
+        onFocus: x => renderTree({ focused: x }),
+        ...props
+      };
+
+      return ReactDOM.render(Tree(treeProps), window.document.body);
+    }
+
+    const checker = Symbol();
+    let isActivated;
+    const mockFn = activated => {
+      isActivated = activated;
+    };
+
+    const tree = renderTree();
+
+    TEST_TREE.expanded = new Set("ABCDEFGHIJKLMNO".split(""));
+
+    // Test Home key -----------------------------------------------------------
+
+    info("Press Home to move to the first node.");
+    renderTree({ focused: "L" });
+    Simulate.keyDown(document.querySelector(".tree"), { key: "Home" });
+    await forceRender(tree);
+
+    isRenderedTree(document.body.textContent, [
+      "A:true",
+      "-B:false",
+      "--E:false",
+      "---K:false",
+      "---L:false",
+      "--F:false",
+      "--G:false",
+      "-C:false",
+      "--H:false",
+      "--I:false",
+      "-D:false",
+      "--J:false",
+      "M:false",
+      "-N:false",
+      "--O:false",
+    ], "After the Home key, A should be focused.");
+
+    info("Press Home again when already on first node.");
+    Simulate.keyDown(document.querySelector(".tree"), { key: "Home" });
+    await forceRender(tree);
+
+    isRenderedTree(document.body.textContent, [
+      "A:true",
+      "-B:false",
+      "--E:false",
+      "---K:false",
+      "---L:false",
+      "--F:false",
+      "--G:false",
+      "-C:false",
+      "--H:false",
+      "--I:false",
+      "-D:false",
+      "--J:false",
+      "M:false",
+      "-N:false",
+      "--O:false",
+    ], "After the Home key again, A should still be focused.");
+
+    // Test End key ------------------------------------------------------------
+
+    info("Press End to move to the last node.");
+    Simulate.keyDown(document.querySelector(".tree"), { key: "End" });
+    await forceRender(tree);
+
+    isRenderedTree(document.body.textContent, [
+      "A:false",
+      "-B:false",
+      "--E:false",
+      "---K:false",
+      "---L:false",
+      "--F:false",
+      "--G:false",
+      "-C:false",
+      "--H:false",
+      "--I:false",
+      "-D:false",
+      "--J:false",
+      "M:false",
+      "-N:false",
+      "--O:true",
+    ], "After the End key, O should be focused.");
+
+    info("Press End again when already on last node.");
+    Simulate.keyDown(document.querySelector(".tree"), { key: "End" });
+    await forceRender(tree);
+
+    isRenderedTree(document.body.textContent, [
+      "A:false",
+      "-B:false",
+      "--E:false",
+      "---K:false",
+      "---L:false",
+      "--F:false",
+      "--G:false",
+      "-C:false",
+      "--H:false",
+      "--I:false",
+      "-D:false",
+      "--J:false",
+      "M:false",
+      "-N:false",
+      "--O:true",
+    ], "After the End key again, O should still be focused.");
+
+    // Test Enter key ----------------------------------------------------------
+
+    info("Press Enter to activate node, when onActivate is not passed.");
+    isActivated = checker;
+    renderTree({ focused: "L" });
+    Simulate.keyDown(document.querySelector(".tree"), { key: "Enter" });
+    await forceRender(tree);
+
+    isRenderedTree(document.body.textContent, [
+      "A:false",
+      "-B:false",
+      "--E:false",
+      "---K:false",
+      "---L:true",
+      "--F:false",
+      "--G:false",
+      "-C:false",
+      "--H:false",
+      "--I:false",
+      "-D:false",
+      "--J:false",
+      "M:false",
+      "-N:false",
+      "--O:false",
+    ], "After the Enter, L should be focused and the tree remained unchanged.");
+    ok(isActivated === checker,
+       "Since onActivate was not specified, 'isActivated' should not be set.");
+
+    info("Press Enter to activate node, when onActivate is passed.");
+    isActivated = checker;
+    renderTree({ focused: "L", onActivate: mockFn });
+    Simulate.keyDown(document.querySelector(".tree"), { key: "Enter" });
+    await forceRender(tree);
+
+    isRenderedTree(document.body.textContent, [
+      "A:false",
+      "-B:false",
+      "--E:false",
+      "---K:false",
+      "---L:true",
+      "--F:false",
+      "--G:false",
+      "-C:false",
+      "--H:false",
+      "--I:false",
+      "-D:false",
+      "--J:false",
+      "M:false",
+      "-N:false",
+      "--O:false",
+    ], "After the Enter, L should be focused and the tree remained unchanged.");
+    is(isActivated, "L", "onActivate function was called with the right node.");
+
+    // Test Space key ----------------------------------------------------------
+
+    info("Press Space to activate node, when onActivate is not passed.");
+    isActivated = checker;
+    renderTree({ focused: "K" });
+    Simulate.keyDown(document.querySelector(".tree"), { key: " " });
+    await forceRender(tree);
+
+    isRenderedTree(document.body.textContent, [
+      "A:false",
+      "-B:false",
+      "--E:false",
+      "---K:true",
+      "---L:false",
+      "--F:false",
+      "--G:false",
+      "-C:false",
+      "--H:false",
+      "--I:false",
+      "-D:false",
+      "--J:false",
+      "M:false",
+      "-N:false",
+      "--O:false",
+    ], "After the Space, K should be focused and the tree remained unchanged.");
+    ok(isActivated === checker,
+       "Since onActivate was not specified, 'isActivated' should not be set.");
+
+    info("Press Space to activate node, when onActivate is passed.");
+    isActivated = checker;
+    renderTree({ focused: "K", onActivate: mockFn });
+    Simulate.keyDown(document.querySelector(".tree"), { key: " " });
+    await forceRender(tree);
+
+    isRenderedTree(document.body.textContent, [
+      "A:false",
+      "-B:false",
+      "--E:false",
+      "---K:true",
+      "---L:false",
+      "--F:false",
+      "--G:false",
+      "-C:false",
+      "--H:false",
+      "--I:false",
+      "-D:false",
+      "--J:false",
+      "M:false",
+      "-N:false",
+      "--O:false",
+    ], "After the Space, K should be focused and the tree remained unchanged.");
+    is(isActivated, "K", "onActivate function was called with the right node.");
+  } catch (e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+};
+</script>
+</pre>
+</body>
+</html>
--- a/devtools/client/shared/components/tree/TreeHeader.js
+++ b/devtools/client/shared/components/tree/TreeHeader.js
@@ -81,17 +81,20 @@ define(function (require, exports, modul
         cells.push(
           td({
             className: classNames.join(" "),
             style: cellStyle,
             role: "presentation",
             id: col.id,
             key: col.id,
           },
-            visible ? div({ className: "treeHeaderCellBox" }, col.title) : null
+            visible ? div({
+              className: "treeHeaderCellBox",
+              role: "presentation"
+            }, col.title) : null
           )
         );
       });
 
       return (
         thead({
           role: "presentation"
         }, tr({
--- a/devtools/client/shared/components/tree/TreeView.js
+++ b/devtools/client/shared/components/tree/TreeView.js
@@ -13,21 +13,31 @@ define(function (require, exports, modul
   const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
   const dom = require("devtools/client/shared/vendor/react-dom-factories");
 
   // Reps
   const { ObjectProvider } = require("./ObjectProvider");
   const TreeRow = createFactory(require("./TreeRow"));
   const TreeHeader = createFactory(require("./TreeHeader"));
 
+  const SUPPORTED_KEYS = [
+    "ArrowUp",
+    "ArrowDown",
+    "ArrowLeft",
+    "ArrowRight",
+    "End",
+    "Home"
+  ];
+
   const defaultProps = {
     object: null,
     renderRow: null,
     provider: ObjectProvider,
     expandedNodes: new Set(),
+    selected: null,
     expandableStrings: true,
     columns: []
   };
 
   /**
    * This component represents a tree view with expandable/collapsible nodes.
    * The tree is rendered using <table> element where every node is represented
    * by <tr> element. The tree is one big table where nodes (rows) are properly
@@ -94,20 +104,24 @@ define(function (require, exports, modul
         // Custom cell renderer
         renderCell: PropTypes.func,
         // Custom value renderer
         renderValue: PropTypes.func,
         // Custom tree label (including a toggle button) renderer
         renderLabelCell: PropTypes.func,
         // Set of expanded nodes
         expandedNodes: PropTypes.object,
+        // Selected node
+        selected: PropTypes.string,
         // Custom filtering callback
         onFilter: PropTypes.func,
         // Custom sorting callback
         onSort: PropTypes.func,
+        // Custom row click callback
+        onClickRow: PropTypes.func,
         // A header is displayed if set to true
         header: PropTypes.bool,
         // Long string is expandable by a toggle button
         expandableStrings: PropTypes.bool,
         // Array of columns
         columns: PropTypes.arrayOf(PropTypes.shape({
           id: PropTypes.string.isRequired,
           title: PropTypes.string,
@@ -121,46 +135,52 @@ define(function (require, exports, modul
     }
 
     constructor(props) {
       super(props);
 
       this.state = {
         expandedNodes: props.expandedNodes,
         columns: ensureDefaultColumn(props.columns),
-        selected: null
+        selected: props.selected,
+        lastSelectedIndex: 0
       };
 
       this.toggle = this.toggle.bind(this);
       this.isExpanded = this.isExpanded.bind(this);
       this.onKeyDown = this.onKeyDown.bind(this);
       this.onClickRow = this.onClickRow.bind(this);
       this.getSelectedRow = this.getSelectedRow.bind(this);
       this.selectRow = this.selectRow.bind(this);
       this.isSelected = this.isSelected.bind(this);
       this.onFilter = this.onFilter.bind(this);
       this.onSort = this.onSort.bind(this);
       this.getMembers = this.getMembers.bind(this);
       this.renderRows = this.renderRows.bind(this);
     }
 
     componentWillReceiveProps(nextProps) {
-      let { expandedNodes } = nextProps;
-      this.setState(Object.assign({}, this.state, {
+      let { expandedNodes, selected } = nextProps;
+      let state = {
         expandedNodes,
-      }));
+        lastSelectedIndex: this.getSelectedRowIndex()
+      };
+
+      if (selected) {
+        state.selected = selected;
+      }
+
+      this.setState(Object.assign({}, this.state, state));
     }
 
     componentDidUpdate() {
-      let selected = this.getSelectedRow(this.rows);
+      let selected = this.getSelectedRow();
       if (!selected && this.rows.length > 0) {
-        // TODO: Do better than just selecting the first row again. We want to
-        // select (in order) previous, next or parent in case when selected
-        // row is removed.
-        this.selectRow(this.rows[0]);
+        this.selectRow(this.rows[
+          Math.min(this.state.lastSelectedIndex, this.rows.length - 1)]);
       }
     }
 
     static subPath(path, subKey) {
       return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
     }
 
     /**
@@ -224,21 +244,21 @@ define(function (require, exports, modul
 
     isExpanded(nodePath) {
       return this.state.expandedNodes.has(nodePath);
     }
 
     // Event Handlers
 
     onKeyDown(event) {
-      if (!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) {
+      if (!SUPPORTED_KEYS.includes(event.key)) {
         return;
       }
 
-      let row = this.getSelectedRow(this.rows);
+      let row = this.getSelectedRow();
       if (!row) {
         return;
       }
 
       let index = this.rows.indexOf(row);
       switch (event.key) {
         case "ArrowRight":
           let { hasChildren, open } = row.props.member;
@@ -258,39 +278,74 @@ define(function (require, exports, modul
           }
           break;
         case "ArrowUp":
           let previousRow = this.rows[index - 1];
           if (previousRow) {
             this.selectRow(previousRow);
           }
           break;
+        case "Home":
+          let firstRow = this.rows[0];
+          if (firstRow) {
+            this.selectRow(firstRow);
+          }
+          break;
+        case "End":
+          let lastRow = this.rows[this.rows.length - 1];
+          if (lastRow) {
+            this.selectRow(lastRow);
+          }
+          break;
       }
 
+      // Focus should always remain on the tree container itself.
+      this.tree.focus();
       event.preventDefault();
     }
 
     onClickRow(nodePath, event) {
+      let onClickRow = this.props.onClickRow;
+
+      if (onClickRow) {
+        onClickRow.call(this, nodePath, event);
+        return;
+      }
+
       event.stopPropagation();
       let cell = event.target.closest("td");
       if (cell && cell.classList.contains("treeLabelCell")) {
         this.toggle(nodePath);
       }
       this.selectRow(event.currentTarget);
     }
 
-    getSelectedRow(rows) {
-      if (!this.state.selected || rows.length === 0) {
+    getSelectedRow() {
+      if (!this.state.selected || this.rows.length === 0) {
         return null;
       }
-      return rows.find(row => this.isSelected(row.props.member.path));
+      return this.rows.find(row => this.isSelected(row.props.member.path));
+    }
+
+    getSelectedRowIndex() {
+      let row = this.getSelectedRow();
+      if (!row) {
+        // If selected row is not found, return index of the first row.
+        return 0;
+      }
+
+      return this.rows.indexOf(row);
     }
 
     selectRow(row) {
       row = findDOMNode(row);
+      if (this.state.selected === row.id) {
+        return;
+      }
+
       this.setState(Object.assign({}, this.state, {
         selected: row.id
       }));
       row.scrollIntoView({block: "nearest"});
     }
 
     isSelected(nodePath) {
       return nodePath === this.state.selected;
@@ -460,25 +515,29 @@ define(function (require, exports, modul
       let props = Object.assign({}, this.props, {
         columns: this.state.columns
       });
 
       return (
         dom.table({
           className: classNames.join(" "),
           role: "tree",
+          ref: tree => {
+            this.tree = tree;
+          },
           tabIndex: 0,
           onKeyDown: this.onKeyDown,
           "aria-label": this.props.label || "",
           "aria-activedescendant": this.state.selected,
           cellPadding: 0,
           cellSpacing: 0},
           TreeHeader(props),
           dom.tbody({
-            role: "presentation"
+            role: "presentation",
+            tabIndex: -1
           }, rows)
         )
       );
     }
   }
 
   // Helpers
 
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -636,8 +636,15 @@
 
 :-moz-native-anonymous .shapes-markers-outline {
   fill: var(--highlighter-guide-color);
 }
 
 :-moz-native-anonymous .shapes-marker-hover {
   fill: var(--highlighter-guide-color);
 }
+
+/* Accessible highlighter */
+
+:-moz-native-anonymous .accessible-bounds {
+  opacity: 0.6;
+  fill: #6a5acd;
+}
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -433,52 +433,54 @@ exports.HighlighterActor = protocol.Acto
  * type name and allows to show/hide it.
  */
 exports.CustomHighlighterActor = protocol.ActorClassWithSpec(customHighlighterSpec, {
   /**
    * Create a highlighter instance given its typename
    * The typename must be one of HIGHLIGHTER_CLASSES and the class must
    * implement constructor(tabActor), show(node), hide(), destroy()
    */
-  initialize: function (inspector, typeName) {
+  initialize: function (parent, typeName) {
     protocol.Actor.prototype.initialize.call(this, null);
 
-    this._inspector = inspector;
+    this._parent = parent;
 
     let modulePath = highlighterTypes.get(typeName);
     if (!modulePath) {
       let list = [...highlighterTypes.keys()];
 
       throw new Error(`${typeName} isn't a valid highlighter class (${list})`);
     }
 
-    // The assumption is that all custom highlighters need the canvasframe
-    // container to append their elements, so if this is a XUL window, bail out.
-    if (!isXUL(this._inspector.tabActor.window)) {
+    let constructor = require("./highlighters/" + modulePath)[typeName];
+    // The assumption is that custom highlighters either need the canvasframe
+    // container to append their elements and thus a non-XUL window or they have
+    // to define a static XULSupported flag that indicates that the highlighter
+    // supports XUL windows. Otherwise, bail out.
+    if (!isXUL(this._parent.tabActor.window) || constructor.XULSupported) {
       this._highlighterEnv = new HighlighterEnvironment();
-      this._highlighterEnv.initFromTabActor(inspector.tabActor);
-      let constructor = require("./highlighters/" + modulePath)[typeName];
+      this._highlighterEnv.initFromTabActor(parent.tabActor);
       this._highlighter = new constructor(this._highlighterEnv);
       if (this._highlighter.on) {
         this._highlighter.on("highlighter-event", this._onHighlighterEvent.bind(this));
       }
     } else {
       throw new Error("Custom " + typeName +
         "highlighter cannot be created in a XUL window");
     }
   },
 
   get conn() {
-    return this._inspector && this._inspector.conn;
+    return this._parent && this._parent.conn;
   },
 
   destroy: function () {
     protocol.Actor.prototype.destroy.call(this);
     this.finalize();
-    this._inspector = null;
+    this._parent = null;
   },
 
   release: function () {},
 
   /**
    * Show the highlighter.
    * This calls through to the highlighter instance's |show(node, options)|
    * method.
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/highlighters/accessible.js
@@ -0,0 +1,270 @@
+/* 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 { AutoRefreshHighlighter } = require("./auto-refresh");
+const { getBounds } = require("./utils/accessibility");
+
+const {
+  CanvasFrameAnonymousContentHelper,
+  createNode,
+  createSVGNode
+} = require("./utils/markup");
+
+const { setIgnoreLayoutChanges } = require("devtools/shared/layout/utils");
+
+/**
+ * The AccessibleHighlighter draws the bounds of an accessible object.
+ *
+ * Usage example:
+ *
+ * let h = new AccessibleHighlighter(env);
+ * h.show(node, { x, y, w, h, [duration] });
+ * h.hide();
+ * h.destroy();
+ *
+ * Available options:
+ *         - {Number} x
+ *           x coordinate of the top left corner of the accessible object
+ *         - {Number} y
+ *           y coordinate of the top left corner of the accessible object
+ *         - {Number} w
+ *           width of the the accessible object
+ *         - {Number} h
+ *           height of the the accessible object
+ *         - {Number} duration
+ *           Duration of time that the highlighter should be shown.
+ *
+ * Structure:
+ * <div class="highlighter-container">
+ *   <div class="accessible-root">
+ *     <svg class="accessible-elements" hidden="true">
+ *       <path class="accessible-bounds" points="..." />
+ *     </svg>
+ *   </div>
+ * </div>
+ */
+class AccessibleHighlighter extends AutoRefreshHighlighter {
+  constructor(highlighterEnv) {
+    super(highlighterEnv);
+
+    this.ID_CLASS_PREFIX = "accessible-";
+
+    this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
+      this._buildMarkup.bind(this));
+
+    this.onPageHide = this.onPageHide.bind(this);
+    this.onWillNavigate = this.onWillNavigate.bind(this);
+
+    this.highlighterEnv.on("will-navigate", this.onWillNavigate);
+
+    this.pageListenerTarget = highlighterEnv.pageListenerTarget;
+    this.pageListenerTarget.addEventListener("pagehide", this.onPageHide);
+  }
+
+  /**
+   * Build highlighter markup.
+   *
+   * @return {Object} Container element for the highlighter markup.
+   */
+  _buildMarkup() {
+    let container = createNode(this.win, {
+      attributes: {
+        "class": "highlighter-container",
+        "role": "presentation"
+      }
+    });
+
+    let root = createNode(this.win, {
+      parent: container,
+      attributes: {
+        "id": "root",
+        "class": "root",
+        "role": "presentation"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    // Build the SVG element.
+    let svg = createSVGNode(this.win, {
+      nodeType: "svg",
+      parent: root,
+      attributes: {
+        "id": "elements",
+        "width": "100%",
+        "height": "100%",
+        "hidden": "true",
+        "role": "presentation"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    createSVGNode(this.win, {
+      nodeType: "path",
+      parent: svg,
+      attributes: {
+        "class": "bounds",
+        "id": "bounds",
+        "role": "presentation"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    return container;
+  }
+
+  /**
+   * Destroy the nodes. Remove listeners.
+   */
+  destroy() {
+    if (this._highlightTimer) {
+      clearTimeout(this._highlightTimer);
+      this._highlightTimer = null;
+    }
+
+    this.highlighterEnv.off("will-navigate", this.onWillNavigate);
+    this.pageListenerTarget.removeEventListener("pagehide", this.onPageHide);
+    this.pageListenerTarget = null;
+
+    this.markup.destroy();
+    AutoRefreshHighlighter.prototype.destroy.call(this);
+  }
+
+  /**
+   * Find an element in highlighter markup.
+   *
+   * @param  {String} id
+   *         Highlighter markup elemet id attribute.
+   * @return {DOMNode} Element in the highlighter markup.
+   */
+  getElement(id) {
+    return this.markup.getElement(this.ID_CLASS_PREFIX + id);
+  }
+
+  /**
+   * Show the highlighter on a given accessible.
+   *
+   * @return {Boolean} True if accessible is highlighted, false otherwise.
+   */
+  _show() {
+    if (this._highlightTimer) {
+      clearTimeout(this._highlightTimer);
+      this._highlightTimer = null;
+    }
+
+    let { duration } = this.options;
+    let shown = this._update();
+    if (shown && duration) {
+      this._highlightTimer = setTimeout(() => {
+        this.hide();
+      }, duration);
+    }
+    return shown;
+  }
+
+  /**
+   * Update and show accessible bounds for a current accessible.
+   *
+   * @return {Boolean} True if accessible is highlighted, false otherwise.
+   */
+  _update() {
+    let shown = false;
+    setIgnoreLayoutChanges(true);
+
+    if (this._updateAccessibleBounds()) {
+      this._showAccessibleBounds();
+      shown = true;
+    } else {
+      // Nothing to highlight (0px rectangle like a <script> tag for instance)
+      this.hide();
+    }
+
+    setIgnoreLayoutChanges(false,
+                           this.highlighterEnv.window.document.documentElement);
+
+    return shown;
+  }
+
+  /**
+   * Hide the highlighter.
+   */
+  _hide() {
+    setIgnoreLayoutChanges(true);
+    this._hideAccessibleBounds();
+    setIgnoreLayoutChanges(false,
+                           this.highlighterEnv.window.document.documentElement);
+  }
+
+  /**
+   * Hide the accessible bounds container.
+   */
+  _hideAccessibleBounds() {
+    this.getElement("elements").setAttribute("hidden", "true");
+  }
+
+  /**
+   * Showthe accessible bounds container.
+   */
+  _showAccessibleBounds() {
+    this.getElement("elements").removeAttribute("hidden");
+  }
+
+  /**
+   * Get current accessible bounds.
+   *
+   * @return {Object|null} Returns, if available, positioning and bounds
+   *                       information for the accessible object.
+   */
+  get _bounds() {
+    return getBounds(this.win, this.options);
+  }
+
+  /**
+   * Update accessible bounds for a current accessible. Re-draw highlighter
+   * markup.
+   *
+   * @return {Boolean} True if accessible is highlighted, false otherwise.
+   */
+  _updateAccessibleBounds() {
+    let bounds = this._bounds;
+    if (!bounds) {
+      this._hideAccessibleBounds();
+      return false;
+    }
+
+    let boundsEl = this.getElement("bounds");
+    let { left, right, top, bottom } = bounds;
+    let path = `M${left},${top} L${right},${top} L${right},${bottom} L${left},${bottom}`;
+    boundsEl.setAttribute("d", path);
+
+    // Un-zoom the root wrapper if the page was zoomed.
+    let rootId = this.ID_CLASS_PREFIX + "elements";
+    this.markup.scaleRootElement(this.currentNode, rootId);
+
+    return true;
+  }
+
+  /**
+   * Hide highlighter on page hide.
+   */
+  onPageHide({ target }) {
+    // If a pagehide event is triggered for current window's highlighter, hide
+    // the highlighter.
+    if (target.defaultView === this.win) {
+      this.hide();
+    }
+  }
+
+  /**
+   * Hide highlighter on navigation.
+   */
+  onWillNavigate({ isTopLevel }) {
+    if (isTopLevel) {
+      this.hide();
+    }
+  }
+}
+
+exports.AccessibleHighlighter = AccessibleHighlighter;
--- a/devtools/server/actors/highlighters/moz.build
+++ b/devtools/server/actors/highlighters/moz.build
@@ -4,22 +4,24 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'utils',
 ]
 
 DevToolsModules(
+    'accessible.js',
     'auto-refresh.js',
     'box-model.js',
     'css-grid.js',
     'css-transform.js',
     'eye-dropper.js',
     'flexbox.js',
     'geometry-editor.js',
     'measuring-tool.js',
     'paused-debugger.js',
     'rulers.js',
     'selector.js',
     'shapes.js',
-    'simple-outline.js'
+    'simple-outline.js',
+    'xul-accessible.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/highlighters/utils/accessibility.js
@@ -0,0 +1,49 @@
+/* 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 { getCurrentZoom } = require("devtools/shared/layout/utils");
+
+/**
+ * A helper function that calculate accessible object bounds and positioning to
+ * be used for highlighting.
+ *
+ * @param  {Object} win
+ *         window that contains accessible object.
+ * @param  {Object} options
+ *         Object used for passing options:
+ *         - {Number} x
+ *           x coordinate of the top left corner of the accessible object
+ *         - {Number} y
+ *           y coordinate of the top left corner of the accessible object
+ *         - {Number} w
+ *           width of the the accessible object
+ *         - {Number} h
+ *           height of the the accessible object
+ * @return {Object|null} Returns, if available, positioning and bounds information for
+ *                 the accessible object.
+ */
+function getBounds(win, { x, y, w, h }) {
+  let { devicePixelRatio, mozInnerScreenX, mozInnerScreenY, scrollX, scrollY } = win;
+  let zoom = getCurrentZoom(win);
+  let left = x, right = x + w, top = y, bottom = y + h;
+
+  left -= (mozInnerScreenX - scrollX) * devicePixelRatio;
+  right -= (mozInnerScreenX - scrollX) * devicePixelRatio;
+  top -= (mozInnerScreenY - scrollY) * devicePixelRatio;
+  bottom -= (mozInnerScreenY - scrollY) * devicePixelRatio;
+
+  left *= zoom / devicePixelRatio;
+  right *= zoom / devicePixelRatio;
+  top *= zoom / devicePixelRatio;
+  bottom *= zoom / devicePixelRatio;
+
+  let width = right - left;
+  let height = bottom - top;
+
+  return { left, right, top, bottom, width, height };
+}
+
+exports.getBounds = getBounds;
--- a/devtools/server/actors/highlighters/utils/moz.build
+++ b/devtools/server/actors/highlighters/utils/moz.build
@@ -1,10 +1,11 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
+    'accessibility.js',
     'canvas.js',
     'markup.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/highlighters/xul-accessible.js
@@ -0,0 +1,221 @@
+/* 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 { getBounds } = require("./utils/accessibility");
+const { createNode, isNodeValid } = require("./utils/markup");
+const { loadSheet } = require("devtools/shared/layout/utils");
+
+/**
+ * Stylesheet used for highlighter styling of accessible objects in chrome. It
+ * is consistent with the styling of an in-content accessible highlighter.
+ */
+const ACCESSIBLE_BOUNDS_SHEET = "data:text/css;charset=utf-8," + encodeURIComponent(`
+  .accessible-bounds {
+    position: fixed;
+    pointer-events: none;
+    z-index: 10;
+    display: block;
+    background-color: #6a5acd!important;
+    opacity: 0.6;
+  }`);
+
+/**
+ * The XULWindowAccessibleHighlighter is a class that has the same API as the
+ * AccessibleHighlighter, and by extension other highlighters that implement
+ * auto-refresh highlighter, but instead of drawing in canvas frame anonymous
+ * content (that is not available for chrome accessible highlighting) it adds a
+ * transparrent inactionable element with the same position and bounds as the
+ * accessible object highlighted. Unlike SimpleOutlineHighlighter, we can't use
+ * element (that corresponds to accessible object) itself because the accessible
+ * position and bounds are calculated differently.
+ *
+ * It is used when canvasframe-based AccessibleHighlighter can't be used. This
+ * is the case for XUL windows.
+ */
+class XULWindowAccessibleHighlighter {
+  constructor(highlighterEnv) {
+    this.highlighterEnv = highlighterEnv;
+    this.win = highlighterEnv.window;
+  }
+
+  /**
+   * Static getter that indicates that XULWindowAccessibleHighlighter supports
+   * highlighting in XUL windows.
+   */
+  static get XULSupported() {
+    return true;
+  }
+
+  /**
+   * Build highlighter markup.
+   */
+  _buildMarkup() {
+    let doc = this.win.document;
+    loadSheet(doc.ownerGlobal, ACCESSIBLE_BOUNDS_SHEET);
+
+    this.container = createNode(this.win, {
+      parent: doc.body || doc.documentElement,
+      attributes: {
+        "class": "highlighter-container",
+        "role": "presentation"
+      }
+    });
+
+    this.bounds = createNode(this.win, {
+      parent: this.container,
+      attributes: {
+        "class": "accessible-bounds",
+        "role": "presentation"
+      }
+    });
+  }
+
+  /**
+   * Get current accessible bounds.
+   *
+   * @return {Object|null} Returns, if available, positioning and bounds
+   *                       information for the accessible object.
+   */
+  get _bounds() {
+    return getBounds(this.win, this.options);
+  }
+
+  /**
+   * Show the highlighter on a given accessible.
+   *
+   * @param {DOMNode} node
+   *        A dom node that corresponds to the accessible object.
+   * @param {Object} options
+   *        Object used for passing options. Available options:
+   *         - {Number} x
+   *           x coordinate of the top left corner of the accessible object
+   *         - {Number} y
+   *           y coordinate of the top left corner of the accessible object
+   *         - {Number} w
+   *           width of the the accessible object
+   *         - {Number} h
+   *           height of the the accessible object
+   *         - duration {Number}
+   *                    Duration of time that the highlighter should be shown.
+   * @return {Boolean} True if accessible is highlighted, false otherwise.
+   */
+  show(node, options = {}) {
+    let isSameNode = node === this.currentNode;
+    let hasBounds = options && typeof options.x == "number" &&
+                               typeof options.y == "number" &&
+                               typeof options.w == "number" &&
+                               typeof options.h == "number";
+    if (!hasBounds || !isNodeValid(node) || isSameNode) {
+      return false;
+    }
+
+    this.options = options;
+    this.currentNode = node;
+
+    return this._show();
+  }
+
+  /**
+   * Internal show method that updates bounds and tracks duration based
+   * highlighting.
+   *
+   * @return {Boolean} True if accessible is highlighted, false otherwise.
+   */
+  _show() {
+    if (this._highlightTimer) {
+      clearTimeout(this._highlightTimer);
+      this._highlightTimer = null;
+    }
+
+    let shown = this._update();
+    let { duration } = this.options;
+    if (shown && duration) {
+      this._highlightTimer = setTimeout(() => {
+        this._hideAccessibleBounds();
+      }, duration);
+    }
+    return shown;
+  }
+
+  /**
+   * Update accessible bounds for a current accessible. Re-draw highlighter
+   * markup.
+   *
+   * @return {Boolean} True if accessible is highlighted, false otherwise.
+   */
+  _update() {
+    this._hideAccessibleBounds();
+    let bounds = this._bounds;
+    if (!bounds) {
+      return false;
+    }
+
+    let boundsEl = this.bounds;
+    if (!boundsEl) {
+      this._buildMarkup();
+      boundsEl = this.bounds;
+    }
+
+    let { left, top, width, height } = bounds;
+    boundsEl.style.top = `${top}px`;
+    boundsEl.style.left = `${left}px`;
+    boundsEl.style.width = `${width}px`;
+    boundsEl.style.height = `${height}px`;
+    this._showAccessibleBounds();
+
+    return true;
+  }
+
+  /**
+   * Hide the highlighter
+   */
+  hide() {
+    if (!this.currentNode || !this.highlighterEnv.window) {
+      return;
+    }
+
+    this._hideAccessibleBounds();
+    this.currentNode = null;
+    this.options = null;
+  }
+
+  /**
+   * Show accessible bounds highlighter.
+   */
+  _showAccessibleBounds() {
+    if (this.container) {
+      this.container.removeAttribute("hidden");
+    }
+  }
+
+  /**
+   * Hide accessible bounds highlighter.
+   */
+  _hideAccessibleBounds() {
+    if (this.container) {
+      this.container.setAttribute("hidden", "true");
+    }
+  }
+
+  /**
+   * Hide accessible highlighter, clean up and remove the markup.
+   */
+  destroy() {
+    if (this._highlightTimer) {
+      clearTimeout(this._highlightTimer);
+      this._highlightTimer = null;
+    }
+
+    this.hide();
+    if (this.container) {
+      this.container.remove();
+    }
+
+    this.win = null;
+  }
+}
+
+exports.XULWindowAccessibleHighlighter = XULWindowAccessibleHighlighter;
--- a/dom/base/DocumentOrShadowRoot.cpp
+++ b/dom/base/DocumentOrShadowRoot.cpp
@@ -141,10 +141,30 @@ DocumentOrShadowRoot::GetPointerLockElem
   }
 
   nsIContent* retargetedPointerLockedElement = Retarget(pointerLockedElement);
   return
     retargetedPointerLockedElement && retargetedPointerLockedElement->IsElement() ?
       retargetedPointerLockedElement->AsElement() : nullptr;
 }
 
+Element*
+DocumentOrShadowRoot::GetFullscreenElement()
+{
+  if (!AsNode().IsInComposedDoc()) {
+    return nullptr;
+  }
+
+  Element* element = AsNode().OwnerDoc()->FullScreenStackTop();
+  NS_ASSERTION(!element ||
+               element->State().HasState(NS_EVENT_STATE_FULL_SCREEN),
+    "Fullscreen element should have fullscreen styles applied");
+
+  nsIContent* retargeted = Retarget(element);
+  if (retargeted && retargeted->IsElement()) {
+    return retargeted->AsElement();
+  }
+
+  return nullptr;
+}
+
 }
 }
--- a/dom/base/DocumentOrShadowRoot.h
+++ b/dom/base/DocumentOrShadowRoot.h
@@ -109,16 +109,17 @@ public:
                          mozilla::ErrorResult&);
 
   already_AddRefed<nsContentList>
   GetElementsByClassName(const nsAString& aClasses);
 
   ~DocumentOrShadowRoot() = default;
 
   Element* GetPointerLockElement();
+  Element* GetFullscreenElement();
 protected:
   nsIContent* Retarget(nsIContent* aContent) const;
 
   /**
    * If focused element's subtree root is this document or shadow root, return
    * focused element, otherwise, get the shadow host recursively until the
    * shadow host's subtree root is this document or shadow root.
    */
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -8812,17 +8812,17 @@ nsDocument::OnPageHide(bool aPersisted,
   mVisible = false;
 
   UpdateVisibilityState();
 
   EnumerateExternalResources(NotifyPageHide, &aPersisted);
   EnumerateActivityObservers(NotifyActivityChanged, nullptr);
 
   ClearPendingFullscreenRequests(this);
-  if (GetFullscreenElement()) {
+  if (FullScreenStackTop()) {
     // If this document was fullscreen, we should exit fullscreen in this
     // doctree branch. This ensures that if the user navigates while in
     // fullscreen mode we don't leave its still visible ancestor documents
     // in fullscreen mode. So exit fullscreen in the document's fullscreen
     // root document, as this will exit fullscreen in all the root's
     // descendant documents. Note that documents are removed from the
     // doctree by the time OnPageHide() is called, so we must store a
     // reference to the root (in nsDocument::mFullscreenRoot) since we can't
@@ -10614,17 +10614,17 @@ nsIDocument::AsyncExitFullscreen(nsIDocu
   } else {
     NS_DispatchToCurrentThread(exit.forget());
   }
 }
 
 static bool
 CountFullscreenSubDocuments(nsIDocument* aDoc, void* aData)
 {
-  if (aDoc->GetFullscreenElement()) {
+  if (aDoc->FullScreenStackTop()) {
     uint32_t* count = static_cast<uint32_t*>(aData);
     (*count)++;
   }
   return true;
 }
 
 static uint32_t
 CountFullscreenSubDocuments(nsIDocument* aDoc)
@@ -10634,30 +10634,30 @@ CountFullscreenSubDocuments(nsIDocument*
   return count;
 }
 
 bool
 nsDocument::IsFullscreenLeaf()
 {
   // A fullscreen leaf document is fullscreen, and has no fullscreen
   // subdocuments.
-  if (!GetFullscreenElement()) {
+  if (!FullScreenStackTop()) {
     return false;
   }
   return CountFullscreenSubDocuments(this) == 0;
 }
 
 static bool
 ResetFullScreen(nsIDocument* aDocument, void* aData)
 {
-  if (aDocument->GetFullscreenElement()) {
+  if (aDocument->FullScreenStackTop()) {
     NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1,
         "Should have at most 1 fullscreen subdocument.");
     static_cast<nsDocument*>(aDocument)->CleanupFullscreenState();
-    NS_ASSERTION(!aDocument->GetFullscreenElement(),
+    NS_ASSERTION(!aDocument->FullScreenStackTop(),
                  "Should reset full-screen");
     auto changed = reinterpret_cast<nsCOMArray<nsIDocument>*>(aData);
     changed->AppendElement(aDocument);
     aDocument->EnumerateSubDocuments(ResetFullScreen, aData);
   }
   return true;
 }
 
@@ -10698,17 +10698,17 @@ private:
 nsIDocument::ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc)
 {
   MOZ_ASSERT(aMaybeNotARootDoc);
 
   // Unlock the pointer
   UnlockPointer();
 
   nsCOMPtr<nsIDocument> root = aMaybeNotARootDoc->GetFullscreenRoot();
-  if (!root || !root->GetFullscreenElement()) {
+  if (!root || !root->FullScreenStackTop()) {
     // If a document was detached before exiting from fullscreen, it is
     // possible that the root had left fullscreen state. In this case,
     // we would not get anything from the ResetFullScreen() call. Root's
     // not being a fullscreen doc also means the widget should have
     // exited fullscreen state. It means even if we do not return here,
     // we would actually do nothing below except crashing ourselves via
     // dispatching the "MozDOMFullscreen:Exited" event to an nonexistent
     // document.
@@ -10727,34 +10727,34 @@ nsIDocument::ExitFullscreenInDocTree(nsI
 
   // Dispatch "fullscreenchange" events. Note this loop is in reverse
   // order so that the events for the leaf document arrives before the root
   // document, as required by the spec.
   for (uint32_t i = 0; i < changed.Length(); ++i) {
     DispatchFullScreenChange(changed[changed.Length() - i - 1]);
   }
 
-  NS_ASSERTION(!root->GetFullscreenElement(),
+  NS_ASSERTION(!root->FullScreenStackTop(),
     "Fullscreen root should no longer be a fullscreen doc...");
 
   // Move the top-level window out of fullscreen mode.
   FullscreenRoots::Remove(root);
 
   nsContentUtils::AddScriptRunner(
     new ExitFullscreenScriptRunnable(Move(changed)));
 }
 
 bool
 GetFullscreenLeaf(nsIDocument* aDoc, void* aData)
 {
   if (aDoc->IsFullscreenLeaf()) {
     nsIDocument** result = static_cast<nsIDocument**>(aData);
     *result = aDoc;
     return false;
-  } else if (aDoc->GetFullscreenElement()) {
+  } else if (aDoc->FullScreenStackTop()) {
     aDoc->EnumerateSubDocuments(GetFullscreenLeaf, aData);
   }
   return true;
 }
 
 static nsIDocument*
 GetFullscreenLeaf(nsIDocument* aDoc)
 {
@@ -10763,30 +10763,30 @@ GetFullscreenLeaf(nsIDocument* aDoc)
   if (leaf) {
     return leaf;
   }
   // Otherwise we could be either in a non-fullscreen doc tree, or we're
   // below the fullscreen doc. Start the search from the root.
   nsIDocument* root = nsContentUtils::GetRootDocument(aDoc);
   // Check that the root is actually fullscreen so we don't waste time walking
   // around its descendants.
-  if (!root->GetFullscreenElement()) {
+  if (!root->FullScreenStackTop()) {
     return nullptr;
   }
   GetFullscreenLeaf(root, &leaf);
   return leaf;
 }
 
 void
 nsDocument::RestorePreviousFullScreenState()
 {
-  NS_ASSERTION(!GetFullscreenElement() || !FullscreenRoots::IsEmpty(),
+  NS_ASSERTION(!FullScreenStackTop() || !FullscreenRoots::IsEmpty(),
     "Should have at least 1 fullscreen root when fullscreen!");
 
-  if (!GetFullscreenElement() || !GetWindow() || FullscreenRoots::IsEmpty()) {
+  if (!FullScreenStackTop() || !GetWindow() || FullscreenRoots::IsEmpty()) {
     return;
   }
 
   nsCOMPtr<nsIDocument> fullScreenDoc = GetFullscreenLeaf(this);
   AutoTArray<nsDocument*, 8> exitDocs;
 
   nsIDocument* doc = fullScreenDoc;
   // Collect all subdocuments.
@@ -10955,17 +10955,17 @@ nsDocument::FullScreenStackPush(Element*
 {
   NS_ASSERTION(aElement, "Must pass non-null to FullScreenStackPush()");
   Element* top = FullScreenStackTop();
   if (top == aElement || !aElement) {
     return false;
   }
   EventStateManager::SetFullScreenState(aElement, true);
   mFullScreenStack.AppendElement(do_GetWeakReference(aElement));
-  NS_ASSERTION(GetFullscreenElement() == aElement, "Should match");
+  NS_ASSERTION(FullScreenStackTop() == aElement, "Should match");
   UpdateViewportScrollbarOverrideForFullscreen(this);
   return true;
 }
 
 void
 nsDocument::FullScreenStackPop()
 {
   if (mFullScreenStack.IsEmpty()) {
@@ -11003,17 +11003,17 @@ Element*
 nsDocument::FullScreenStackTop()
 {
   if (mFullScreenStack.IsEmpty()) {
     return nullptr;
   }
   uint32_t last = mFullScreenStack.Length() - 1;
   nsCOMPtr<Element> element(do_QueryReferent(mFullScreenStack[last]));
   NS_ASSERTION(element, "Should have full-screen element!");
-  NS_ASSERTION(element->IsInUncomposedDoc(), "Full-screen element should be in doc");
+  NS_ASSERTION(element->IsInComposedDoc(), "Full-screen element should be in doc");
   NS_ASSERTION(element->OwnerDoc() == this, "Full-screen element should be in this doc");
   return element;
 }
 
 /* virtual */ nsTArray<Element*>
 nsDocument::GetFullscreenStack() const
 {
   nsTArray<Element*> elements;
@@ -11081,17 +11081,17 @@ nsresult nsDocument::RemoteFrameFullscre
 
 nsresult nsDocument::RemoteFrameFullscreenReverted()
 {
   RestorePreviousFullScreenState();
   return NS_OK;
 }
 
 /* static */ bool
-nsDocument::IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject)
+nsIDocument::IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject)
 {
   MOZ_ASSERT(NS_IsMainThread());
   return nsContentUtils::IsSystemCaller(aCx) ||
          nsContentUtils::IsUnprefixedFullscreenApiEnabled();
 }
 
 static bool
 HasFullScreenSubDocument(nsIDocument* aDoc)
@@ -11128,20 +11128,20 @@ GetFullscreenError(nsIDocument* aDoc, bo
 }
 
 bool
 nsDocument::FullscreenElementReadyCheck(Element* aElement,
                                         bool aWasCallerChrome)
 {
   NS_ASSERTION(aElement,
     "Must pass non-null element to nsDocument::RequestFullScreen");
-  if (!aElement || aElement == GetFullscreenElement()) {
+  if (!aElement || aElement == FullScreenStackTop()) {
     return false;
   }
-  if (!aElement->IsInUncomposedDoc()) {
+  if (!aElement->IsInComposedDoc()) {
     DispatchFullscreenError("FullscreenDeniedNotInDocument");
     return false;
   }
   if (aElement->OwnerDoc() != this) {
     DispatchFullscreenError("FullscreenDeniedMovedDocument");
     return false;
   }
   if (!GetWindow()) {
@@ -11155,18 +11155,21 @@ nsDocument::FullscreenElementReadyCheck(
   if (!IsVisible()) {
     DispatchFullscreenError("FullscreenDeniedHidden");
     return false;
   }
   if (HasFullScreenSubDocument(this)) {
     DispatchFullscreenError("FullscreenDeniedSubDocFullScreen");
     return false;
   }
-  if (GetFullscreenElement() &&
-      !nsContentUtils::ContentIsDescendantOf(aElement, GetFullscreenElement())) {
+  //XXXsmaug Note, we don't follow the latest fullscreen spec here.
+  //         This whole check could be probably removed.
+  if (FullScreenStackTop() &&
+      !nsContentUtils::ContentIsHostIncludingDescendantOf(aElement,
+                                                          FullScreenStackTop())) {
     // If this document is full-screen, only grant full-screen requests from
     // a descendant of the current full-screen element.
     DispatchFullscreenError("FullscreenDeniedNotDescendant");
     return false;
   }
   if (!nsContentUtils::IsChromeDoc(this) && !IsInActiveTab(this)) {
     DispatchFullscreenError("FullscreenDeniedNotFocusedTab");
     return false;
@@ -11507,26 +11510,16 @@ nsDocument::ApplyFullscreen(const Fullsc
   // order so that the events for the root document arrives before the leaf
   // document, as required by the spec.
   for (uint32_t i = 0; i < changed.Length(); ++i) {
     DispatchFullScreenChange(changed[changed.Length() - i - 1]);
   }
   return true;
 }
 
-Element*
-nsDocument::GetFullscreenElement()
-{
-  Element* element = FullScreenStackTop();
-  NS_ASSERTION(!element ||
-               element->State().HasState(NS_EVENT_STATE_FULL_SCREEN),
-    "Fullscreen element should have fullscreen styles applied");
-  return element;
-}
-
 bool
 nsDocument::FullscreenEnabled(CallerType aCallerType)
 {
   return !GetFullscreenError(this, aCallerType == CallerType::System);
 }
 
 uint16_t
 nsDocument::CurrentOrientationAngle() const
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -878,18 +878,16 @@ public:
   // Returns strong references to mBlockedTrackingNodes. (nsIDocument.h)
   //
   // This array contains nodes that have been blocked to prevent
   // user tracking. They most likely have had their nsIChannel
   // canceled by the URL classifier (Safebrowsing).
   //
   already_AddRefed<nsSimpleContentList> BlockedTrackingNodes() const;
 
-  static bool IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject);
-
   // Do the "fullscreen element ready check" from the fullscreen spec.
   // It returns true if the given element is allowed to go into fullscreen.
   bool FullscreenElementReadyCheck(Element* aElement, bool aWasCallerChrome);
 
   // This is called asynchronously by nsIDocument::AsyncRequestFullScreen()
   // to move this document into full-screen mode if allowed.
   void RequestFullScreen(mozilla::UniquePtr<FullscreenRequest>&& aRequest);
 
@@ -903,21 +901,20 @@ public:
   bool FullScreenStackPush(Element* aElement);
 
   // Remove the top element from the full-screen stack. Removes the full-screen
   // styles from the former top element, and applies them to the new top
   // element, if there is one.
   void FullScreenStackPop();
 
   // Returns the top element from the full-screen stack.
-  Element* FullScreenStackTop();
+  Element* FullScreenStackTop() override;
 
   // DOM-exposed fullscreen API
   bool FullscreenEnabled(mozilla::dom::CallerType aCallerType) override;
-  Element* GetFullscreenElement() override;
 
   virtual bool AllowPaymentRequest() const override;
   virtual void SetAllowPaymentRequest(bool aIsAllowPaymentRequest) override;
 
   void RequestPointerLock(Element* aElement,
                           mozilla::dom::CallerType aCallerType) override;
   bool SetPointerLock(Element* aElement, int aCursorStyle);
   static void UnlockPointer(nsIDocument* aDoc = nullptr);
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2921,26 +2921,29 @@ public:
   }
   Element* GetCurrentScript();
   void ReleaseCapture() const;
   virtual void MozSetImageElement(const nsAString& aImageElementId,
                                   Element* aElement) = 0;
   nsIURI* GetDocumentURIObject() const;
   // Not const because all the full-screen goop is not const
   virtual bool FullscreenEnabled(mozilla::dom::CallerType aCallerType) = 0;
-  virtual Element* GetFullscreenElement() = 0;
+  virtual Element* FullScreenStackTop() = 0;
   bool Fullscreen()
   {
     return !!GetFullscreenElement();
   }
   void ExitFullscreen();
   void ExitPointerLock()
   {
     UnlockPointer(this);
   }
+
+  static bool IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject);
+
 #ifdef MOZILLA_INTERNAL_API
   bool Hidden() const
   {
     return mVisibilityState != mozilla::dom::VisibilityState::Visible;
   }
   mozilla::dom::VisibilityState VisibilityState() const
   {
     return mVisibilityState;
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -6,17 +6,16 @@
 
 #include "Fetch.h"
 #include "FetchConsumer.h"
 #include "FetchStream.h"
 
 #include "nsIDocument.h"
 #include "nsIGlobalObject.h"
 #include "nsIStreamLoader.h"
-#include "nsIThreadRetargetableRequest.h"
 
 #include "nsCharSeparatedTokenizer.h"
 #include "nsDOMString.h"
 #include "nsJSUtils.h"
 #include "nsNetUtil.h"
 #include "nsReadableUtils.h"
 #include "nsStreamUtils.h"
 #include "nsStringStream.h"
--- a/dom/fetch/FetchConsumer.cpp
+++ b/dom/fetch/FetchConsumer.cpp
@@ -8,16 +8,17 @@
 #include "FetchConsumer.h"
 
 #include "mozilla/dom/WorkerCommon.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerRunnable.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 #include "nsIInputStreamPump.h"
+#include "nsIThreadRetargetableRequest.h"
 #include "nsProxyRelease.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 template <class Derived>
@@ -579,19 +580,17 @@ FetchBodyConsumer<Derived>::BeginConsume
   // stays alive for the lifetime of the FetchConsumer.
   mConsumeBodyPump = pump;
 
   // It is ok for retargeting to fail and reads to happen on the main thread.
   autoReject.DontFail();
 
   // Try to retarget, otherwise fall back to main thread.
   nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump);
-  nsCOMPtr<nsIThreadRetargetableStreamListener> rl =
-    do_QueryInterface(listener);
-  if (rr && rl) {
+  if (rr) {
     nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
     rv = rr->RetargetDeliveryTo(sts);
     if (NS_FAILED(rv)) {
       NS_WARNING("Retargeting failed");
     }
   }
 }
 
--- a/dom/file/MutableBlobStorage.cpp
+++ b/dom/file/MutableBlobStorage.cpp
@@ -103,17 +103,16 @@ private:
 // the temporary file, if its File Descriptor has not been already closed.
 class WriteRunnable final : public Runnable
 {
 public:
   static WriteRunnable*
   CopyBuffer(MutableBlobStorage* aBlobStorage,
              const void* aData, uint32_t aLength)
   {
-    MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aBlobStorage);
     MOZ_ASSERT(aData);
 
     // We have to take a copy of this buffer.
     void* data = malloc(aLength);
     if (!data) {
       return nullptr;
     }
@@ -157,17 +156,16 @@ public:
 
 private:
   WriteRunnable(MutableBlobStorage* aBlobStorage, void* aData, uint32_t aLength)
     : Runnable("dom::WriteRunnable")
     , mBlobStorage(aBlobStorage)
     , mData(aData)
     , mLength(aLength)
   {
-    MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mBlobStorage);
     MOZ_ASSERT(aData);
   }
 
   ~WriteRunnable()
   {
     free(mData);
   }
@@ -330,17 +328,18 @@ private:
   RefPtr<MutableBlobStorageCallback> mCallback;
 };
 
 } // anonymous namespace
 
 MutableBlobStorage::MutableBlobStorage(MutableBlobStorageType aType,
                                        nsIEventTarget* aEventTarget,
                                        uint32_t aMaxMemory)
-  : mData(nullptr)
+  : mMutex("MutableBlobStorage::mMutex")
+  , mData(nullptr)
   , mDataLen(0)
   , mDataBufferLen(0)
   , mStorageState(aType == eOnlyInMemory ? eKeepInMemory : eInMemory)
   , mFD(nullptr)
   , mErrorResult(NS_OK)
   , mEventTarget(aEventTarget)
   , mMaxMemory(aMaxMemory)
 {
@@ -380,16 +379,18 @@ MutableBlobStorage::~MutableBlobStorage(
 void
 MutableBlobStorage::GetBlobWhenReady(nsISupports* aParent,
                                      const nsACString& aContentType,
                                      MutableBlobStorageCallback* aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aCallback);
 
+  MutexAutoLock lock(mMutex);
+
   // GetBlob can be called just once.
   MOZ_ASSERT(mStorageState != eClosed);
   StorageState previousState = mStorageState;
   mStorageState = eClosed;
 
   if (previousState == eInTemporaryFile) {
     if (NS_FAILED(mErrorResult)) {
       MOZ_ASSERT(!mActor);
@@ -444,28 +445,30 @@ MutableBlobStorage::GetBlobWhenReady(nsI
   if (NS_WARN_IF(NS_FAILED(error))) {
     return;
   }
 }
 
 nsresult
 MutableBlobStorage::Append(const void* aData, uint32_t aLength)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  // This method can be called on any thread.
+
+  MutexAutoLock lock(mMutex);
   MOZ_ASSERT(mStorageState != eClosed);
   NS_ENSURE_ARG_POINTER(aData);
 
   if (!aLength) {
     return NS_OK;
   }
 
   // If eInMemory is the current Storage state, we could maybe migrate to
   // a temporary file.
-  if (mStorageState == eInMemory && ShouldBeTemporaryStorage(aLength) &&
-      !MaybeCreateTemporaryFile()) {
+  if (mStorageState == eInMemory && ShouldBeTemporaryStorage(lock, aLength) &&
+      !MaybeCreateTemporaryFile(lock)) {
     return NS_ERROR_FAILURE;
   }
 
   // If we are already in the temporaryFile mode, we have to dispatch a
   // runnable.
   if (mStorageState == eInTemporaryFile) {
     // If a previous operation failed, let's return that error now.
     if (NS_FAILED(mErrorResult)) {
@@ -486,28 +489,28 @@ MutableBlobStorage::Append(const void* a
     mDataLen += aLength;
     return NS_OK;
   }
 
   // By default, we store in memory.
 
   uint64_t offset = mDataLen;
 
-  if (!ExpandBufferSize(aLength)) {
+  if (!ExpandBufferSize(lock, aLength)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   memcpy((char*)mData + offset, aData, aLength);
   return NS_OK;
 }
 
 bool
-MutableBlobStorage::ExpandBufferSize(uint64_t aSize)
+MutableBlobStorage::ExpandBufferSize(const MutexAutoLock& aProofOfLock,
+                                     uint64_t aSize)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mStorageState < eInTemporaryFile);
 
   if (mDataBufferLen >= mDataLen + aSize) {
     mDataLen += aSize;
     return true;
   }
 
   // Start at 1 or we'll loop forever.
@@ -528,60 +531,78 @@ MutableBlobStorage::ExpandBufferSize(uin
 
   mData = data;
   mDataBufferLen = bufferLen.value();
   mDataLen += aSize;
   return true;
 }
 
 bool
-MutableBlobStorage::ShouldBeTemporaryStorage(uint64_t aSize) const
+MutableBlobStorage::ShouldBeTemporaryStorage(const MutexAutoLock& aProofOfLock,
+                                             uint64_t aSize) const
 {
   MOZ_ASSERT(mStorageState == eInMemory);
 
   CheckedUint32 bufferSize = mDataLen;
   bufferSize += aSize;
 
   if (!bufferSize.isValid()) {
     return false;
   }
 
   return bufferSize.value() >= mMaxMemory;
 }
 
 bool
-MutableBlobStorage::MaybeCreateTemporaryFile()
+MutableBlobStorage::MaybeCreateTemporaryFile(const MutexAutoLock& aProofOfLock)
+{
+  mStorageState = eWaitingForTemporaryFile;
+
+  if (!NS_IsMainThread()) {
+    RefPtr<MutableBlobStorage> self = this;
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+      "MutableBlobStorage::MaybeCreateTemporaryFile",
+      [self]() { self->MaybeCreateTemporaryFileOnMainThread(); });
+    EventTarget()->Dispatch(r.forget(), NS_DISPATCH_SYNC);
+    return !!mActor;
+  }
+
+  MaybeCreateTemporaryFileOnMainThread();
+  return !!mActor;
+}
+
+void
+MutableBlobStorage::MaybeCreateTemporaryFileOnMainThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
-
-  mStorageState = eWaitingForTemporaryFile;
+  MOZ_ASSERT(!mActor);
 
   mozilla::ipc::PBackgroundChild* actorChild =
     mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
   if (NS_WARN_IF(!actorChild)) {
-    return false;
+    return;
   }
 
   mActor = new TemporaryIPCBlobChild(this);
   actorChild->SendPTemporaryIPCBlobConstructor(mActor);
 
   // We need manually to increase the reference for this actor because the
   // IPC allocator method is not triggered. The Release() is called by IPDL
   // when the actor is deleted.
   mActor.get()->AddRef();
 
   // The actor will call us when the FileDescriptor is received.
-
-  return true;
 }
 
 void
 MutableBlobStorage::TemporaryFileCreated(PRFileDesc* aFD)
 {
   MOZ_ASSERT(NS_IsMainThread());
+
+  MutexAutoLock lock(mMutex);
   MOZ_ASSERT(mStorageState == eWaitingForTemporaryFile ||
              mStorageState == eClosed);
   MOZ_ASSERT_IF(mPendingCallback, mStorageState == eClosed);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(aFD);
 
   // If the object has been already closed and we don't need to execute a
   // callback, we need just to close the file descriptor in the correct thread.
@@ -638,16 +659,18 @@ MutableBlobStorage::TemporaryFileCreated
   }
 }
 
 void
 MutableBlobStorage::AskForBlob(TemporaryIPCBlobChildCallback* aCallback,
                                const nsACString& aContentType)
 {
   MOZ_ASSERT(NS_IsMainThread());
+
+  MutexAutoLock lock(mMutex);
   MOZ_ASSERT(mStorageState == eClosed);
   MOZ_ASSERT(mFD);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(aCallback);
 
   // Let's pass the FileDescriptor to the parent actor in order to keep the file
   // locked on windows.
   mActor->AskForBlob(aCallback, aContentType, mFD);
@@ -661,16 +684,18 @@ MutableBlobStorage::AskForBlob(Temporary
   mFD = nullptr;
   mActor = nullptr;
 }
 
 void
 MutableBlobStorage::ErrorPropagated(nsresult aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
+
+  MutexAutoLock lock(mMutex);
   mErrorResult = aRv;
 
   if (mActor) {
     mActor->SendOperationFailed();
     mActor = nullptr;
   }
 }
 
@@ -690,33 +715,36 @@ MutableBlobStorage::DispatchToIOThread(a
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 size_t
-MutableBlobStorage::SizeOfCurrentMemoryBuffer() const
+MutableBlobStorage::SizeOfCurrentMemoryBuffer()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MutexAutoLock lock(mMutex);
   return mStorageState < eInTemporaryFile ? mDataLen : 0;
 }
 
 PRFileDesc*
-MutableBlobStorage::GetFD() const
+MutableBlobStorage::GetFD()
 {
   MOZ_ASSERT(!NS_IsMainThread());
+  MutexAutoLock lock(mMutex);
   return mFD;
 }
 
 void
 MutableBlobStorage::CloseFD()
 {
   MOZ_ASSERT(!NS_IsMainThread());
+  MutexAutoLock lock(mMutex);
   MOZ_ASSERT(mFD);
 
   PR_Close(mFD);
   mFD = nullptr;
 }
 
 } // dom namespace
 } // mozilla namespace
--- a/dom/file/MutableBlobStorage.h
+++ b/dom/file/MutableBlobStorage.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_MutableBlobStorage_h
 #define mozilla_dom_MutableBlobStorage_h
 
 #include "mozilla/RefPtr.h"
+#include "mozilla/Mutex.h"
 #include "prio.h"
 
 class nsIEventTarget;
 class nsIRunnable;
 
 namespace mozilla {
 
 class TaskQueue;
@@ -30,17 +31,18 @@ class MutableBlobStorageCallback
 public:
   NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
 
   virtual void BlobStoreCompleted(MutableBlobStorage* aBlobStorage,
                                   Blob* aBlob,
                                   nsresult aRv) = 0;
 };
 
-// This class is main-thread only.
+// This class is must be created and used on main-thread, except for Append()
+// that can be called on any thread.
 class MutableBlobStorage final
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MutableBlobStorage)
 
   enum MutableBlobStorageType
   {
     eOnlyInMemory,
@@ -69,35 +71,41 @@ public:
   nsIEventTarget* EventTarget()
   {
     MOZ_ASSERT(mEventTarget);
     return mEventTarget;
   }
 
   // Returns the heap size in bytes of our internal buffers.
   // Note that this intentionally ignores the data in the temp file.
-  size_t SizeOfCurrentMemoryBuffer() const;
+  size_t SizeOfCurrentMemoryBuffer();
 
-  PRFileDesc* GetFD() const;
+  PRFileDesc* GetFD();
 
   void CloseFD();
 
 private:
   ~MutableBlobStorage();
 
-  bool ExpandBufferSize(uint64_t aSize);
+  bool ExpandBufferSize(const MutexAutoLock& aProofOfLock,
+                        uint64_t aSize);
 
-  bool ShouldBeTemporaryStorage(uint64_t aSize) const;
+  bool ShouldBeTemporaryStorage(const MutexAutoLock& aProofOfLock,
+                                uint64_t aSize) const;
 
-  bool MaybeCreateTemporaryFile();
+  bool MaybeCreateTemporaryFile(const MutexAutoLock& aProofOfLock);
+  void MaybeCreateTemporaryFileOnMainThread();
 
   MOZ_MUST_USE nsresult
   DispatchToIOThread(already_AddRefed<nsIRunnable> aRunnable);
 
-  // All these variables are touched on the main thread only.
+  Mutex mMutex;
+
+  // All these variables are touched on the main thread only or in the
+  // retargeted thread when used by Append(). They are protected by mMutex.
 
   void* mData;
   uint64_t mDataLen;
   uint64_t mDataBufferLen;
 
   enum StorageState {
     eKeepInMemory,
     eInMemory,
--- a/dom/file/MutableBlobStreamListener.cpp
+++ b/dom/file/MutableBlobStreamListener.cpp
@@ -33,16 +33,17 @@ MutableBlobStreamListener::MutableBlobSt
 
 MutableBlobStreamListener::~MutableBlobStreamListener()
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 NS_IMPL_ISUPPORTS(MutableBlobStreamListener,
                   nsIStreamListener,
+                  nsIThreadRetargetableStreamListener,
                   nsIRequestObserver)
 
 NS_IMETHODIMP
 MutableBlobStreamListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mStorage);
   MOZ_ASSERT(mEventTarget);
@@ -74,39 +75,45 @@ MutableBlobStreamListener::OnStopRequest
 
 NS_IMETHODIMP
 MutableBlobStreamListener::OnDataAvailable(nsIRequest* aRequest,
                                            nsISupports* aContext,
                                            nsIInputStream* aStream,
                                            uint64_t aSourceOffset,
                                            uint32_t aCount)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  // This method could be called on any thread.
   MOZ_ASSERT(mStorage);
 
   uint32_t countRead;
   return aStream->ReadSegments(WriteSegmentFun, this, aCount, &countRead);
 }
 
 nsresult
 MutableBlobStreamListener::WriteSegmentFun(nsIInputStream* aWriterStream,
                                            void* aClosure,
                                            const char* aFromSegment,
                                            uint32_t aToOffset,
                                            uint32_t aCount,
                                            uint32_t* aWriteCount)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  // This method could be called on any thread.
 
   MutableBlobStreamListener* self = static_cast<MutableBlobStreamListener*>(aClosure);
   MOZ_ASSERT(self->mStorage);
 
   nsresult rv = self->mStorage->Append(aFromSegment, aCount);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   *aWriteCount = aCount;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+MutableBlobStreamListener::CheckListenerChain()
+{
+  return NS_OK;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/dom/file/MutableBlobStreamListener.h
+++ b/dom/file/MutableBlobStreamListener.h
@@ -3,29 +3,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_MutableBlobStreamListener_h
 #define mozilla_dom_MutableBlobStreamListener_h
 
 #include "nsIStreamListener.h"
+#include "nsIThreadRetargetableStreamListener.h"
 #include "mozilla/dom/MutableBlobStorage.h"
 
 class nsIEventTarget;
 
 namespace mozilla {
 namespace dom {
 
-// This class is main-thread only.
 class MutableBlobStreamListener final : public nsIStreamListener
+                                      , public nsIThreadRetargetableStreamListener
 {
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSISTREAMLISTENER
+  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
   NS_DECL_NSIREQUESTOBSERVER
 
   MutableBlobStreamListener(MutableBlobStorage::MutableBlobStorageType aType,
                             nsISupports* aParent,
                             const nsACString& aContentType,
                             MutableBlobStorageCallback* aCallback,
                             nsIEventTarget* aEventTarget = nullptr);
 
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_fullscreen-shadowdom.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+  <!--
+  https://bugzilla.mozilla.org/show_bug.cgi?id=1430305
+  Bug 1430305 - Implement ShadowRoot.fullscreenElement
+  -->
+  <head>
+    <title>Bug 1430305</title>
+    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+    </script>
+    <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
+    </script>
+    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  </head>
+  <body>
+    <a target="_blank"
+       href="https://bugzilla.mozilla.org/show_bug.cgi?id=1430305">
+      Mozilla Bug 1430305</a>
+
+    <div id="host"></div>
+
+    <pre id="test">
+      <script type="application/javascript">
+
+        function begin() {
+          var host = document.getElementById("host");
+          var shadowRoot = host.attachShadow({mode: "open"});
+          shadowRoot.innerHTML = "<div>test</div>";
+          var elem = shadowRoot.firstChild;
+          var gotFullscreenEvent = false;
+
+          document.addEventListener("fullscreenchange", function (e) {
+            if (document.fullscreenElement === host) {
+              is(shadowRoot.fullscreenElement, elem,
+                 "Expected element entered fullsceen");
+              gotFullscreenEvent = true;
+              document.exitFullscreen();
+            } else {
+              opener.ok(gotFullscreenEvent, "Entered fullscreen as expected");
+              is(shadowRoot.fullscreenElement, null,
+                 "Shouldn't have fullscreenElement anymore.");
+              is(document.fullscreenElement, null,
+                 "Shouldn't have fullscreenElement anymore.");
+              opener.nextTest();
+            }
+          });
+          elem.requestFullscreen();
+        }
+      </script>
+    </pre>
+  </body>
+</html>
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -454,16 +454,17 @@ support-files =
   file_fullscreen-multiple.html
   file_fullscreen-navigation.html
   file_fullscreen-nested.html
   file_fullscreen-prefixed.html
   file_fullscreen-plugins.html
   file_fullscreen-rollback.html
   file_fullscreen-scrollbar.html
   file_fullscreen-selector.html
+  file_fullscreen-shadowdom.html
   file_fullscreen-svg-element.html
   file_fullscreen-table.html
   file_fullscreen-top-layer.html
   file_fullscreen-unprefix-disabled-inner.html
   file_fullscreen-unprefix-disabled.html
   file_fullscreen-utils.js
 [test_fullscreen-api-race.html]
 tags = fullscreen
--- a/dom/html/test/test_fullscreen-api.html
+++ b/dom/html/test/test_fullscreen-api.html
@@ -33,16 +33,17 @@ var gTestWindows = [
   "file_fullscreen-denied.html",
   "file_fullscreen-api.html",
   "file_fullscreen-plugins.html",
   "file_fullscreen-hidden.html",
   "file_fullscreen-svg-element.html",
   "file_fullscreen-navigation.html",
   "file_fullscreen-scrollbar.html",
   "file_fullscreen-selector.html",
+  "file_fullscreen-shadowdom.html",
   "file_fullscreen-top-layer.html",
   "file_fullscreen-backdrop.html",
   "file_fullscreen-nested.html",
   "file_fullscreen-prefixed.html",
   "file_fullscreen-unprefix-disabled.html",
   "file_fullscreen-lenient-setters.html",
   "file_fullscreen-table.html",
 ];
@@ -141,16 +142,17 @@ is(window.fullScreen, false, "Shouldn't 
 // to write
 addLoadEvent(function() {
   SpecialPowers.pushPrefEnv({
       "set": [
         ["full-screen-api.enabled", true],
         ["full-screen-api.unprefix.enabled", true],
         ["full-screen-api.allow-trusted-requests-only", false],
         ["full-screen-api.transition-duration.enter", "0 0"],
-        ["full-screen-api.transition-duration.leave", "0 0"]
+        ["full-screen-api.transition-duration.leave", "0 0"],
+        ["dom.webcomponents.shadowdom.enabled", true]
       ]}, nextTest);
 });
 SimpleTest.waitForExplicitFinish();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/systemservices/CamerasChild.cpp
+++ b/dom/media/systemservices/CamerasChild.cpp
@@ -29,17 +29,19 @@ mozilla::LazyLogModule gCamerasChildLog(
 
 namespace mozilla {
 namespace camera {
 
 CamerasSingleton::CamerasSingleton()
   : mCamerasMutex("CamerasSingleton::mCamerasMutex"),
     mCameras(nullptr),
     mCamerasChildThread(nullptr),
-    mFakeDeviceChangeEventThread(nullptr) {
+    mFakeDeviceChangeEventThread(nullptr),
+    mInShutdown(false)
+{
   LOG(("CamerasSingleton: %p", this));
 }
 
 CamerasSingleton::~CamerasSingleton() {
   LOG(("~CamerasSingleton: %p", this));
 }
 
 class FakeOnDeviceChangeEventRunnable : public Runnable
@@ -285,36 +287,38 @@ CamerasChild::DispatchToParent(nsIRunnab
 
 int
 CamerasChild::NumberOfCapabilities(CaptureEngine aCapEngine,
                                    const char* deviceUniqueIdUTF8)
 {
   LOG((__PRETTY_FUNCTION__));
   LOG(("NumberOfCapabilities for %s", deviceUniqueIdUTF8));
   nsCString unique_id(deviceUniqueIdUTF8);
+  RefPtr<CamerasChild> self(this);
   nsCOMPtr<nsIRunnable> runnable =
     mozilla::NewNonOwningRunnableMethod<CaptureEngine, nsCString>(
       "camera::PCamerasChild::SendNumberOfCapabilities",
-      this,
+      self,
       &CamerasChild::SendNumberOfCapabilities,
       aCapEngine,
       unique_id);
   LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger);
   LOG(("Capture capability count: %d", dispatcher.ReturnValue()));
   return dispatcher.ReturnValue();
 }
 
 int
 CamerasChild::NumberOfCaptureDevices(CaptureEngine aCapEngine)
 {
   LOG((__PRETTY_FUNCTION__));
+  RefPtr<CamerasChild> self(this);
   nsCOMPtr<nsIRunnable> runnable =
     mozilla::NewNonOwningRunnableMethod<CaptureEngine>(
       "camera::PCamerasChild::SendNumberOfCaptureDevices",
-      this,
+      self,
       &CamerasChild::SendNumberOfCaptureDevices,
       aCapEngine);
   LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger);
   LOG(("Capture Devices: %d", dispatcher.ReturnValue()));
   return dispatcher.ReturnValue();
 }
 
 mozilla::ipc::IPCResult
@@ -328,39 +332,41 @@ CamerasChild::RecvReplyNumberOfCaptureDe
   monitor.Notify();
   return IPC_OK();
 }
 
 int
 CamerasChild::EnsureInitialized(CaptureEngine aCapEngine)
 {
   LOG((__PRETTY_FUNCTION__));
+  RefPtr<CamerasChild> self(this);
   nsCOMPtr<nsIRunnable> runnable =
     mozilla::NewNonOwningRunnableMethod<CaptureEngine>(
       "camera::PCamerasChild::SendEnsureInitialized",
-      this,
+      self,
       &CamerasChild::SendEnsureInitialized,
       aCapEngine);
   LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger);
   LOG(("Capture Devices: %d", dispatcher.ReturnValue()));
   return dispatcher.ReturnValue();
 }
 
 int
 CamerasChild::GetCaptureCapability(CaptureEngine aCapEngine,
                                    const char* unique_idUTF8,
                                    const unsigned int capability_number,
                                    webrtc::VideoCaptureCapability& capability)
 {
   LOG(("GetCaptureCapability: %s %d", unique_idUTF8, capability_number));
   nsCString unique_id(unique_idUTF8);
+  RefPtr<CamerasChild> self(this);
   nsCOMPtr<nsIRunnable> runnable =
     mozilla::NewNonOwningRunnableMethod<CaptureEngine, nsCString, unsigned int>(
       "camera::PCamerasChild::SendGetCaptureCapability",
-      this,
+      self,
       &CamerasChild::SendGetCaptureCapability,
       aCapEngine,
       unique_id,
       capability_number);
   LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero);
   if (dispatcher.Success()) {
     capability = mReplyCapability;
   }
@@ -389,20 +395,21 @@ int
 CamerasChild::GetCaptureDevice(CaptureEngine aCapEngine,
                                unsigned int list_number, char* device_nameUTF8,
                                const unsigned int device_nameUTF8Length,
                                char* unique_idUTF8,
                                const unsigned int unique_idUTF8Length,
                                bool* scary)
 {
   LOG((__PRETTY_FUNCTION__));
+  RefPtr<CamerasChild> self(this);
   nsCOMPtr<nsIRunnable> runnable =
     mozilla::NewNonOwningRunnableMethod<CaptureEngine, unsigned int>(
       "camera::PCamerasChild::SendGetCaptureDevice",
-      this,
+      self,
       &CamerasChild::SendGetCaptureDevice,
       aCapEngine,
       list_number);
   LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero);
   if (dispatcher.Success()) {
     base::strlcpy(device_nameUTF8, mReplyDeviceName.get(), device_nameUTF8Length);
     base::strlcpy(unique_idUTF8, mReplyDeviceID.get(), unique_idUTF8Length);
     if (scary) {
@@ -433,22 +440,23 @@ int
 CamerasChild::AllocateCaptureDevice(CaptureEngine aCapEngine,
                                     const char* unique_idUTF8,
                                     const unsigned int unique_idUTF8Length,
                                     int& aStreamId,
                                     const mozilla::ipc::PrincipalInfo& aPrincipalInfo)
 {
   LOG((__PRETTY_FUNCTION__));
   nsCString unique_id(unique_idUTF8);
+  RefPtr<CamerasChild> self(this);
   nsCOMPtr<nsIRunnable> runnable =
     mozilla::NewNonOwningRunnableMethod<CaptureEngine,
                                         nsCString,
                                         const mozilla::ipc::PrincipalInfo&>(
       "camera::PCamerasChild::SendAllocateCaptureDevice",
-      this,
+      self,
       &CamerasChild::SendAllocateCaptureDevice,
       aCapEngine,
       unique_id,
       aPrincipalInfo);
   LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero);
   if (dispatcher.Success()) {
     LOG(("Capture Device allocated: %d", mReplyInteger));
     aStreamId = mReplyInteger;
@@ -469,20 +477,21 @@ CamerasChild::RecvReplyAllocateCaptureDe
   return IPC_OK();
 }
 
 int
 CamerasChild::ReleaseCaptureDevice(CaptureEngine aCapEngine,
                                    const int capture_id)
 {
   LOG((__PRETTY_FUNCTION__));
+  RefPtr<CamerasChild> self(this);
   nsCOMPtr<nsIRunnable> runnable =
     mozilla::NewNonOwningRunnableMethod<CaptureEngine, int>(
       "camera::PCamerasChild::SendReleaseCaptureDevice",
-      this,
+      self,
       &CamerasChild::SendReleaseCaptureDevice,
       aCapEngine,
       capture_id);
   LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero);
   return dispatcher.ReturnValue();
 }
 
 void
@@ -520,50 +529,55 @@ CamerasChild::StartCapture(CaptureEngine
   AddCallback(aCapEngine, capture_id, cb);
   VideoCaptureCapability capCap(webrtcCaps.width,
                            webrtcCaps.height,
                            webrtcCaps.maxFPS,
                            webrtcCaps.expectedCaptureDelay,
                            webrtcCaps.rawType,
                            webrtcCaps.codecType,
                            webrtcCaps.interlaced);
+  RefPtr<CamerasChild> self(this);
   nsCOMPtr<nsIRunnable> runnable = mozilla::
     NewNonOwningRunnableMethod<CaptureEngine, int, VideoCaptureCapability>(
       "camera::PCamerasChild::SendStartCapture",
-      this,
+      self,
       &CamerasChild::SendStartCapture,
       aCapEngine,
       capture_id,
       capCap);
   LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero);
   return dispatcher.ReturnValue();
 }
 
 int
 CamerasChild::StopCapture(CaptureEngine aCapEngine, const int capture_id)
 {
   LOG((__PRETTY_FUNCTION__));
+  RefPtr<CamerasChild> self(this);
   nsCOMPtr<nsIRunnable> runnable =
     mozilla::NewNonOwningRunnableMethod<CaptureEngine, int>(
       "camera::PCamerasChild::SendStopCapture",
-      this,
+      self,
       &CamerasChild::SendStopCapture,
       aCapEngine,
       capture_id);
   LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero);
   if (dispatcher.Success()) {
     RemoveCallback(aCapEngine, capture_id);
   }
   return dispatcher.ReturnValue();
 }
 
 void
 Shutdown(void)
 {
   OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
+
+  CamerasSingleton::StartShutdown();
+
   CamerasChild* child = CamerasSingleton::Child();
   if (!child) {
     // We don't want to cause everything to get fired up if we're
     // really already shut down.
     LOG(("Shutdown when already shut down"));
     return;
   }
   child->ShutdownAll();
@@ -605,18 +619,19 @@ CamerasChild::ShutdownParent()
     mIPCIsAlive = false;
     monitor.NotifyAll();
   }
   if (CamerasSingleton::Thread()) {
     LOG(("Dispatching actor deletion"));
     // Delete the parent actor.
     // CamerasChild (this) will remain alive and is only deleted by the
     // IPC layer when SendAllDone returns.
+    RefPtr<CamerasChild> self(this);
     nsCOMPtr<nsIRunnable> deleteRunnable = mozilla::NewNonOwningRunnableMethod(
-      "camera::PCamerasChild::SendAllDone", this, &CamerasChild::SendAllDone);
+      "camera::PCamerasChild::SendAllDone", self, &CamerasChild::SendAllDone);
     CamerasSingleton::Thread()->Dispatch(deleteRunnable, NS_DISPATCH_NORMAL);
   } else {
     LOG(("ShutdownParent called without PBackground thread"));
   }
 }
 
 void
 CamerasChild::ShutdownChild()
@@ -713,17 +728,17 @@ CamerasChild::CamerasChild()
 
   MOZ_COUNT_CTOR(CamerasChild);
 }
 
 CamerasChild::~CamerasChild()
 {
   LOG(("~CamerasChild: %p", this));
 
-  {
+  if (!CamerasSingleton::InShutdown()) {
     OffTheBooksMutexAutoLock lock(CamerasSingleton::Mutex());
     // In normal circumstances we've already shut down and the
     // following does nothing. But on fatal IPC errors we will
     // get destructed immediately, and should not try to reach
     // the parent.
     ShutdownChild();
   }
 
--- a/dom/media/systemservices/CamerasChild.h
+++ b/dom/media/systemservices/CamerasChild.h
@@ -87,16 +87,24 @@ public:
     return gTheInstance.get()->mCamerasChildThread;
   }
 
   static nsCOMPtr<nsIThread>& FakeDeviceChangeEventThread() {
     Mutex().AssertCurrentThreadOwns();
     return gTheInstance.get()->mFakeDeviceChangeEventThread;
   }
 
+  static bool InShutdown() {
+    return gTheInstance.get()->mInShutdown;
+  }
+
+  static void StartShutdown() {
+    gTheInstance.get()->mInShutdown = true;
+  }
+
 private:
   static Singleton<CamerasSingleton> gTheInstance;
 
   // Reinitializing CamerasChild will change the pointers below.
   // We don't want this to happen in the middle of preparing IPC.
   // We will be alive on destruction, so this needs to be off the books.
   mozilla::OffTheBooksMutex mCamerasMutex;
 
@@ -104,16 +112,17 @@ private:
   // It will set and clear this pointer as appropriate in setup/teardown.
   // We'd normally make this a WeakPtr but unfortunately the IPC code already
   // uses the WeakPtr mixin in a protected base class of CamerasChild, and in
   // any case the object becomes unusable as soon as IPC is tearing down, which
   // will be before actual destruction.
   CamerasChild* mCameras;
   nsCOMPtr<nsIThread> mCamerasChildThread;
   nsCOMPtr<nsIThread> mFakeDeviceChangeEventThread;
+  Atomic<bool> mInShutdown;
 };
 
 // Get a pointer to a CamerasChild object we can use to do IPC with.
 // This does everything needed to set up, including starting the IPC
 // channel with PBackground, blocking until thats done, and starting the
 // thread to do IPC on. This will fail if we're in shutdown. On success
 // it will set up the CamerasSingleton.
 CamerasChild* GetCamerasChild();
@@ -143,17 +152,17 @@ class CamerasChild final : public PCamer
                           ,public DeviceChangeCallback
 {
   friend class mozilla::ipc::BackgroundChildImpl;
   template <class T> friend class mozilla::camera::LockAndDispatch;
 
 public:
   // We are owned by the PBackground thread only. CamerasSingleton
   // takes a non-owning reference.
-  NS_INLINE_DECL_REFCOUNTING(CamerasChild)
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CamerasChild)
 
   // IPC messages recevied, received on the PBackground thread
   // these are the actual callbacks with data
   mozilla::ipc::IPCResult RecvDeliverFrame(const CaptureEngine&, const int&,
                                            mozilla::ipc::Shmem&&,
                                            const VideoFrameProperties & prop) override;
 
   mozilla::ipc::IPCResult RecvDeviceChange() override;
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -237,20 +237,16 @@ partial interface Document {
   [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"]
   readonly attribute boolean fullscreen;
   [BinaryName="fullscreen"]
   readonly attribute boolean mozFullScreen;
   [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled", NeedsCallerType]
   readonly attribute boolean fullscreenEnabled;
   [BinaryName="fullscreenEnabled", NeedsCallerType]
   readonly attribute boolean mozFullScreenEnabled;
-  [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"]
-  readonly attribute Element? fullscreenElement;
-  [BinaryName="fullscreenElement"]
-  readonly attribute Element? mozFullScreenElement;
 
   [Func="nsDocument::IsUnprefixedFullscreenEnabled"]
   void exitFullscreen();
   [BinaryName="exitFullscreen"]
   void mozCancelFullScreen();
 
   // Events handlers
   [Func="nsDocument::IsUnprefixedFullscreenEnabled"]
--- a/dom/webidl/DocumentOrShadowRoot.webidl
+++ b/dom/webidl/DocumentOrShadowRoot.webidl
@@ -18,11 +18,13 @@ interface DocumentOrShadowRoot {
   // sequence<Element> elementsFromPoint (float x, float y);
   // Not implemented yet: bug 1430307.
   // CaretPosition? caretPositionFromPoint (float x, float y);
 
   readonly attribute Element? activeElement;
   readonly attribute StyleSheetList styleSheets;
 
   readonly attribute Element? pointerLockElement;
-  // Not implemented yet: bug 1430305.
-  // readonly attribute Element? fullscreenElement;
+  [LenientSetter, Func="nsIDocument::IsUnprefixedFullscreenEnabled"]
+  readonly attribute Element? fullscreenElement;
+  [BinaryName="fullscreenElement"]
+  readonly attribute Element? mozFullScreenElement;
 };
--- a/dom/workers/WorkerHolder.h
+++ b/dom/workers/WorkerHolder.h
@@ -28,21 +28,16 @@ class WorkerPrivate;
  * | Terminating |     yes     |       yes       |
  * +-------------+-------------+-----------------+
  * |  Canceling  |     yes     |       yes       |
  * +-------------+-------------+-----------------+
  * |   Killing   |     yes     |       yes       |
  * +-------------+-------------+-----------------+
  */
 
-#ifdef Status
-/* Xlib headers insist on this for some reason... Nuke it because
-   it'll override our member name */
-#undef Status
-#endif
 enum WorkerStatus
 {
   // Not yet scheduled.
   Pending = 0,
 
   // This status means that the worker is active.
   Running,
 
--- a/gfx/2d/FilterNodeSoftware.cpp
+++ b/gfx/2d/FilterNodeSoftware.cpp
@@ -608,17 +608,88 @@ already_AddRefed<DataSourceSurface>
 FilterNodeSoftware::GetOutput(const IntRect &aRect)
 {
   MOZ_ASSERT(GetOutputRectInRect(aRect).Contains(aRect));
 
   if (aRect.Overflows()) {
     return nullptr;
   }
 
-  return Render(aRect);
+  IntRect cachedRect;
+  IntRect requestedRect;
+  RefPtr<DataSourceSurface> cachedOutput;
+
+  // Lock the cache and retrieve a cached surface if we have one and it can
+  // satisfy this request, or else request a rect we will compute and cache
+  {
+    MutexAutoLock lock(mCacheMutex);
+
+    if (!mCachedRect.Contains(aRect)) {
+      RequestRect(aRect);
+      requestedRect = mRequestedRect;
+    } else {
+      MOZ_ASSERT(mCachedOutput, "cached rect but no cached output?");
+      cachedRect = mCachedRect;
+      cachedOutput = mCachedOutput;
+    }
+  }
+
+  if (!cachedOutput) {
+    // Compute the output
+    cachedOutput = Render(requestedRect);
+
+    // Update the cache for future requests
+    MutexAutoLock lock(mCacheMutex);
+
+    mCachedOutput = cachedOutput;
+    if (!mCachedOutput) {
+      mCachedRect = IntRect();
+      mRequestedRect = IntRect();
+      return nullptr;
+    }
+    mCachedRect = requestedRect;
+    mRequestedRect = IntRect();
+
+    cachedRect = mCachedRect;
+  }
+
+  return GetDataSurfaceInRect(cachedOutput, cachedRect, aRect, EDGE_MODE_NONE);
+}
+
+void
+FilterNodeSoftware::RequestRect(const IntRect &aRect)
+{
+  if (mRequestedRect.Contains(aRect)) {
+    // Bail out now. Otherwise pathological filters can spend time exponential
+    // in the number of primitives, e.g. if each primitive takes the
+    // previous primitive as its two inputs.
+    return;
+  }
+  mRequestedRect = mRequestedRect.Union(aRect);
+  RequestFromInputsForRect(aRect);
+}
+
+void
+FilterNodeSoftware::RequestInputRect(uint32_t aInputEnumIndex, const IntRect &aRect)
+{
+  if (aRect.Overflows()) {
+    return;
+  }
+
+  int32_t inputIndex = InputIndex(aInputEnumIndex);
+  if (inputIndex < 0 || (uint32_t)inputIndex >= NumberOfSetInputs()) {
+    gfxDevCrash(LogReason::FilterInputError) << "Invalid input " << inputIndex << " vs. " << NumberOfSetInputs();
+    return;
+  }
+  if (mInputSurfaces[inputIndex]) {
+    return;
+  }
+  RefPtr<FilterNodeSoftware> filter = mInputFilters[inputIndex];
+  MOZ_ASSERT(filter, "missing input");
+  filter->RequestRect(filter->GetOutputRectInRect(aRect));
 }
 
 SurfaceFormat
 FilterNodeSoftware::DesiredFormat(SurfaceFormat aCurrentFormat,
                                   FormatHint aFormatHint)
 {
   if (aCurrentFormat == SurfaceFormat::A8 && aFormatHint == CAN_HANDLE_A8) {
     return SurfaceFormat::A8;
@@ -767,18 +838,66 @@ FilterNodeSoftware::GetInputRectInRect(u
 }
 
 size_t
 FilterNodeSoftware::NumberOfSetInputs()
 {
   return std::max(mInputSurfaces.size(), mInputFilters.size());
 }
 
+void
+FilterNodeSoftware::AddInvalidationListener(FilterInvalidationListener* aListener)
+{
+  MOZ_ASSERT(aListener, "null listener");
+  mInvalidationListeners.push_back(aListener);
+}
+
+void
+FilterNodeSoftware::RemoveInvalidationListener(FilterInvalidationListener* aListener)
+{
+  MOZ_ASSERT(aListener, "null listener");
+  std::vector<FilterInvalidationListener*>::iterator it =
+    std::find(mInvalidationListeners.begin(), mInvalidationListeners.end(), aListener);
+  mInvalidationListeners.erase(it);
+}
+
+void
+FilterNodeSoftware::FilterInvalidated(FilterNodeSoftware* aFilter)
+{
+  Invalidate();
+}
+
+void
+FilterNodeSoftware::Invalidate()
+{
+  MutexAutoLock lock(mCacheMutex);
+  mCachedOutput = nullptr;
+  mCachedRect = IntRect();
+  for (std::vector<FilterInvalidationListener*>::iterator it = mInvalidationListeners.begin();
+       it != mInvalidationListeners.end(); it++) {
+    (*it)->FilterInvalidated(this);
+  }
+}
+
+FilterNodeSoftware::FilterNodeSoftware()
+  : mCacheMutex("FilterNodeSoftware::mCacheMutex")
+{
+}
+
 FilterNodeSoftware::~FilterNodeSoftware()
 {
+  MOZ_ASSERT(!mInvalidationListeners.size(),
+             "All invalidation listeners should have unsubscribed themselves by now!");
+
+  for (std::vector<RefPtr<FilterNodeSoftware> >::iterator it = mInputFilters.begin();
+       it != mInputFilters.end(); it++) {
+    if (*it) {
+      (*it)->RemoveInvalidationListener(this);
+    }
+  }
 }
 
 void
 FilterNodeSoftware::SetInput(uint32_t aIndex, FilterNode *aFilter)
 {
   if (aFilter && aFilter->GetBackendType() != FILTER_BACKEND_SOFTWARE) {
     MOZ_ASSERT(false, "can only take software filters as inputs");
     return;
@@ -802,21 +921,28 @@ FilterNodeSoftware::SetInput(uint32_t aI
     gfxDevCrash(LogReason::FilterInputSet) << "Invalid set " << inputIndex;
     return;
   }
   if ((uint32_t)inputIndex >= NumberOfSetInputs()) {
     mInputSurfaces.resize(inputIndex + 1);
     mInputFilters.resize(inputIndex + 1);
   }
   mInputSurfaces[inputIndex] = aSurface;
+  if (mInputFilters[inputIndex]) {
+    mInputFilters[inputIndex]->RemoveInvalidationListener(this);
+  }
+  if (aFilter) {
+    aFilter->AddInvalidationListener(this);
+  }
   mInputFilters[inputIndex] = aFilter;
   if (!aSurface && !aFilter && (size_t)inputIndex == NumberOfSetInputs()) {
     mInputSurfaces.resize(inputIndex);
     mInputFilters.resize(inputIndex);
   }
+  Invalidate();
 }
 
 FilterNodeBlendSoftware::FilterNodeBlendSoftware()
  : mBlendMode(BLEND_MODE_MULTIPLY)
 {}
 
 int32_t
 FilterNodeBlendSoftware::InputIndex(uint32_t aInputEnumIndex)
@@ -828,16 +954,17 @@ FilterNodeBlendSoftware::InputIndex(uint
   }
 }
 
 void
 FilterNodeBlendSoftware::SetAttribute(uint32_t aIndex, uint32_t aBlendMode)
 {
   MOZ_ASSERT(aIndex == ATT_BLEND_BLENDMODE);
   mBlendMode = static_cast<BlendMode>(aBlendMode);
+  Invalidate();
 }
 
 static CompositionOp ToBlendOp(BlendMode aOp)
 {
   switch (aOp) {
   case BLEND_MODE_MULTIPLY:
     return CompositionOp::OP_MULTIPLY;
   case BLEND_MODE_SCREEN:
@@ -931,16 +1058,23 @@ FilterNodeBlendSoftware::Render(const In
   }
 
   Rect r(0, 0, size.width, size.height);
   dt->DrawSurface(input2, r, r, DrawSurfaceOptions(), DrawOptions(1.0f, ToBlendOp(mBlendMode)));
   dt->Flush();
   return target.forget();
 }
 
+void
+FilterNodeBlendSoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  RequestInputRect(IN_BLEND_IN, aRect);
+  RequestInputRect(IN_BLEND_IN2, aRect);
+}
+
 IntRect
 FilterNodeBlendSoftware::GetOutputRectInRect(const IntRect& aRect)
 {
   return GetInputRectInRect(IN_BLEND_IN, aRect).Union(
     GetInputRectInRect(IN_BLEND_IN2, aRect)).Intersect(aRect);
 }
 
 FilterNodeTransformSoftware::FilterNodeTransformSoftware()
@@ -956,23 +1090,25 @@ FilterNodeTransformSoftware::InputIndex(
   }
 }
 
 void
 FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex, uint32_t aFilter)
 {
   MOZ_ASSERT(aIndex == ATT_TRANSFORM_FILTER);
   mSamplingFilter = static_cast<SamplingFilter>(aFilter);
+  Invalidate();
 }
 
 void
 FilterNodeTransformSoftware::SetAttribute(uint32_t aIndex, const Matrix &aMatrix)
 {
   MOZ_ASSERT(aIndex == ATT_TRANSFORM_MATRIX);
   mMatrix = aMatrix;
+  Invalidate();
 }
 
 IntRect
 FilterNodeTransformSoftware::SourceRectForOutputRect(const IntRect &aRect)
 {
   if (aRect.IsEmpty()) {
     return IntRect();
   }
@@ -1037,16 +1173,22 @@ FilterNodeTransformSoftware::Render(cons
   dt->SetTransform(transform);
   dt->DrawSurface(input, r, r, DrawSurfaceOptions(mSamplingFilter));
 
   dt->Flush();
   surf->Unmap();
   return surf.forget();
 }
 
+void
+FilterNodeTransformSoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  RequestInputRect(IN_TRANSFORM_IN, SourceRectForOutputRect(aRect));
+}
+
 IntRect
 FilterNodeTransformSoftware::GetOutputRectInRect(const IntRect& aRect)
 {
   IntRect srcRect = SourceRectForOutputRect(aRect);
   if (srcRect.IsEmpty()) {
     return IntRect();
   }
 
@@ -1074,24 +1216,26 @@ FilterNodeMorphologySoftware::InputIndex
 
 void
 FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex,
                                            const IntSize &aRadii)
 {
   MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_RADII);
   mRadii.width = std::min(std::max(aRadii.width, 0), 100000);
   mRadii.height = std::min(std::max(aRadii.height, 0), 100000);
+  Invalidate();
 }
 
 void
 FilterNodeMorphologySoftware::SetAttribute(uint32_t aIndex,
                                            uint32_t aOperator)
 {
   MOZ_ASSERT(aIndex == ATT_MORPHOLOGY_OPERATOR);
   mOperator = static_cast<MorphologyOperator>(aOperator);
+  Invalidate();
 }
 
 static already_AddRefed<DataSourceSurface>
 ApplyMorphology(const IntRect& aSourceRect, DataSourceSurface* aInput,
                 const IntRect& aDestRect, int32_t rx, int32_t ry,
                 MorphologyOperator aOperator)
 {
   IntRect srcRect = aSourceRect - aDestRect.TopLeft();
@@ -1170,16 +1314,24 @@ FilterNodeMorphologySoftware::Render(con
 
   if (rx == 0 && ry == 0) {
     return input.forget();
   }
 
   return ApplyMorphology(srcRect, input, aRect, rx, ry, mOperator);
 }
 
+void
+FilterNodeMorphologySoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  IntRect srcRect = aRect;
+  srcRect.Inflate(mRadii);
+  RequestInputRect(IN_MORPHOLOGY_IN, srcRect);
+}
+
 IntRect
 FilterNodeMorphologySoftware::GetOutputRectInRect(const IntRect& aRect)
 {
   IntRect inflatedSourceRect = aRect;
   inflatedSourceRect.Inflate(mRadii);
   IntRect inputRect = GetInputRectInRect(IN_MORPHOLOGY_IN, inflatedSourceRect);
   if (mOperator == MORPHOLOGY_OPERATOR_ERODE) {
     inputRect.Deflate(mRadii);
@@ -1199,24 +1351,26 @@ FilterNodeColorMatrixSoftware::InputInde
 }
 
 void
 FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex,
                                             const Matrix5x4 &aMatrix)
 {
   MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_MATRIX);
   mMatrix = aMatrix;
+  Invalidate();
 }
 
 void
 FilterNodeColorMatrixSoftware::SetAttribute(uint32_t aIndex,
                                             uint32_t aAlphaMode)
 {
   MOZ_ASSERT(aIndex == ATT_COLOR_MATRIX_ALPHA_MODE);
   mAlphaMode = (AlphaMode)aAlphaMode;
+  Invalidate();
 }
 
 static already_AddRefed<DataSourceSurface>
 Premultiply(DataSourceSurface* aSurface)
 {
   if (aSurface->GetFormat() == SurfaceFormat::A8) {
     RefPtr<DataSourceSurface> surface(aSurface);
     return surface.forget();
@@ -1296,30 +1450,37 @@ FilterNodeColorMatrixSoftware::Render(co
 
   if (mAlphaMode == ALPHA_MODE_PREMULTIPLIED) {
     result = Premultiply(result);
   }
 
   return result.forget();
 }
 
+void
+FilterNodeColorMatrixSoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  RequestInputRect(IN_COLOR_MATRIX_IN, aRect);
+}
+
 IntRect
 FilterNodeColorMatrixSoftware::GetOutputRectInRect(const IntRect& aRect)
 {
   if (mMatrix._54 > 0.0f) {
     return aRect;
   }
   return GetInputRectInRect(IN_COLOR_MATRIX_IN, aRect);
 }
 
 void
 FilterNodeFloodSoftware::SetAttribute(uint32_t aIndex, const Color &aColor)
 {
   MOZ_ASSERT(aIndex == ATT_FLOOD_COLOR);
   mColor = aColor;
+  Invalidate();
 }
 
 static uint32_t
 ColorToBGRA(const Color& aColor)
 {
   union {
     uint32_t color;
     uint8_t components[4];
@@ -1412,16 +1573,17 @@ FilterNodeTileSoftware::InputIndex(uint3
 
 void
 FilterNodeTileSoftware::SetAttribute(uint32_t aIndex,
                                      const IntRect &aSourceRect)
 {
   MOZ_ASSERT(aIndex == ATT_TILE_SOURCE_RECT);
   mSourceRect.SetRect(int32_t(aSourceRect.X()), int32_t(aSourceRect.Y()),
                       int32_t(aSourceRect.Width()), int32_t(aSourceRect.Height()));
+  Invalidate();
 }
 
 namespace {
 struct CompareIntRects
 {
   bool operator()(const IntRect& a, const IntRect& b) const
   {
     if (a.X() != b.X()) {
@@ -1500,16 +1662,25 @@ FilterNodeTileSoftware::Render(const Int
 
       CopyRect(input, target, srcRect - srcRect.TopLeft(), destRect.TopLeft() - aRect.TopLeft());
     }
   }
 
   return target.forget();
 }
 
+void
+FilterNodeTileSoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  // Do not request anything.
+  // Source rects for the tile filter can be discontinuous with large gaps
+  // between them. Requesting those from our input filter might cause it to
+  // render the whole bounding box of all of them, which would be wasteful.
+}
+
 IntRect
 FilterNodeTileSoftware::GetOutputRectInRect(const IntRect& aRect)
 {
   return aRect;
 }
 
 FilterNodeComponentTransferSoftware::FilterNodeComponentTransferSoftware()
  : mDisableR(true)
@@ -1533,16 +1704,17 @@ FilterNodeComponentTransferSoftware::Set
       mDisableB = aDisable;
       break;
     case ATT_TRANSFER_DISABLE_A:
       mDisableA = aDisable;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeComponentTransferSoftware::SetAttribute");
   }
+  Invalidate();
 }
 
 void
 FilterNodeComponentTransferSoftware::GenerateLookupTable(ptrdiff_t aComponent,
                                                          uint8_t aTables[4][256],
                                                          bool aDisabled)
 {
   if (aDisabled) {
@@ -1659,16 +1831,22 @@ FilterNodeComponentTransferSoftware::Ren
     TransferComponents<1>(input, target, &lookupTables[B8G8R8A8_COMPONENT_BYTEOFFSET_A]);
   } else {
     TransferComponents<4>(input, target, lookupTables);
   }
 
   return target.forget();
 }
 
+void
+FilterNodeComponentTransferSoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  RequestInputRect(IN_TRANSFER_IN, aRect);
+}
+
 IntRect
 FilterNodeComponentTransferSoftware::GetOutputRectInRect(const IntRect& aRect)
 {
   if (mDisableA) {
     return GetInputRectInRect(IN_TRANSFER_IN, aRect);
   }
   return aRect;
 }
@@ -1699,16 +1877,17 @@ FilterNodeTableTransferSoftware::SetAttr
       mTableB = table;
       break;
     case ATT_TABLE_TRANSFER_TABLE_A:
       mTableA = table;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeTableTransferSoftware::SetAttribute");
   }
+  Invalidate();
 }
 
 void
 FilterNodeTableTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
                                                  uint8_t aTable[256])
 {
   switch (aComponent) {
     case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
@@ -1767,16 +1946,17 @@ FilterNodeDiscreteTransferSoftware::SetA
       mTableB = discrete;
       break;
     case ATT_DISCRETE_TRANSFER_TABLE_A:
       mTableA = discrete;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeDiscreteTransferSoftware::SetAttribute");
   }
+  Invalidate();
 }
 
 void
 FilterNodeDiscreteTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
                                                     uint8_t aTable[256])
 {
   switch (aComponent) {
     case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
@@ -1855,16 +2035,17 @@ FilterNodeLinearTransferSoftware::SetAtt
       mSlopeA = aValue;
       break;
     case ATT_LINEAR_TRANSFER_INTERCEPT_A:
       mInterceptA = aValue;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeLinearTransferSoftware::SetAttribute");
   }
+  Invalidate();
 }
 
 void
 FilterNodeLinearTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
                                                   uint8_t aTable[256])
 {
   switch (aComponent) {
     case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
@@ -1948,16 +2129,17 @@ FilterNodeGammaTransferSoftware::SetAttr
       mExponentA = aValue;
       break;
     case ATT_GAMMA_TRANSFER_OFFSET_A:
       mOffsetA = aValue;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeGammaTransferSoftware::SetAttribute");
   }
+  Invalidate();
 }
 
 void
 FilterNodeGammaTransferSoftware::FillLookupTable(ptrdiff_t aComponent,
                                                  uint8_t aTable[256])
 {
   switch (aComponent) {
     case B8G8R8A8_COMPONENT_BYTEOFFSET_R:
@@ -2009,84 +2191,92 @@ FilterNodeConvolveMatrixSoftware::InputI
 }
 
 void
 FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
                                                const IntSize &aKernelSize)
 {
   MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_SIZE);
   mKernelSize = aKernelSize;
+  Invalidate();
 }
 
 void
 FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
                                                const Float *aMatrix,
                                                uint32_t aSize)
 {
   MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_KERNEL_MATRIX);
   mKernelMatrix = std::vector<Float>(aMatrix, aMatrix + aSize);
+  Invalidate();
 }
 
 void
 FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, Float aValue)
 {
   switch (aIndex) {
     case ATT_CONVOLVE_MATRIX_DIVISOR:
       mDivisor = aValue;
       break;
     case ATT_CONVOLVE_MATRIX_BIAS:
       mBias = aValue;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute");
   }
+  Invalidate();
 }
 
 void
 FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex, const Size &aKernelUnitLength)
 {
   switch (aIndex) {
     case ATT_CONVOLVE_MATRIX_KERNEL_UNIT_LENGTH:
       mKernelUnitLength = aKernelUnitLength;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeConvolveMatrixSoftware::SetAttribute");
   }
+  Invalidate();
 }
 
 void
 FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
                                                const IntPoint &aTarget)
 {
   MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_TARGET);
   mTarget = aTarget;
+  Invalidate();
 }
 
 void
 FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
                                                const IntRect &aSourceRect)
 {
   MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_SOURCE_RECT);
   mSourceRect = aSourceRect;
+  Invalidate();
 }
 
 void
 FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
                                                uint32_t aEdgeMode)
 {
   MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_EDGE_MODE);
   mEdgeMode = static_cast<ConvolveMatrixEdgeMode>(aEdgeMode);
+  Invalidate();
 }
 
 void
 FilterNodeConvolveMatrixSoftware::SetAttribute(uint32_t aIndex,
                                                bool aPreserveAlpha)
 {
   MOZ_ASSERT(aIndex == ATT_CONVOLVE_MATRIX_PRESERVE_ALPHA);
   mPreserveAlpha = aPreserveAlpha;
+  Invalidate();
 }
 
 #ifdef DEBUG
 static inline void
 DebugOnlyCheckColorSamplingAccess(const uint8_t* aSampleAddress, const uint8_t* aBoundsBegin, const uint8_t* aBoundsEnd)
 {
   MOZ_ASSERT(aSampleAddress >= aBoundsBegin, "accessing before start");
   MOZ_ASSERT(aSampleAddress < aBoundsEnd, "accessing after end");
@@ -2323,16 +2513,22 @@ FilterNodeConvolveMatrixSoftware::DoRend
                     aKernelUnitLengthX, aKernelUnitLengthY);
     }
   }
   delete[] intKernel;
 
   return target.forget();
 }
 
+void
+FilterNodeConvolveMatrixSoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  RequestInputRect(IN_CONVOLVE_MATRIX_IN, InflatedSourceRect(aRect));
+}
+
 IntRect
 FilterNodeConvolveMatrixSoftware::InflatedSourceRect(const IntRect &aDestRect)
 {
   if (aDestRect.IsEmpty()) {
     return IntRect();
   }
 
   IntMargin margin;
@@ -2389,31 +2585,33 @@ FilterNodeDisplacementMapSoftware::Input
 }
 
 void
 FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex,
                                                 Float aScale)
 {
   MOZ_ASSERT(aIndex == ATT_DISPLACEMENT_MAP_SCALE);
   mScale = aScale;
+  Invalidate();
 }
 
 void
 FilterNodeDisplacementMapSoftware::SetAttribute(uint32_t aIndex, uint32_t aValue)
 {
   switch (aIndex) {
     case ATT_DISPLACEMENT_MAP_X_CHANNEL:
       mChannelX = static_cast<ColorChannel>(aValue);
       break;
     case ATT_DISPLACEMENT_MAP_Y_CHANNEL:
       mChannelY = static_cast<ColorChannel>(aValue);
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeDisplacementMapSoftware::SetAttribute");
   }
+  Invalidate();
 }
 
 already_AddRefed<DataSourceSurface>
 FilterNodeDisplacementMapSoftware::Render(const IntRect& aRect)
 {
   IntRect srcRect = InflatedSourceOrDestRect(aRect);
   RefPtr<DataSourceSurface> input =
     GetInputDataSourceSurface(IN_DISPLACEMENT_MAP_IN, srcRect, NEED_COLOR_CHANNELS);
@@ -2468,16 +2666,23 @@ FilterNodeDisplacementMapSoftware::Rende
 
     // Keep valgrind happy.
     PodZero(&targetData[y * targetStride + 4 * aRect.Width()], targetStride - 4 * aRect.Width());
   }
 
   return target.forget();
 }
 
+void
+FilterNodeDisplacementMapSoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  RequestInputRect(IN_DISPLACEMENT_MAP_IN, InflatedSourceOrDestRect(aRect));
+  RequestInputRect(IN_DISPLACEMENT_MAP_IN2, aRect);
+}
+
 IntRect
 FilterNodeDisplacementMapSoftware::InflatedSourceOrDestRect(const IntRect &aDestOrSourceRect)
 {
   IntRect sourceOrDestRect = aDestOrSourceRect;
   sourceOrDestRect.Inflate(ceil(fabs(mScale) / 2));
   return sourceOrDestRect;
 }
 
@@ -2508,36 +2713,39 @@ FilterNodeTurbulenceSoftware::SetAttribu
   switch (aIndex) {
     case ATT_TURBULENCE_BASE_FREQUENCY:
       mBaseFrequency = aBaseFrequency;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute");
       break;
   }
+  Invalidate();
 }
 
 void
 FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, const IntRect &aRect)
 {
   switch (aIndex) {
     case ATT_TURBULENCE_RECT:
       mRenderRect = aRect;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute");
       break;
   }
+  Invalidate();
 }
 
 void
 FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, bool aStitchable)
 {
   MOZ_ASSERT(aIndex == ATT_TURBULENCE_STITCHABLE);
   mStitchable = aStitchable;
+  Invalidate();
 }
 
 void
 FilterNodeTurbulenceSoftware::SetAttribute(uint32_t aIndex, uint32_t aValue)
 {
   switch (aIndex) {
     case ATT_TURBULENCE_NUM_OCTAVES:
       mNumOctaves = aValue;
@@ -2547,16 +2755,17 @@ FilterNodeTurbulenceSoftware::SetAttribu
       break;
     case ATT_TURBULENCE_TYPE:
       mType = static_cast<TurbulenceType>(aValue);
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeTurbulenceSoftware::SetAttribute");
       break;
   }
+  Invalidate();
 }
 
 already_AddRefed<DataSourceSurface>
 FilterNodeTurbulenceSoftware::Render(const IntRect& aRect)
 {
   return FilterProcessing::RenderTurbulence(
     aRect.Size(), aRect.TopLeft(), mBaseFrequency,
     mSeed, mNumOctaves, mType, mStitchable, Rect(mRenderRect));
@@ -2590,16 +2799,18 @@ FilterNodeArithmeticCombineSoftware::Set
 {
   MOZ_ASSERT(aIndex == ATT_ARITHMETIC_COMBINE_COEFFICIENTS);
   MOZ_ASSERT(aSize == 4);
 
   mK1 = aFloat[0];
   mK2 = aFloat[1];
   mK3 = aFloat[2];
   mK4 = aFloat[3];
+
+  Invalidate();
 }
 
 already_AddRefed<DataSourceSurface>
 FilterNodeArithmeticCombineSoftware::Render(const IntRect& aRect)
 {
   RefPtr<DataSourceSurface> input1 =
     GetInputDataSourceSurface(IN_ARITHMETIC_COMBINE_IN, aRect, NEED_COLOR_CHANNELS);
   RefPtr<DataSourceSurface> input2 =
@@ -2620,16 +2831,23 @@ FilterNodeArithmeticCombineSoftware::Ren
     k1 = 0.0f;
     k3 = 0.0f;
     input2 = input1;
   }
 
   return FilterProcessing::ApplyArithmeticCombine(input1, input2, k1, k2, k3, k4);
 }
 
+void
+FilterNodeArithmeticCombineSoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  RequestInputRect(IN_ARITHMETIC_COMBINE_IN, aRect);
+  RequestInputRect(IN_ARITHMETIC_COMBINE_IN2, aRect);
+}
+
 IntRect
 FilterNodeArithmeticCombineSoftware::GetOutputRectInRect(const IntRect& aRect)
 {
   if (mK4 > 0.0f) {
     return aRect;
   }
   IntRect rectFrom1 = GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN, aRect).Intersect(aRect);
   IntRect rectFrom2 = GetInputRectInRect(IN_ARITHMETIC_COMBINE_IN2, aRect).Intersect(aRect);
@@ -2656,16 +2874,17 @@ FilterNodeCompositeSoftware::InputIndex(
   return aInputEnumIndex - IN_COMPOSITE_IN_START;
 }
 
 void
 FilterNodeCompositeSoftware::SetAttribute(uint32_t aIndex, uint32_t aCompositeOperator)
 {
   MOZ_ASSERT(aIndex == ATT_COMPOSITE_OPERATOR);
   mOperator = static_cast<CompositeOperator>(aCompositeOperator);
+  Invalidate();
 }
 
 already_AddRefed<DataSourceSurface>
 FilterNodeCompositeSoftware::Render(const IntRect& aRect)
 {
   RefPtr<DataSourceSurface> start =
     GetInputDataSourceSurface(IN_COMPOSITE_IN_START, aRect, NEED_COLOR_CHANNELS);
   RefPtr<DataSourceSurface> dest =
@@ -2702,16 +2921,24 @@ FilterNodeCompositeSoftware::Render(cons
           // no additional input can get rid of that transparency.
           return nullptr;
       }
     }
   }
   return dest.forget();
 }
 
+void
+FilterNodeCompositeSoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) {
+    RequestInputRect(IN_COMPOSITE_IN_START + inputIndex, aRect);
+  }
+}
+
 IntRect
 FilterNodeCompositeSoftware::GetOutputRectInRect(const IntRect& aRect)
 {
   IntRect rect;
   for (size_t inputIndex = 0; inputIndex < NumberOfSetInputs(); inputIndex++) {
     IntRect inputRect = GetInputRectInRect(IN_COMPOSITE_IN_START + inputIndex, aRect);
     if (mOperator == COMPOSITE_OPERATOR_IN && inputIndex > 0) {
       rect = rect.Intersect(inputRect);
@@ -2787,16 +3014,22 @@ FilterNodeBlurXYSoftware::Render(const I
       blur.Blur(channel3Map.GetData());
     }
     target = FilterProcessing::CombineColorChannels(channel0, channel1, channel2, channel3);
   }
 
   return GetDataSurfaceInRect(target, srcRect, aRect, EDGE_MODE_NONE);
 }
 
+void
+FilterNodeBlurXYSoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  RequestInputRect(IN_GAUSSIAN_BLUR_IN, InflatedSourceOrDestRect(aRect));
+}
+
 IntRect
 FilterNodeBlurXYSoftware::InflatedSourceOrDestRect(const IntRect &aDestRect)
 {
   Size sigmaXY = StdDeviationXY();
   IntSize d = AlphaBoxBlur::CalculateBlurRadius(Point(sigmaXY.width, sigmaXY.height));
   IntRect srcRect = aDestRect;
   srcRect.Inflate(d);
   return srcRect;
@@ -2827,16 +3060,17 @@ FilterNodeGaussianBlurSoftware::SetAttri
 {
   switch (aIndex) {
     case ATT_GAUSSIAN_BLUR_STD_DEVIATION:
       mStdDeviation = ClampStdDeviation(aStdDeviation);
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeGaussianBlurSoftware::SetAttribute");
   }
+  Invalidate();
 }
 
 Size
 FilterNodeGaussianBlurSoftware::StdDeviationXY()
 {
   return Size(mStdDeviation, mStdDeviation);
 }
 
@@ -2850,29 +3084,31 @@ FilterNodeDirectionalBlurSoftware::SetAt
 {
   switch (aIndex) {
     case ATT_DIRECTIONAL_BLUR_STD_DEVIATION:
       mStdDeviation = ClampStdDeviation(aStdDeviation);
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute");
   }
+  Invalidate();
 }
 
 void
 FilterNodeDirectionalBlurSoftware::SetAttribute(uint32_t aIndex,
                                                 uint32_t aBlurDirection)
 {
   switch (aIndex) {
     case ATT_DIRECTIONAL_BLUR_DIRECTION:
       mBlurDirection = (BlurDirection)aBlurDirection;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeDirectionalBlurSoftware::SetAttribute");
   }
+  Invalidate();
 }
 
 Size
 FilterNodeDirectionalBlurSoftware::StdDeviationXY()
 {
   float sigmaX = mBlurDirection == BLUR_DIRECTION_X ? mStdDeviation : 0;
   float sigmaY = mBlurDirection == BLUR_DIRECTION_Y ? mStdDeviation : 0;
   return Size(sigmaX, sigmaY);
@@ -2892,24 +3128,31 @@ FilterNodeCropSoftware::SetAttribute(uin
                                      const Rect &aSourceRect)
 {
   MOZ_ASSERT(aIndex == ATT_CROP_RECT);
   Rect srcRect = aSourceRect;
   srcRect.Round();
   if (!srcRect.ToIntRect(&mCropRect)) {
     mCropRect = IntRect();
   }
+  Invalidate();
 }
 
 already_AddRefed<DataSourceSurface>
 FilterNodeCropSoftware::Render(const IntRect& aRect)
 {
   return GetInputDataSourceSurface(IN_CROP_IN, aRect.Intersect(mCropRect));
 }
 
+void
+FilterNodeCropSoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  RequestInputRect(IN_CROP_IN, aRect.Intersect(mCropRect));
+}
+
 IntRect
 FilterNodeCropSoftware::GetOutputRectInRect(const IntRect& aRect)
 {
   return GetInputRectInRect(IN_CROP_IN, aRect).Intersect(mCropRect);
 }
 
 int32_t
 FilterNodePremultiplySoftware::InputIndex(uint32_t aInputEnumIndex)
@@ -2923,16 +3166,22 @@ FilterNodePremultiplySoftware::InputInde
 already_AddRefed<DataSourceSurface>
 FilterNodePremultiplySoftware::Render(const IntRect& aRect)
 {
   RefPtr<DataSourceSurface> input =
     GetInputDataSourceSurface(IN_PREMULTIPLY_IN, aRect);
   return input ? Premultiply(input) : nullptr;
 }
 
+void
+FilterNodePremultiplySoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  RequestInputRect(IN_PREMULTIPLY_IN, aRect);
+}
+
 IntRect
 FilterNodePremultiplySoftware::GetOutputRectInRect(const IntRect& aRect)
 {
   return GetInputRectInRect(IN_PREMULTIPLY_IN, aRect);
 }
 
 int32_t
 FilterNodeUnpremultiplySoftware::InputIndex(uint32_t aInputEnumIndex)
@@ -2946,16 +3195,22 @@ FilterNodeUnpremultiplySoftware::InputIn
 already_AddRefed<DataSourceSurface>
 FilterNodeUnpremultiplySoftware::Render(const IntRect& aRect)
 {
   RefPtr<DataSourceSurface> input =
     GetInputDataSourceSurface(IN_UNPREMULTIPLY_IN, aRect);
   return input ? Unpremultiply(input) : nullptr;
 }
 
+void
+FilterNodeUnpremultiplySoftware::RequestFromInputsForRect(const IntRect &aRect)
+{
+  RequestInputRect(IN_UNPREMULTIPLY_IN, aRect);
+}
+
 IntRect
 FilterNodeUnpremultiplySoftware::GetOutputRectInRect(const IntRect& aRect)
 {
   return GetInputRectInRect(IN_UNPREMULTIPLY_IN, aRect);
 }
 
 bool
 PointLightSoftware::SetAttribute(uint32_t aIndex, const Point3D &aPoint)
@@ -3056,57 +3311,62 @@ FilterNodeLightingSoftware<LightType, Li
   }
 }
 
 template<typename LightType, typename LightingType>
 void
 FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(uint32_t aIndex, const Point3D &aPoint)
 {
   if (mLight.SetAttribute(aIndex, aPoint)) {
+    Invalidate();
     return;
   }
   MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute point");
 }
 
 template<typename LightType, typename LightingType>
 void
 FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(uint32_t aIndex, Float aValue)
 {
   if (mLight.SetAttribute(aIndex, aValue) ||
       mLighting.SetAttribute(aIndex, aValue)) {
+    Invalidate();
     return;
   }
   switch (aIndex) {
     case ATT_LIGHTING_SURFACE_SCALE:
       mSurfaceScale = std::fpclassify(aValue) == FP_SUBNORMAL ? 0.0 : aValue;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute float");
   }
+  Invalidate();
 }
 
 template<typename LightType, typename LightingType>
 void
 FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(uint32_t aIndex, const Size &aKernelUnitLength)
 {
   switch (aIndex) {
     case ATT_LIGHTING_KERNEL_UNIT_LENGTH:
       mKernelUnitLength = aKernelUnitLength;
       break;
     default:
       MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute size");
   }
+  Invalidate();
 }
 
 template<typename LightType, typename LightingType>
 void
 FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(uint32_t aIndex, const Color &aColor)
 {
   MOZ_ASSERT(aIndex == ATT_LIGHTING_COLOR);
   mColor = aColor;
+  Invalidate();
 }
 
 template<typename LightType, typename LightingType>
 IntRect
 FilterNodeLightingSoftware<LightType, LightingType>::GetOutputRectInRect(const IntRect& aRect)
 {
   return aRect;
 }
@@ -3227,16 +3487,26 @@ FilterNodeLightingSoftware<LightType, Li
 {
   if (mKernelUnitLength.width == floor(mKernelUnitLength.width) &&
       mKernelUnitLength.height == floor(mKernelUnitLength.height)) {
     return DoRender(aRect, (int32_t)mKernelUnitLength.width, (int32_t)mKernelUnitLength.height);
   }
   return DoRender(aRect, mKernelUnitLength.width, mKernelUnitLength.height);
 }
 
+template<typename LightType, typename LightingType>
+void
+FilterNodeLightingSoftware<LightType, LightingType>::RequestFromInputsForRect(const IntRect &aRect)
+{
+  IntRect srcRect = aRect;
+  srcRect.Inflate(ceil(mKernelUnitLength.width),
+                  ceil(mKernelUnitLength.height));
+  RequestInputRect(IN_LIGHTING_IN, srcRect);
+}
+
 template<typename LightType, typename LightingType> template<typename CoordType>
 already_AddRefed<DataSourceSurface>
 FilterNodeLightingSoftware<LightType, LightingType>::DoRender(const IntRect& aRect,
                                                               CoordType aKernelUnitLengthX,
                                                               CoordType aKernelUnitLengthY)
 {
   MOZ_ASSERT(aKernelUnitLengthX > 0, "aKernelUnitLengthX can be a negative or zero value");
   MOZ_ASSERT(aKernelUnitLengthY > 0, "aKernelUnitLengthY can be a negative or zero value");
--- a/gfx/2d/FilterNodeSoftware.h
+++ b/gfx/2d/FilterNodeSoftware.h
@@ -15,39 +15,59 @@ namespace mozilla {
 namespace gfx {
 
 class DataSourceSurface;
 class DrawTarget;
 struct DrawOptions;
 class FilterNodeSoftware;
 
 /**
+ * Can be attached to FilterNodeSoftware instances using
+ * AddInvalidationListener. FilterInvalidated is called whenever the output of
+ * the observed filter may have changed; that is, whenever cached GetOutput()
+ * results (and results derived from them) need to discarded.
+ */
+class FilterInvalidationListener
+{
+public:
+  virtual void FilterInvalidated(FilterNodeSoftware* aFilter) = 0;
+};
+
+/**
  * This is the base class for the software (i.e. pure CPU, non-accelerated)
  * FilterNode implementation. The software implementation is backend-agnostic,
  * so it can be used as a fallback for all DrawTarget implementations.
  */
-class FilterNodeSoftware : public FilterNode
+class FilterNodeSoftware : public FilterNode,
+                           public FilterInvalidationListener
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeSoftware, override)
+  FilterNodeSoftware();
   virtual ~FilterNodeSoftware();
 
   // Factory method, intended to be called from DrawTarget*::CreateFilter.
   static already_AddRefed<FilterNode> Create(FilterType aType);
 
   // Draw the filter, intended to be called by DrawTarget*::DrawFilter.
   void Draw(DrawTarget* aDrawTarget, const Rect &aSourceRect,
             const Point &aDestPoint, const DrawOptions &aOptions);
 
   virtual FilterBackend GetBackendType() override { return FILTER_BACKEND_SOFTWARE; }
   virtual void SetInput(uint32_t aIndex, SourceSurface *aSurface) override;
   virtual void SetInput(uint32_t aIndex, FilterNode *aFilter) override;
 
   virtual const char* GetName() { return "Unknown"; }
 
+  virtual void AddInvalidationListener(FilterInvalidationListener* aListener);
+  virtual void RemoveInvalidationListener(FilterInvalidationListener* aListener);
+
+  // FilterInvalidationListener implementation
+  virtual void FilterInvalidated(FilterNodeSoftware* aFilter) override;
+
 protected:
 
   // The following methods are intended to be overriden by subclasses.
 
   /**
    * Translates a *FilterInputs enum value into an index for the
    * mInputFilters / mInputSurfaces arrays. Returns -1 for invalid inputs.
    * If somebody calls SetInput(enumValue, input) with an enumValue for which
@@ -71,16 +91,23 @@ protected:
    * May return nullptr in error conditions or for an empty aRect.
    * Implementations are not required to allocate a new surface and may even
    * pass through input surfaces unchanged.
    * Callers need to treat the returned surface as immutable.
    */
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) = 0;
 
   /**
+   * Call RequestRect (see below) on any input filters with the desired input
+   * rect, so that the input filter knows what to cache the next time it
+   * renders.
+   */
+  virtual void RequestFromInputsForRect(const IntRect &aRect) {}
+
+  /**
    * This method provides a caching default implementation but can be overriden
    * by subclasses that don't want to cache their output. Those classes should
    * call Render(aRect) directly from here.
    */
   virtual already_AddRefed<DataSourceSurface> GetOutput(const IntRect &aRect);
 
   // The following methods are non-virtual helper methods.
 
@@ -123,36 +150,81 @@ protected:
 
   /**
    * Returns the intersection of the input filter's or surface's output rect
    * with aInRect.
    */
   IntRect GetInputRectInRect(uint32_t aInputEnumIndex, const IntRect& aInRect);
 
   /**
+   * Calls RequestRect on the specified input, if it's a filter.
+   */
+  void RequestInputRect(uint32_t aInputEnumIndex, const IntRect& aRect);
+
+  /**
    * Returns the number of set input filters or surfaces. Needed for filters
    * which can have an arbitrary number of inputs.
    */
   size_t NumberOfSetInputs();
 
   /**
+   * Discard the cached surface that was stored in the GetOutput default
+   * implementation. Needs to be called whenever attributes or inputs are set
+   * that might change the result of a Render() call.
+   */
+  void Invalidate();
+
+  /**
+   * Called in order to let this filter know what to cache during the next
+   * GetOutput call. Expected to call RequestRect on this filter's input
+   * filters.
+   */
+  void RequestRect(const IntRect &aRect);
+
+  /**
    * Set input filter and clear input surface for this input index, or set
    * input surface and clear input filter. One of aSurface and aFilter should
    * be null.
    */
   void SetInput(uint32_t aIndex, SourceSurface *aSurface,
                 FilterNodeSoftware *aFilter);
 
 protected:
   /**
    * mInputSurfaces / mInputFilters: For each input index, either a surface or
    * a filter is set, and the other is null.
    */
   std::vector<RefPtr<SourceSurface> > mInputSurfaces;
   std::vector<RefPtr<FilterNodeSoftware> > mInputFilters;
+
+  /**
+   * Weak pointers to our invalidation listeners, i.e. to those filters who
+   * have this filter as an input. Invalidation listeners are required to
+   * unsubscribe themselves from us when they let go of their reference to us.
+   * This ensures that the pointers in this array are never stale.
+   */
+  std::vector<FilterInvalidationListener*> mInvalidationListeners;
+
+  /**
+   * Lock guarding mRequestedRect, mCachedRect, and mCachedOutput. All uses
+   * of those members must aquire this lock.
+   */
+  Mutex mCacheMutex;
+
+  /**
+   * Stores the rect which we want to render and cache on the next call to
+   * GetOutput.
+   */
+  IntRect mRequestedRect;
+
+  /**
+   * Stores our cached output.
+   */
+  IntRect mCachedRect;
+  RefPtr<DataSourceSurface> mCachedOutput;
 };
 
 // Subclasses for specific filters.
 
 class FilterNodeTransformSoftware : public FilterNodeSoftware
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeTransformSoftware, override)
@@ -161,16 +233,17 @@ public:
   using FilterNodeSoftware::SetAttribute;
   virtual void SetAttribute(uint32_t aIndex, uint32_t aGraphicsFilter) override;
   virtual void SetAttribute(uint32_t aIndex, const Matrix &aMatrix) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
   IntRect SourceRectForOutputRect(const IntRect &aRect);
 
 private:
   Matrix mMatrix;
   SamplingFilter mSamplingFilter;
 };
 
 class FilterNodeBlendSoftware : public FilterNodeSoftware
@@ -181,16 +254,17 @@ public:
   virtual const char* GetName() override { return "Blend"; }
   using FilterNodeSoftware::SetAttribute;
   virtual void SetAttribute(uint32_t aIndex, uint32_t aBlendMode) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 
 private:
   BlendMode mBlendMode;
 };
 
 class FilterNodeMorphologySoftware : public FilterNodeSoftware
 {
 public:
@@ -200,16 +274,17 @@ public:
   using FilterNodeSoftware::SetAttribute;
   virtual void SetAttribute(uint32_t aIndex, const IntSize &aRadii) override;
   virtual void SetAttribute(uint32_t aIndex, uint32_t aOperator) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 
 private:
   IntSize mRadii;
   MorphologyOperator mOperator;
 };
 
 class FilterNodeColorMatrixSoftware : public FilterNodeSoftware
 {
@@ -219,16 +294,17 @@ public:
   using FilterNodeSoftware::SetAttribute;
   virtual void SetAttribute(uint32_t aIndex, const Matrix5x4 &aMatrix) override;
   virtual void SetAttribute(uint32_t aIndex, uint32_t aAlphaMode) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 
 private:
   Matrix5x4 mMatrix;
   AlphaMode mAlphaMode;
 };
 
 class FilterNodeFloodSoftware : public FilterNodeSoftware
 {
@@ -254,16 +330,17 @@ public:
   virtual const char* GetName() override { return "Tile"; }
   using FilterNodeSoftware::SetAttribute;
   virtual void SetAttribute(uint32_t aIndex, const IntRect &aSourceRect) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 
 private:
   IntRect mSourceRect;
 };
 
 /**
  * Baseclass for the four different component transfer filters.
  */
@@ -275,16 +352,17 @@ public:
 
   using FilterNodeSoftware::SetAttribute;
   virtual void SetAttribute(uint32_t aIndex, bool aDisable) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
   virtual void GenerateLookupTable(ptrdiff_t aComponent, uint8_t aTables[4][256],
                                    bool aDisabled);
   virtual void FillLookupTable(ptrdiff_t aComponent, uint8_t aTable[256]) = 0;
 
   bool mDisableR;
   bool mDisableG;
   bool mDisableB;
   bool mDisableA;
@@ -399,16 +477,17 @@ public:
   virtual void SetAttribute(uint32_t aIndex, const IntPoint &aTarget) override;
   virtual void SetAttribute(uint32_t aIndex, uint32_t aEdgeMode) override;
   virtual void SetAttribute(uint32_t aIndex, bool aPreserveAlpha) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 
 private:
   template<typename CoordType>
   already_AddRefed<DataSourceSurface> DoRender(const IntRect& aRect,
                                            CoordType aKernelUnitLengthX,
                                            CoordType aKernelUnitLengthY);
 
   IntRect InflatedSourceRect(const IntRect &aDestRect);
@@ -434,16 +513,17 @@ public:
   using FilterNodeSoftware::SetAttribute;
   virtual void SetAttribute(uint32_t aIndex, Float aScale) override;
   virtual void SetAttribute(uint32_t aIndex, uint32_t aValue) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 
 private:
   IntRect InflatedSourceOrDestRect(const IntRect &aDestOrSourceRect);
 
   Float mScale;
   ColorChannel mChannelX;
   ColorChannel mChannelY;
 };
@@ -482,16 +562,17 @@ public:
   virtual const char* GetName() override { return "ArithmeticCombine"; }
   using FilterNodeSoftware::SetAttribute;
   virtual void SetAttribute(uint32_t aIndex, const Float* aFloat, uint32_t aSize) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 
 private:
   Float mK1;
   Float mK2;
   Float mK3;
   Float mK4;
 };
 
@@ -503,32 +584,34 @@ public:
   virtual const char* GetName() override { return "Composite"; }
   using FilterNodeSoftware::SetAttribute;
   virtual void SetAttribute(uint32_t aIndex, uint32_t aOperator) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 
 private:
   CompositeOperator mOperator;
 };
 
 // Base class for FilterNodeGaussianBlurSoftware and
 // FilterNodeDirectionalBlurSoftware.
 class FilterNodeBlurXYSoftware : public FilterNodeSoftware
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeBlurXYSoftware, override)
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
   IntRect InflatedSourceOrDestRect(const IntRect &aDestRect);
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 
   // Implemented by subclasses.
   virtual Size StdDeviationXY() = 0;
 };
 
 class FilterNodeGaussianBlurSoftware : public FilterNodeBlurXYSoftware
 {
 public:
@@ -570,41 +653,44 @@ public:
   virtual const char* GetName() override { return "Crop"; }
   using FilterNodeSoftware::SetAttribute;
   virtual void SetAttribute(uint32_t aIndex, const Rect &aSourceRect) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 
 private:
   IntRect mCropRect;
 };
 
 class FilterNodePremultiplySoftware : public FilterNodeSoftware
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodePremultiplySoftware, override)
   virtual const char* GetName() override { return "Premultiply"; }
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 };
 
 class FilterNodeUnpremultiplySoftware : public FilterNodeSoftware
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(FilterNodeUnpremultiplySoftware, override)
   virtual const char* GetName() override { return "Unpremultiply"; }
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 };
 
 template<typename LightType, typename LightingType>
 class FilterNodeLightingSoftware : public FilterNodeSoftware
 {
 public:
 #if defined(MOZILLA_INTERNAL_API) && (defined(DEBUG) || defined(FORCE_BUILD_REFCNT_LOGGING))
   // Helpers for refcounted
@@ -618,16 +704,17 @@ public:
   virtual void SetAttribute(uint32_t aIndex, const Size &) override;
   virtual void SetAttribute(uint32_t aIndex, const Point3D &) override;
   virtual void SetAttribute(uint32_t aIndex, const Color &) override;
 
 protected:
   virtual already_AddRefed<DataSourceSurface> Render(const IntRect& aRect) override;
   virtual IntRect GetOutputRectInRect(const IntRect& aRect) override;
   virtual int32_t InputIndex(uint32_t aInputEnumIndex) override;
+  virtual void RequestFromInputsForRect(const IntRect &aRect) override;
 
 private:
   template<typename CoordType>
   already_AddRefed<DataSourceSurface> DoRender(const IntRect& aRect,
                                            CoordType aKernelUnitLengthX,
                                            CoordType aKernelUnitLengthY);
 
   Mutex mLock;
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -600,22 +600,21 @@ WebRenderDebugPrefChangeCallback(const c
   if (Preferences::GetBool(WR_DEBUG_PREF suffix, false)) {        \
     flags |= (bit);                                             \
   }
 
   // TODO: It would be nice to get the bit patterns directly from the rust code.
   GFX_WEBRENDER_DEBUG(".profiler",           1 << 0)
   GFX_WEBRENDER_DEBUG(".render-targets",     1 << 1)
   GFX_WEBRENDER_DEBUG(".texture-cache",      1 << 2)
-  GFX_WEBRENDER_DEBUG(".alpha-primitives",   1 << 3)
-  GFX_WEBRENDER_DEBUG(".gpu-time-queries",   1 << 4)
-  GFX_WEBRENDER_DEBUG(".gpu-sample-queries", 1 << 5)
-  GFX_WEBRENDER_DEBUG(".disable-batching",   1 << 6)
-  GFX_WEBRENDER_DEBUG(".epochs",             1 << 7)
-  GFX_WEBRENDER_DEBUG(".compact-profiler",   1 << 8)
+  GFX_WEBRENDER_DEBUG(".gpu-time-queries",   1 << 3)
+  GFX_WEBRENDER_DEBUG(".gpu-sample-queries", 1 << 4)
+  GFX_WEBRENDER_DEBUG(".disable-batching",   1 << 5)
+  GFX_WEBRENDER_DEBUG(".epochs",             1 << 6)
+  GFX_WEBRENDER_DEBUG(".compact-profiler",   1 << 7)
 #undef GFX_WEBRENDER_DEBUG
 
   gfx::gfxVars::SetWebRenderDebugFlags(flags);
 }
 
 
 #if defined(USE_SKIA)
 static uint32_t GetSkiaGlyphCacheSize()
--- a/js/src/builtin/DataViewObject.cpp
+++ b/js/src/builtin/DataViewObject.cpp
@@ -38,20 +38,18 @@ using namespace js;
 using namespace js::gc;
 
 using mozilla::AssertedCast;
 using JS::CanonicalizeNaN;
 using JS::ToInt32;
 using JS::ToUint32;
 
 static NewObjectKind
-DataViewNewObjectKind(JSContext* cx, uint32_t byteLength, JSObject* proto)
+DataViewNewObjectKind(JSContext* cx)
 {
-    if (!proto && byteLength >= TypedArrayObject::SINGLETON_BYTE_LENGTH)
-        return SingletonObject;
     jsbytecode* pc;
     JSScript* script = cx->currentScript(&pc);
     if (script && ObjectGroup::useSingletonForAllocationSite(script, pc, &DataViewObject::class_))
         return SingletonObject;
     return GenericObject;
 }
 
 DataViewObject*
@@ -65,17 +63,17 @@ DataViewObject::create(JSContext* cx, ui
 
     MOZ_ASSERT(byteOffset <= INT32_MAX);
     MOZ_ASSERT(byteLength <= INT32_MAX);
     MOZ_ASSERT(byteOffset + byteLength < UINT32_MAX);
 
     RootedObject proto(cx, protoArg);
     RootedObject obj(cx);
 
-    NewObjectKind newKind = DataViewNewObjectKind(cx, byteLength, proto);
+    NewObjectKind newKind = DataViewNewObjectKind(cx);
     obj = NewObjectWithClassProto(cx, &class_, proto, newKind);
     if (!obj)
         return nullptr;
 
     if (!proto) {
         if (byteLength >= TypedArrayObject::SINGLETON_BYTE_LENGTH) {
             MOZ_ASSERT(obj->isSingleton());
         } else {
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef builtin_ModuleObject_h
 #define builtin_ModuleObject_h
 
 #include "mozilla/Maybe.h"
 
 #include "jsapi.h"
-#include "jsatom.h"
 
 #include "builtin/SelfHostingDefines.h"
 #include "js/GCVector.h"
 #include "js/Id.h"
 #include "js/UniquePtr.h"
 #include "vm/NativeObject.h"
 #include "vm/ProxyObject.h"
 
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -8,17 +8,16 @@
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Move.h"
 
 #include <stdlib.h>
 
 #include "jsarray.h"
-#include "jsatom.h"
 #include "jsobj.h"
 #include "jspubtd.h"
 
 #include "builtin/Reflect.h"
 #include "frontend/Parser.h"
 #include "frontend/TokenStream.h"
 #include "js/CharacterEncoding.h"
 #include "vm/RegExpObject.h"
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -16,17 +16,16 @@
 #include "builtin/SIMD.h"
 #include "gc/Marking.h"
 #include "js/Vector.h"
 #include "vm/GlobalObject.h"
 #include "vm/String.h"
 #include "vm/StringBuffer.h"
 #include "vm/TypedArrayObject.h"
 
-#include "jsatominlines.h"
 #include "jsobjinlines.h"
 
 #include "gc/Nursery-inl.h"
 #include "gc/StoreBuffer-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/Shape-inl.h"
 
 using mozilla::AssertedCast;
--- a/js/src/builtin/intl/CommonFunctions.js
+++ b/js/src/builtin/intl/CommonFunctions.js
@@ -7,21 +7,16 @@
 /**
  * Holder object for encapsulating regexp instances.
  *
  * Regular expression instances should be created after the initialization of
  * self-hosted global.
  */
 var internalIntlRegExps = std_Object_create(null);
 internalIntlRegExps.unicodeLocaleExtensionSequenceRE = null;
-internalIntlRegExps.languageTagRE = null;
-internalIntlRegExps.duplicateVariantRE = null;
-internalIntlRegExps.duplicateSingletonRE = null;
-internalIntlRegExps.isWellFormedCurrencyCodeRE = null;
-internalIntlRegExps.currencyDigitsRE = null;
 
 /**
  * Regular expression matching a "Unicode locale extension sequence", which the
  * specification defines as: "any substring of a language tag that starts with
  * a separator '-' and the singleton 'u' and includes the maximum sequence of
  * following non-singleton subtags and their preceding '-' separators."
  *
  * Alternatively, this may be defined as: the components of a language tag that
@@ -69,244 +64,565 @@ function removeUnicodeExtensions(locale)
             return true;
         var xindex = callFunction(std_String_indexOf, combined, "-x-");
         return xindex > 0 && xindex < uindex;
     }(), "recombination failed to remove all Unicode locale extension sequences");
 
     return combined;
 }
 
+/* eslint-disable complexity */
 /**
- * Regular expression defining BCP 47 language tags.
+ * Parser for BCP 47 language tags.
+ *
+ * Returns null if |locale| can't be parsed as a Language-Tag. If the input is
+ * an irregular grandfathered language tag, the object
+ *
+ *   {
+ *     locale: locale.toLowerCase(),
+ *     grandfathered: true,
+ *   }
+ *
+ * is returned. Otherwise the returned object has the following structure:
+ *
+ *   {
+ *     locale: locale.toLowerCase(),
+ *     language: language subtag without extlang / undefined,
+ *     extlang1: first extlang subtag / undefined,
+ *     extlang2: second extlang subtag / undefined,
+ *     extlang3: third extlang subtag / undefined,
+ *     script: script subtag / undefined,
+ *     region: region subtag / undefined,
+ *     variants: array of variant subtags,
+ *     extensions: array of extension subtags,
+ *     privateuse: privateuse subtag / undefined,
+ *   }
+ *
+ * All language tag subtags are returned in lower-case:
+ *
+ *   var langtag = parseLanguageTag("en-Latn-US");
+ *   assertEq("en-latn-us", langtag.locale);
+ *   assertEq("en", langtag.language);
+ *   assertEq("latn", langtag.script);
+ *   assertEq("us", langtag.region);
  *
  * Spec: RFC 5646 section 2.1.
  */
-function getLanguageTagRE() {
-    if (internalIntlRegExps.languageTagRE)
-        return internalIntlRegExps.languageTagRE;
+function parseLanguageTag(locale) {
+    assert(typeof locale === "string", "locale is a string");
+
+    // Current parse index in |locale|.
+    var index = 0;
+
+    // The three possible token type bits. Expressed as #defines to avoid
+    // extra named lookups in the interpreter/jits.
+    #define NONE  0b00
+    #define ALPHA 0b01
+    #define DIGIT 0b10
+
+    // The current token type, its start index, and its length.
+    var token = 0;
+    var tokenStart = 0;
+    var tokenLength = 0;
+
+    // Constants for code units used below.
+    #define HYPHEN  0x2D
+    #define DIGIT_ZERO 0x30
+    #define DIGIT_NINE 0x39
+    #define UPPER_A 0x41
+    #define UPPER_Z 0x5A
+    #define LOWER_A 0x61
+    #define LOWER_X 0x78
+    #define LOWER_Z 0x7A
+    assert(std_String_fromCharCode(HYPHEN) === "-" &&
+           std_String_fromCharCode(DIGIT_ZERO) === "0" &&
+           std_String_fromCharCode(DIGIT_NINE) === "9" &&
+           std_String_fromCharCode(UPPER_A) === "A" &&
+           std_String_fromCharCode(UPPER_Z) === "Z" &&
+           std_String_fromCharCode(LOWER_A) === "a" &&
+           std_String_fromCharCode(LOWER_X) === "x" &&
+           std_String_fromCharCode(LOWER_Z) === "z",
+           "code unit constants should match the expected characters");
 
-    // RFC 5234 section B.1
-    // ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
-    var ALPHA = "[a-zA-Z]";
-    // DIGIT          =  %x30-39
-    //                        ; 0-9
-    var DIGIT = "[0-9]";
+    // Reads the next token, returns |false| if an illegal character was
+    // found, otherwise returns |true|.
+    function nextToken() {
+        var type = NONE;
+        for (var i = index; i < locale.length; i++) {
+            // RFC 5234 section B.1
+            // ALPHA = %x41-5A / %x61-7A   ; A-Z / a-z
+            // DIGIT = %x30-39             ; 0-9
+            var c = callFunction(std_String_charCodeAt, locale, i);
+            if ((UPPER_A <= c && c <= UPPER_Z) || (LOWER_A <= c && c <= LOWER_Z))
+                type |= ALPHA;
+            else if (DIGIT_ZERO <= c && c <= DIGIT_NINE)
+                type |= DIGIT;
+            else if (c === HYPHEN && i > index && i + 1 < locale.length)
+                break;
+            else
+                return false;
+        }
+
+        token = type;
+        tokenStart = index;
+        tokenLength = i - index;
+        index = i + 1;
+        return true;
+    }
+
+    // Language tags are compared and processed case-insensitively, so
+    // technically it's not necessary to adjust case. But for easier processing,
+    // and because the canonical form for most subtags is lower case, we start
+    // with lower case for all.
+    //
+    // Note that the tokenizer function keeps using the original input string
+    // to properly detect non-ASCII characters. The lower-case string can't be
+    // used to detect those characters, because some non-ASCII characters
+    // lower-case map into ASCII characters, e.g. U+212A (KELVIN SIGN) lower-
+    // case maps to U+006B (LATIN SMALL LETTER K).
+    var localeLowercase = callFunction(std_String_toLowerCase, locale);
+
+    // Returns the code unit of the first character at the current token
+    // position. Always returns the lower-case form of an alphabetical
+    // character.
+    function tokenStartCodeUnitLower() {
+        var c = callFunction(std_String_charCodeAt, localeLowercase, tokenStart);
+        assert((DIGIT_ZERO <= c && c <= DIGIT_NINE) || (LOWER_A <= c && c <= LOWER_Z),
+               "unexpected code unit");
+        return c;
+    }
+
+    // Returns the current token part transformed to lower-case.
+    function tokenStringLower() {
+        return Substring(localeLowercase, tokenStart, tokenLength);
+    }
 
-    // RFC 5646 section 2.1
-    // alphanum      = (ALPHA / DIGIT)     ; letters and numbers
-    var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")";
-    // regular       = "art-lojban"        ; these tags match the 'langtag'
-    //               / "cel-gaulish"       ; production, but their subtags
-    //               / "no-bok"            ; are not extended language
-    //               / "no-nyn"            ; or variant subtags: their meaning
-    //               / "zh-guoyu"          ; is defined by their registration
-    //               / "zh-hakka"          ; and all of these are deprecated
-    //               / "zh-min"            ; in favor of a more modern
-    //               / "zh-min-nan"        ; subtag or sequence of subtags
-    //               / "zh-xiang"
-    var regular = "(?:art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)";
-    // irregular     = "en-GB-oed"         ; irregular tags do not match
-    //                / "i-ami"             ; the 'langtag' production and
-    //                / "i-bnn"             ; would not otherwise be
-    //                / "i-default"         ; considered 'well-formed'
-    //                / "i-enochian"        ; These tags are all valid,
-    //                / "i-hak"             ; but most are deprecated
-    //                / "i-klingon"         ; in favor of more modern
-    //                / "i-lux"             ; subtags or subtag
-    //                / "i-mingo"           ; combination
-    //                / "i-navajo"
-    //                / "i-pwn"
-    //                / "i-tao"
-    //                / "i-tay"
-    //                / "i-tsu"
-    //                / "sgn-BE-FR"
-    //                / "sgn-BE-NL"
-    //                / "sgn-CH-DE"
-    var irregular = "(?:en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)";
-    // grandfathered = irregular           ; non-redundant tags registered
-    //               / regular             ; during the RFC 3066 era
-    var grandfathered = "(?:" + irregular + "|" + regular + ")";
-    // privateuse    = "x" 1*("-" (1*8alphanum))
-    var privateuse = "(?:x(?:-[a-z0-9]{1,8})+)";
-    // singleton     = DIGIT               ; 0 - 9
-    //               / %x41-57             ; A - W
-    //               / %x59-5A             ; Y - Z
-    //               / %x61-77             ; a - w
-    //               / %x79-7A             ; y - z
-    var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])";
-    // extension     = singleton 1*("-" (2*8alphanum))
-    var extension = "(?:" + singleton + "(?:-" + alphanum + "{2,8})+)";
-    // variant       = 5*8alphanum         ; registered variants
-    //               / (DIGIT 3alphanum)
-    var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))";
-    // region        = 2ALPHA              ; ISO 3166-1 code
-    //               / 3DIGIT              ; UN M.49 code
-    var region = "(?:" + ALPHA + "{2}|" + DIGIT + "{3})";
-    // script        = 4ALPHA              ; ISO 15924 code
-    var script = "(?:" + ALPHA + "{4})";
-    // extlang       = 3ALPHA              ; selected ISO 639 codes
-    //                 *2("-" 3ALPHA)      ; permanently reserved
-    var extlang = "(?:" + ALPHA + "{3}(?:-" + ALPHA + "{3}){0,2})";
-    // language      = 2*3ALPHA            ; shortest ISO 639 code
-    //                 ["-" extlang]       ; sometimes followed by
-    //                                     ; extended language subtags
-    //               / 4ALPHA              ; or reserved for future use
-    //               / 5*8ALPHA            ; or registered language subtag
-    var language = "(?:" + ALPHA + "{2,3}(?:-" + extlang + ")?|" + ALPHA + "{4}|" + ALPHA + "{5,8})";
-    // langtag       = language
-    //                 ["-" script]
-    //                 ["-" region]
-    //                 *("-" variant)
-    //                 *("-" extension)
-    //                 ["-" privateuse]
-    var langtag = language + "(?:-" + script + ")?(?:-" + region + ")?(?:-" +
-                  variant + ")*(?:-" + extension + ")*(?:-" + privateuse + ")?";
-    // Language-Tag  = langtag             ; normal language tags
-    //               / privateuse          ; private use tag
-    //               / grandfathered       ; grandfathered tags
-    var languageTag = "^(?:" + langtag + "|" + privateuse + "|" + grandfathered + ")$";
+    // Language-Tag = langtag           ; normal language tags
+    //              / privateuse        ; private use tag
+    //              / grandfathered     ; grandfathered tags
+    if (!nextToken())
+        return null;
+
+    // All Language-Tag productions start with the ALPHA token and contain
+    // less-or-equal to eight characters.
+    if (token !== ALPHA || tokenLength > 8)
+        return null;
+
+    assert(tokenLength > 0, "token length is not zero if type is ALPHA");
+
+    var language, extlang1, extlang2, extlang3, script, region, privateuse;
+    var variants = [];
+    var extensions = [];
+
+    // langtag = language
+    //           ["-" script]
+    //           ["-" region]
+    //           *("-" variant)
+    //           *("-" extension)
+    //           ["-" privateuse]
+    if (tokenLength > 1) {
+        // language = 2*3ALPHA          ; shortest ISO 639 code
+        //            ["-" extlang]     ; sometimes followed by
+        //                              ; extended language subtags
+        //          / 4ALPHA            ; or reserved for future use
+        //          / 5*8ALPHA          ; or registered language subtag
+        if (tokenLength <= 3) {
+            language = tokenStringLower();
+            if (!nextToken())
+                return null;
 
-    // Language tags are case insensitive (RFC 5646 section 2.1.1).
-    return (internalIntlRegExps.languageTagRE = RegExpCreate(languageTag, "i"));
-}
+            // extlang = 3ALPHA         ; selected ISO 639 codes
+            //           *2("-" 3ALPHA) ; permanently reserved
+            if (token === ALPHA && tokenLength === 3) {
+                extlang1 = tokenStringLower();
+                if (!nextToken())
+                    return null;
+                if (token === ALPHA && tokenLength === 3) {
+                    extlang2 = tokenStringLower();
+                    if (!nextToken())
+                        return null;
+                    if (token === ALPHA && tokenLength === 3) {
+                        extlang3 = tokenStringLower();
+                        if (!nextToken())
+                            return null;
+                    }
+                }
+            }
+        } else {
+            assert(4 <= tokenLength && tokenLength <= 8, "reserved/registered language subtags");
+            language = tokenStringLower();
+            if (!nextToken())
+                return null;
+        }
+
+        // script = 4ALPHA              ; ISO 15924 code
+        if (tokenLength === 4 && token === ALPHA) {
+            script = tokenStringLower();
+            if (!nextToken())
+                return null;
+        }
+
+        // region = 2ALPHA              ; ISO 3166-1 code
+        //        / 3DIGIT              ; UN M.49 code
+        if ((tokenLength === 2 && token === ALPHA) || (tokenLength === 3 && token === DIGIT)) {
+            region = tokenStringLower();
+            if (!nextToken())
+                return null;
+        }
+
+        // variant = 5*8alphanum        ; registered variants
+        //         / (DIGIT 3alphanum)
+        //
+        // RFC 5646 section 2.1
+        // alphanum = (ALPHA / DIGIT)   ; letters and numbers
+        while ((5 <= tokenLength && tokenLength <= 8) ||
+               (tokenLength === 4 && tokenStartCodeUnitLower() <= DIGIT_NINE))
+        {
+            assert(!(tokenStartCodeUnitLower() <= DIGIT_NINE) ||
+                   tokenStartCodeUnitLower() >= DIGIT_ZERO,
+                   "token-start-code-unit <= '9' implies token-start-code-unit is in '0'..'9'");
+
+            // Language tags are case insensitive (RFC 5646 section 2.1.1).
+            // All seen variants are compared ignoring case differences by
+            // using the lower-case form. This allows to properly detect and
+            // reject variant repetitions with differing case, e.g.
+            // "en-variant-Variant".
+            var variant = tokenStringLower();
 
-function getDuplicateVariantRE() {
-    if (internalIntlRegExps.duplicateVariantRE)
-        return internalIntlRegExps.duplicateVariantRE;
+            // Reject the language tag if a duplicate variant was found.
+            //
+            // This linear-time verification step means the whole variant
+            // subtag checking is potentially quadratic, but we're okay doing
+            // that because language tags are unlikely to be deliberately
+            // pathological.
+            if (callFunction(ArrayIndexOf, variants, variant) !== -1)
+                return null;
+            _DefineDataProperty(variants, variants.length, variant);
+
+            if (!nextToken())
+                return null;
+        }
+
+        // extension = singleton 1*("-" (2*8alphanum))
+        // singleton = DIGIT            ; 0 - 9
+        //           / %x41-57          ; A - W
+        //           / %x59-5A          ; Y - Z
+        //           / %x61-77          ; a - w
+        //           / %x79-7A          ; y - z
+        var seenSingletons = [];
+        while (tokenLength === 1) {
+            var extensionStart = tokenStart;
+            var singleton = tokenStartCodeUnitLower();
+            if (singleton === LOWER_X)
+                break;
+
+            // Language tags are case insensitive (RFC 5646 section 2.1.1).
+            // Ensure |tokenStartCodeUnitLower()| does not return the code
+            // unit of an upper-case character, so we can properly detect and
+            // reject language tags with different case, e.g. "en-u-foo-U-foo".
+            assert(!(UPPER_A <= singleton && singleton <= UPPER_Z),
+                   "unexpected upper-case code unit");
 
-    // RFC 5234 section B.1
-    // ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
-    var ALPHA = "[a-zA-Z]";
-    // DIGIT          =  %x30-39
-    //                        ; 0-9
-    var DIGIT = "[0-9]";
+            // Reject the input if a duplicate singleton was found.
+            //
+            // Similar to the variant validation step this check is O(n**2),
+            // but given that there are only 35 possible singletons the
+            // quadratic runtime is negligible.
+            if (callFunction(ArrayIndexOf, seenSingletons, singleton) !== -1)
+                return null;
+            _DefineDataProperty(seenSingletons, seenSingletons.length, singleton);
+
+            if (!nextToken())
+                return null;
+
+            if (!(2 <= tokenLength && tokenLength <= 8))
+                return null;
+            do {
+                if (!nextToken())
+                    return null;
+            } while (2 <= tokenLength && tokenLength <= 8);
 
-    // RFC 5646 section 2.1
-    // alphanum      = (ALPHA / DIGIT)     ; letters and numbers
-    var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")";
-    // variant       = 5*8alphanum         ; registered variants
-    //               / (DIGIT 3alphanum)
-    var variant = "(?:" + alphanum + "{5,8}|(?:" + DIGIT + alphanum + "{3}))";
+            var extension = Substring(localeLowercase, extensionStart,
+                                      (tokenStart - 1 - extensionStart));
+            _DefineDataProperty(extensions, extensions.length, extension);
+        }
+    }
+
+    // Either trailing privateuse component of the langtag production or
+    // standalone privateuse tag.
+    //
+    // privateuse = "x" 1*("-" (1*8alphanum))
+    if (tokenLength === 1 && tokenStartCodeUnitLower() === LOWER_X) {
+        var privateuseStart = tokenStart;
+        if (!nextToken())
+            return null;
+
+        if (!(1 <= tokenLength && tokenLength <= 8))
+            return null;
+        do {
+            if (!nextToken())
+                return null;
+        } while (1 <= tokenLength && tokenLength <= 8);
+
+        privateuse = Substring(localeLowercase, privateuseStart,
+                               localeLowercase.length - privateuseStart);
+    }
 
-    // Match a langtag that contains a duplicate variant.
-    var duplicateVariant =
-        // Match everything in a langtag prior to any variants, and maybe some
-        // of the variants as well (which makes this pattern inefficient but
-        // not wrong, for our purposes);
-        "^(?:" + alphanum + "{2,8}-)+" +
-        // a variant, parenthesised so that we can refer back to it later;
-        "(" + variant + ")-" +
-        // zero or more subtags at least two characters long (thus stopping
-        // before extension and privateuse components);
-        "(?:" + alphanum + "{2,8}-)*" +
-        // and the same variant again
-        "\\1" +
-        // ...but not followed by any characters that would turn it into a
-        // different subtag.
-        "(?!" + alphanum + ")";
+    // Return if the complete input was successfully parsed. That means it is
+    // either a langtag or privateuse-only language tag, or it is a regular
+    // grandfathered language tag.
+    if (token === NONE) {
+        return {
+            locale: localeLowercase,
+            language,
+            extlang1,
+            extlang2,
+            extlang3,
+            script,
+            region,
+            variants,
+            extensions,
+            privateuse,
+        };
+    }
+
+    // Before we can compare the lower-case form of locale to the list of
+    // grandfathered language tags, we need to ensure any remaining parts are
+    // alphanum-only ASCII characters. This step is necessary because locale
+    // could include other characters which lower-case map into ASCII
+    // characters.
+    // For example we need to reject "i-ha\u212A" (U+212A KELVIN SIGN) even
+    // though its lower-case form "i-hak" matches a grandfathered language
+    // tag.
+    do {
+        if (!nextToken())
+            return null;
+    } while (token !== NONE);
 
-    // Language tags are case insensitive (RFC 5646 section 2.1.1).  Using
-    // character classes covering both upper- and lower-case characters nearly
-    // addresses this -- but for the possibility of variant repetition with
-    // differing case, e.g. "en-variant-Variant".  Use a case-insensitive
-    // regular expression to address this.  (Note that there's no worry about
-    // case transformation accepting invalid characters here: users have
-    // already verified the string is alphanumeric Latin plus "-".)
-    return (internalIntlRegExps.duplicateVariantRE = RegExpCreate(duplicateVariant, "i"));
+    // grandfathered = irregular        ; non-redundant tags registered
+    //               / regular          ; during the RFC 3066 era
+    switch (localeLowercase) {
+#ifdef DEBUG
+      // regular = "art-lojban"         ; these tags match the 'langtag'
+      //         / "cel-gaulish"        ; production, but their subtags
+      //         / "no-bok"             ; are not extended language
+      //         / "no-nyn"             ; or variant subtags: their meaning
+      //         / "zh-guoyu"           ; is defined by their registration
+      //         / "zh-hakka"           ; and all of these are deprecated
+      //         / "zh-min"             ; in favor of a more modern
+      //         / "zh-min-nan"         ; subtag or sequence of subtags
+      //         / "zh-xiang"
+      case "art-lojban":
+      case "cel-gaulish":
+      case "no-bok":
+      case "no-nyn":
+      case "zh-guoyu":
+      case "zh-hakka":
+      case "zh-min":
+      case "zh-min-nan":
+      case "zh-xiang":
+        assert(false, "regular grandfathered tags should have been matched above");
+#endif /* DEBUG */
+
+      // irregular = "en-GB-oed"        ; irregular tags do not match
+      //           / "i-ami"            ; the 'langtag' production and
+      //           / "i-bnn"            ; would not otherwise be
+      //           / "i-default"        ; considered 'well-formed'
+      //           / "i-enochian"       ; These tags are all valid,
+      //           / "i-hak"            ; but most are deprecated
+      //           / "i-klingon"        ; in favor of more modern
+      //           / "i-lux"            ; subtags or subtag
+      //           / "i-mingo"          ; combination
+      //           / "i-navajo"
+      //           / "i-pwn"
+      //           / "i-tao"
+      //           / "i-tay"
+      //           / "i-tsu"
+      //           / "sgn-BE-FR"
+      //           / "sgn-BE-NL"
+      //           / "sgn-CH-DE"
+      case "en-gb-oed":
+      case "i-ami":
+      case "i-bnn":
+      case "i-default":
+      case "i-enochian":
+      case "i-hak":
+      case "i-klingon":
+      case "i-lux":
+      case "i-mingo":
+      case "i-navajo":
+      case "i-pwn":
+      case "i-tao":
+      case "i-tay":
+      case "i-tsu":
+      case "sgn-be-fr":
+      case "sgn-be-nl":
+      case "sgn-ch-de":
+        return { locale: localeLowercase, grandfathered: true };
+
+      default:
+        return null;
+    }
+
+    #undef NONE
+    #undef ALPHA
+    #undef DIGIT
+    #undef HYPHEN
+    #undef DIGIT_ZERO
+    #undef DIGIT_NINE
+    #undef UPPER_A
+    #undef UPPER_Z
+    #undef LOWER_A
+    #undef LOWER_X
+    #undef LOWER_Z
 }
-
-function getDuplicateSingletonRE() {
-    if (internalIntlRegExps.duplicateSingletonRE)
-        return internalIntlRegExps.duplicateSingletonRE;
-
-    // RFC 5234 section B.1
-    // ALPHA          =  %x41-5A / %x61-7A   ; A-Z / a-z
-    var ALPHA = "[a-zA-Z]";
-    // DIGIT          =  %x30-39
-    //                        ; 0-9
-    var DIGIT = "[0-9]";
-
-    // RFC 5646 section 2.1
-    // alphanum      = (ALPHA / DIGIT)     ; letters and numbers
-    var alphanum = "(?:" + ALPHA + "|" + DIGIT + ")";
-    // singleton     = DIGIT               ; 0 - 9
-    //               / %x41-57             ; A - W
-    //               / %x59-5A             ; Y - Z
-    //               / %x61-77             ; a - w
-    //               / %x79-7A             ; y - z
-    var singleton = "(?:" + DIGIT + "|[A-WY-Za-wy-z])";
-
-    // Match a langtag that contains a duplicate singleton.
-    var duplicateSingleton =
-        // Match a singleton subtag, parenthesised so that we can refer back to
-        // it later;
-        "-(" + singleton + ")-" +
-        // then zero or more subtags;
-        "(?:" + alphanum + "+-)*" +
-        // and the same singleton again
-        "\\1" +
-        // ...but not followed by any characters that would turn it into a
-        // different subtag.
-        "(?!" + alphanum + ")";
-
-    // Language tags are case insensitive (RFC 5646 section 2.1.1).  Using
-    // character classes covering both upper- and lower-case characters nearly
-    // addresses this -- but for the possibility of singleton repetition with
-    // differing case, e.g. "en-u-foo-U-foo".  Use a case-insensitive regular
-    // expression to address this.  (Note that there's no worry about case
-    // transformation accepting invalid characters here: users have already
-    // verified the string is alphanumeric Latin plus "-".)
-    return (internalIntlRegExps.duplicateSingletonRE = RegExpCreate(duplicateSingleton, "i"));
-}
+/* eslint-enable complexity */
 
 /**
  * Verifies that the given string is a well-formed BCP 47 language tag
  * with no duplicate variant or singleton subtags.
  *
  * Spec: ECMAScript Internationalization API Specification, 6.2.2.
  */
 function IsStructurallyValidLanguageTag(locale) {
-    assert(typeof locale === "string", "IsStructurallyValidLanguageTag");
-    var languageTagRE = getLanguageTagRE();
-    if (!regexp_test_no_statics(languageTagRE, locale))
-        return false;
-
-    // Before checking for duplicate variant or singleton subtags with
-    // regular expressions, we have to get private use subtag sequences
-    // out of the picture.
-    if (callFunction(std_String_startsWith, locale, "x-"))
-        return true;
-    var pos = callFunction(std_String_indexOf, locale, "-x-");
-    if (pos !== -1)
-        locale = callFunction(String_substring, locale, 0, pos);
-
-    // Check for duplicate variant or singleton subtags.
-    var duplicateVariantRE = getDuplicateVariantRE();
-    var duplicateSingletonRE = getDuplicateSingletonRE();
-    return !regexp_test_no_statics(duplicateVariantRE, locale) &&
-           !regexp_test_no_statics(duplicateSingletonRE, locale);
+    return parseLanguageTag(locale) !== null;
 }
 
 /**
- * Joins the array elements in the given range with the supplied separator.
+ * Canonicalizes the given structurally valid BCP 47 language tag, including
+ * regularized case of subtags. For example, the language tag
+ * Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE, where
+ *
+ *     Zh             ; 2*3ALPHA
+ *     -NAN           ; ["-" extlang]
+ *     -haNS          ; ["-" script]
+ *     -bu            ; ["-" region]
+ *     -variant2      ; *("-" variant)
+ *     -Variant1
+ *     -u-ca-chinese  ; *("-" extension)
+ *     -t-Zh-laTN
+ *     -x-PRIVATE     ; ["-" privateuse]
+ *
+ * becomes nan-Hans-mm-variant2-variant1-t-zh-latn-u-ca-chinese-x-private
+ *
+ * Spec: ECMAScript Internationalization API Specification, 6.2.3.
+ * Spec: RFC 5646, section 4.5.
  */
-function ArrayJoinRange(array, separator, from, to = array.length) {
-    assert(typeof separator === "string", "|separator| is a string value");
-    assert(typeof from === "number", "|from| is a number value");
-    assert(typeof to === "number", "|to| is a number value");
-    assert(0 <= from && from <= to && to <= array.length, "|from| and |to| form a valid range");
+function CanonicalizeLanguageTagFromObject(localeObj) {
+    assert(IsObject(localeObj), "CanonicalizeLanguageTagFromObject");
+
+    var {locale} = localeObj;
+    assert(locale === callFunction(std_String_toLowerCase, locale),
+           "expected lower-case form for locale string");
+
+    // Handle mappings for complete tags.
+    if (hasOwn(locale, langTagMappings))
+        return langTagMappings[locale];
+
+    assert(!hasOwn("grandfathered", localeObj),
+           "grandfathered tags should be mapped completely");
+
+    var {
+        language,
+        extlang1,
+        extlang2,
+        extlang3,
+        script,
+        region,
+        variants,
+        extensions,
+        privateuse,
+    } = localeObj;
+
+    // Be careful of a Language-Tag that is entirely privateuse.
+    if (!language) {
+        assert(typeof privateuse === "string", "language or privateuse subtag required");
+        return privateuse;
+    }
+
+    // Replace deprecated language tags with their preferred values.
+    // "in" -> "id"
+    if (hasOwn(language, languageMappings))
+        language = languageMappings[language];
+
+    var canonical = language;
 
-    if (from === to)
-        return "";
+    if (extlang1) {
+        // When an extlang subtag is encountered with its corresponding
+        // primary language tag prefix, replace the combination with the
+        // preferred value -- which MUST be the unadorned extlang subtag.
+        // For example, this entry
+        //
+        //   Type: extlang
+        //   Subtag: nan
+        //   Description: Min Nan Chinese
+        //   Added: 2009-07-29
+        //   Preferred-Value: nan
+        //   Prefix: zh
+        //   Macrolanguage: zh
+        //
+        // is interpreted to say that if a "nan" extlang appears after a "zh"
+        // primary language prefix, the extlang and its prefix must be
+        // replaced by its preferred value, so "zh-nan" must be replaced by
+        // the preferred value "nan". (RFC 5646 section 2.2.2)
+        if (hasOwn(extlang1, extlangMappings) && extlangMappings[extlang1] === language)
+            canonical = extlang1;
+        else
+            canonical += "-" + extlang1;
+    }
+
+    // The second extlang subtag will always be left as is.
+    // (RFC 5646 section 2.2.2)
+    if (extlang2)
+        canonical += "-" + extlang2;
+
+    // The third extlang subtag will always be left as is.
+    // (RFC 5646 section 2.2.2)
+    if (extlang3)
+        canonical += "-" + extlang3;
 
-    var result = array[from];
-    for (var i = from + 1; i < to; i++) {
-        result += separator + array[i];
+    if (script) {
+        // The first character of a script code needs to be capitalized.
+        // "hans" -> "Hans"
+        script = callFunction(std_String_toUpperCase, script[0]) +
+                 Substring(script, 1, script.length - 1);
+
+        // No script replacements are currently present, so append as is.
+        canonical += "-" + script;
     }
-    return result;
+
+    if (region) {
+        // Region codes need to be in upper-case. "bu" -> "BU"
+        region = callFunction(std_String_toUpperCase, region);
+
+        // Replace deprecated subtags with their preferred values.
+        // "BU" -> "MM"
+        if (hasOwn(region, regionMappings))
+            region = regionMappings[region];
+
+        canonical += "-" + region;
+    }
+
+    // No variant replacements are currently present, so append as is.
+    if (variants.length > 0)
+        canonical += "-" + callFunction(std_Array_join, variants, "-");
+
+    if (extensions.length > 0) {
+        // Extension sequences are sorted by their singleton characters.
+        // "u-ca-chinese-t-zh-latn" -> "t-zh-latn-u-ca-chinese"
+        callFunction(ArraySort, extensions);
+
+        canonical += "-" + callFunction(std_Array_join, extensions, "-");
+    }
+
+    // Private use sequences are left as is. "x-private"
+    if (privateuse)
+        canonical += "-" + privateuse;
+
+    return canonical;
 }
 
 /**
  * Canonicalizes the given structurally valid BCP 47 language tag, including
  * regularized case of subtags. For example, the language tag
  * Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE, where
  *
  *     Zh             ; 2*3ALPHA
@@ -320,129 +636,20 @@ function ArrayJoinRange(array, separator
  *     -x-PRIVATE     ; ["-" privateuse]
  *
  * becomes nan-Hans-mm-variant2-variant1-t-zh-latn-u-ca-chinese-x-private
  *
  * Spec: ECMAScript Internationalization API Specification, 6.2.3.
  * Spec: RFC 5646, section 4.5.
  */
 function CanonicalizeLanguageTag(locale) {
-    assert(IsStructurallyValidLanguageTag(locale), "CanonicalizeLanguageTag");
-
-    // The input
-    // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE"
-    // will be used throughout this method to illustrate how it works.
-
-    // Language tags are compared and processed case-insensitively, so
-    // technically it's not necessary to adjust case. But for easier processing,
-    // and because the canonical form for most subtags is lower case, we start
-    // with lower case for all.
-    // "Zh-NAN-haNS-bu-variant2-Variant1-u-ca-chinese-t-Zh-laTN-x-PRIVATE" ->
-    // "zh-nan-hans-bu-variant2-variant1-u-ca-chinese-t-zh-latn-x-private"
-    locale = callFunction(std_String_toLowerCase, locale);
-
-    // Handle mappings for complete tags.
-    if (hasOwn(locale, langTagMappings))
-        return langTagMappings[locale];
-
-    var subtags = StringSplitString(locale, "-");
-    var i = 0;
-
-    // Handle the standard part: All subtags before the first singleton or "x".
-    // "zh-nan-hans-bu-variant2-variant1"
-    while (i < subtags.length) {
-        var subtag = subtags[i];
-
-        // If we reach the start of an extension sequence or private use part,
-        // we're done with this loop. We have to check for i > 0 because for
-        // irregular language tags, such as i-klingon, the single-character
-        // subtag "i" is not the start of an extension sequence.
-        // In the example, we break at "u".
-        if (subtag.length === 1 && (i > 0 || subtag === "x"))
-            break;
+    var localeObj = parseLanguageTag(locale);
+    assert(localeObj !== null, "CanonicalizeLanguageTag");
 
-        if (i !== 0) {
-            if (subtag.length === 4) {
-                // 4-character subtags that are not in initial position are
-                // script codes; their first character needs to be capitalized.
-                // "hans" -> "Hans"
-                subtag = callFunction(std_String_toUpperCase, subtag[0]) +
-                         callFunction(String_substring, subtag, 1);
-            } else if (subtag.length === 2) {
-                // 2-character subtags that are not in initial position are
-                // region codes; they need to be upper case. "bu" -> "BU"
-                subtag = callFunction(std_String_toUpperCase, subtag);
-            }
-        }
-        if (hasOwn(subtag, langSubtagMappings)) {
-            // Replace deprecated subtags with their preferred values.
-            // "BU" -> "MM"
-            // This has to come after we capitalize region codes because
-            // otherwise some language and region codes could be confused.
-            // For example, "in" is an obsolete language code for Indonesian,
-            // but "IN" is the country code for India.
-            // Note that the script generating langSubtagMappings makes sure
-            // that no regular subtag mapping will replace an extlang code.
-            subtag = langSubtagMappings[subtag];
-        } else if (hasOwn(subtag, extlangMappings)) {
-            // Replace deprecated extlang subtags with their preferred values,
-            // and remove the preceding subtag if it's a redundant prefix.
-            // "zh-nan" -> "nan"
-            // Note that the script generating extlangMappings makes sure that
-            // no extlang mapping will replace a normal language code.
-            // The preferred value for all current deprecated extlang subtags
-            // is equal to the extlang subtag, so we only need remove the
-            // redundant prefix to get the preferred value.
-            if (i === 1 && extlangMappings[subtag] === subtags[0]) {
-                callFunction(std_Array_shift, subtags);
-                i--;
-            }
-        }
-        subtags[i] = subtag;
-        i++;
-    }
-
-    // Directly return when the language tag doesn't contain any extension or
-    // private use sub-tags.
-    if (i === subtags.length)
-        return callFunction(std_Array_join, subtags, "-");
-
-    var normal = ArrayJoinRange(subtags, "-", 0, i);
-
-    // Extension sequences are sorted by their singleton characters.
-    // "u-ca-chinese-t-zh-latn" -> "t-zh-latn-u-ca-chinese"
-    var extensions = [];
-    while (i < subtags.length && subtags[i] !== "x") {
-        var extensionStart = i;
-        i++;
-        while (i < subtags.length && subtags[i].length > 1)
-            i++;
-        var extension = ArrayJoinRange(subtags, "-", extensionStart, i);
-        _DefineDataProperty(extensions, extensions.length, extension);
-    }
-    callFunction(ArraySort, extensions);
-
-    // Private use sequences are left as is. "x-private"
-    var privateUse = "";
-    if (i < subtags.length)
-        privateUse = ArrayJoinRange(subtags, "-", i);
-
-    // Put everything back together.
-    var canonical = normal;
-    if (extensions.length > 0)
-        canonical += "-" + callFunction(std_Array_join, extensions, "-");
-    if (privateUse.length > 0) {
-        // Be careful of a Language-Tag that is entirely privateuse.
-        if (canonical.length > 0)
-            canonical += "-" + privateUse;
-        else
-            canonical = privateUse;
-    }
-
-    return canonical;
+    return CanonicalizeLanguageTagFromObject(localeObj);
 }
 
 /**
  * Returns true if the input contains only ASCII alphabetical characters.
  */
 function IsASCIIAlphaString(s) {
     assert(typeof s === "string", "IsASCIIAlphaString");
 
@@ -475,28 +682,29 @@ function ValidateAndCanonicalizeLanguage
         // The language subtag is canonicalized to lower case.
         locale = callFunction(std_String_toLowerCase, locale);
 
         // langTagMappings doesn't contain any 2*3ALPHA keys, so we don't need
         // to check for possible replacements in this map.
         assert(!hasOwn(locale, langTagMappings), "langTagMappings contains no 2*3ALPHA mappings");
 
         // Replace deprecated subtags with their preferred values.
-        locale = hasOwn(locale, langSubtagMappings)
-                 ? langSubtagMappings[locale]
+        locale = hasOwn(locale, languageMappings)
+                 ? languageMappings[locale]
                  : locale;
         assert(locale === CanonicalizeLanguageTag(locale), "expected same canonicalization");
 
         return locale;
     }
 
-    if (!IsStructurallyValidLanguageTag(locale))
+    var localeObj = parseLanguageTag(locale);
+    if (localeObj === null)
         ThrowRangeError(JSMSG_INVALID_LANGUAGE_TAG, locale);
 
-    return CanonicalizeLanguageTag(locale);
+    return CanonicalizeLanguageTagFromObject(localeObj);
 }
 
 function localeContainsNoUnicodeExtensions(locale) {
     // No "-u-", no possible Unicode extension.
     if (callFunction(std_String_indexOf, locale, "-u-") === -1)
         return true;
 
     // "-u-" within privateuse also isn't one.
@@ -551,21 +759,21 @@ var localeCache = {
  */
 function DefaultLocaleIgnoringAvailableLocales() {
     const runtimeDefaultLocale = RuntimeDefaultLocale();
     if (runtimeDefaultLocale === localeCandidateCache.runtimeDefaultLocale)
         return localeCandidateCache.candidateDefaultLocale;
 
     // If we didn't get a cache hit, compute the candidate default locale and
     // cache it.  Fall back on the last-ditch locale when necessary.
-    var candidate;
-    if (!IsStructurallyValidLanguageTag(runtimeDefaultLocale)) {
+    var candidate = parseLanguageTag(runtimeDefaultLocale);
+    if (candidate === null) {
         candidate = lastDitchLocale();
     } else {
-        candidate = CanonicalizeLanguageTag(runtimeDefaultLocale);
+        candidate = CanonicalizeLanguageTagFromObject(candidate);
 
         // The default locale must be in [[availableLocales]], and that list
         // must not contain any locales with Unicode extension sequences, so
         // remove any present in the candidate.
         candidate = removeUnicodeExtensions(candidate);
 
         if (hasOwn(candidate, oldStyleLanguageTagMappings))
             candidate = oldStyleLanguageTagMappings[candidate];
@@ -653,36 +861,31 @@ function addSpecialMissingLanguageTags(a
 /**
  * Canonicalizes a locale list.
  *
  * Spec: ECMAScript Internationalization API Specification, 9.2.1.
  */
 function CanonicalizeLocaleList(locales) {
     if (locales === undefined)
         return [];
-    if (typeof locales === "string") {
-        if (!IsStructurallyValidLanguageTag(locales))
-            ThrowRangeError(JSMSG_INVALID_LANGUAGE_TAG, locales);
-        return [CanonicalizeLanguageTag(locales)];
-    }
+    if (typeof locales === "string")
+        return [ValidateAndCanonicalizeLanguageTag(locales)];
     var seen = [];
     var O = ToObject(locales);
     var len = ToLength(O.length);
     var k = 0;
     while (k < len) {
         // Don't call ToString(k) - SpiderMonkey is faster with integers.
         var kPresent = HasProperty(O, k);
         if (kPresent) {
             var kValue = O[k];
             if (!(typeof kValue === "string" || IsObject(kValue)))
                 ThrowTypeError(JSMSG_INVALID_LOCALES_ELEMENT);
             var tag = ToString(kValue);
-            if (!IsStructurallyValidLanguageTag(tag))
-                ThrowRangeError(JSMSG_INVALID_LANGUAGE_TAG, tag);
-            tag = CanonicalizeLanguageTag(tag);
+            tag = ValidateAndCanonicalizeLanguageTag(tag);
             if (callFunction(ArrayIndexOf, seen, tag) === -1)
                 _DefineDataProperty(seen, seen.length, tag);
         }
         k++;
     }
     return seen;
 }
 
--- a/js/src/builtin/intl/LangTagMappingsGenerated.js
+++ b/js/src/builtin/intl/LangTagMappingsGenerated.js
@@ -53,26 +53,20 @@ var langTagMappings = {
     "zh-hakka": "hak",
     "zh-min": "zh-min",
     "zh-min-nan": "nan",
     "zh-wuu": "wuu",
     "zh-xiang": "hsn",
     "zh-yue": "yue",
 };
 
-// Mappings from non-extlang subtags to preferred values.
+// Mappings from language subtags to preferred values.
 // Derived from IANA Language Subtag Registry, file date 2018-01-25.
 // https://www.iana.org/assignments/language-subtag-registry
-var langSubtagMappings = {
-    "BU": "MM",
-    "DD": "DE",
-    "FX": "FR",
-    "TP": "TL",
-    "YD": "YE",
-    "ZR": "CD",
+var languageMappings = {
     "aam": "aas",
     "adp": "dz",
     "aue": "ktz",
     "ayx": "nun",
     "bgm": "bcg",
     "bjd": "drl",
     "ccq": "rki",
     "cjr": "mom",
@@ -142,16 +136,28 @@ var langSubtagMappings = {
     "xsj": "suj",
     "ybd": "rki",
     "yma": "lrr",
     "ymt": "mtm",
     "yos": "zom",
     "yuu": "yug",
 };
 
+// Mappings from region subtags to preferred values.
+// Derived from IANA Language Subtag Registry, file date 2018-01-25.
+// https://www.iana.org/assignments/language-subtag-registry
+var regionMappings = {
+    "BU": "MM",
+    "DD": "DE",
+    "FX": "FR",
+    "TP": "TL",
+    "YD": "YE",
+    "ZR": "CD",
+};
+
 // Mappings from extlang subtags to preferred values.
 // All current deprecated extlang subtags have the form `<prefix>-<extlang>`
 // and their preferred value is exactly equal to `<extlang>`. So each key in
 // extlangMappings acts both as the extlang subtag and its preferred value.
 // Derived from IANA Language Subtag Registry, file date 2018-01-25.
 // https://www.iana.org/assignments/language-subtag-registry
 var extlangMappings = {
     "aao": "ar",
--- a/js/src/builtin/intl/NumberFormat.js
+++ b/js/src/builtin/intl/NumberFormat.js
@@ -186,27 +186,20 @@ function toASCIIUpperCase(s) {
     return result;
 }
 
 /**
  * Verifies that the given string is a well-formed ISO 4217 currency code.
  *
  * Spec: ECMAScript Internationalization API Specification, 6.3.1.
  */
-function getIsWellFormedCurrencyCodeRE() {
-    return internalIntlRegExps.isWellFormedCurrencyCodeRE ||
-           (internalIntlRegExps.isWellFormedCurrencyCodeRE = RegExpCreate("[^A-Z]"));
-}
+function IsWellFormedCurrencyCode(currency) {
+    assert(typeof currency === "string", "currency is a string value");
 
-function IsWellFormedCurrencyCode(currency) {
-    var c = ToString(currency);
-    var normalized = toASCIIUpperCase(c);
-    if (normalized.length !== 3)
-        return false;
-    return !regexp_test_no_statics(getIsWellFormedCurrencyCodeRE(), normalized);
+    return currency.length === 3 && IsASCIIAlphaString(currency);
 }
 
 /**
  * Initializes an object as a NumberFormat.
  *
  * This method is complicated a moderate bit by its implementing initialization
  * as a *lazy* concept.  Everything that must happen now, does -- but we defer
  * all the work we can until the object is actually used as a NumberFormat.
@@ -334,23 +327,20 @@ function InitializeNumberFormat(numberFo
     return numberFormat;
 }
 
 /**
  * Returns the number of decimal digits to be used for the given currency.
  *
  * Spec: ECMAScript Internationalization API Specification, 11.1.1.
  */
-function getCurrencyDigitsRE() {
-    return internalIntlRegExps.currencyDigitsRE ||
-           (internalIntlRegExps.currencyDigitsRE = RegExpCreate("^[A-Z]{3}$"));
-}
 function CurrencyDigits(currency) {
-    assert(typeof currency === "string", "CurrencyDigits");
-    assert(regexp_test_no_statics(getCurrencyDigitsRE(), currency), "CurrencyDigits");
+    assert(typeof currency === "string", "currency is a string value");
+    assert(IsWellFormedCurrencyCode(currency), "currency is well-formed");
+    assert(currency == toASCIIUpperCase(currency), "currency is all upper-case");
 
     if (hasOwn(currency, currencyDigits))
         return currencyDigits[currency];
     return 2;
 }
 
 /**
  * Returns the subset of the given locale list for which this locale list has a
--- a/js/src/builtin/intl/make_intl_data.py
+++ b/js/src/builtin/intl/make_intl_data.py
@@ -71,29 +71,30 @@ def readRegistryRecord(registry):
 
 
 def readRegistry(registry):
     """ Reads IANA Language Subtag Registry and extracts information for Intl.js.
 
         Information extracted:
         - langTagMappings: mappings from complete language tags to preferred
           complete language tags
-        - langSubtagMappings: mappings from subtags to preferred subtags
+        - languageMappings: mappings from language subtags to preferred subtags
+        - regionMappings: mappings from region subtags to preferred subtags
         - extlangMappings: mappings from extlang subtags to preferred subtags,
           with prefix to be removed
-        Returns these three mappings as dictionaries, along with the registry's
+        Returns these four mappings as dictionaries, along with the registry's
         file date.
 
-        We also check that mappings for language subtags don't affect extlang
-        subtags and vice versa, so that CanonicalizeLanguageTag doesn't have
-        to separate them for processing. Region codes are separated by case,
-        and script codes by length, so they're unproblematic.
+        We also check that extlang mappings don't generate preferred values
+        which in turn are subject to language subtag mappings, so that
+        CanonicalizeLanguageTag can process subtags sequentially.
     """
     langTagMappings = {}
-    langSubtagMappings = {}
+    languageMappings = {}
+    regionMappings = {}
     extlangMappings = {}
     languageSubtags = set()
     extlangSubtags = set()
 
     for record in readRegistryRecord(registry):
         if "File-Date" in record:
             fileDate = record["File-Date"]
             continue
@@ -109,65 +110,84 @@ def readRegistry(registry):
                 langTagMappings[tag.lower()] = record["Preferred-Value"]
             else:
                 langTagMappings[tag.lower()] = tag
         elif record["Type"] == "redundant":
             # For langTagMappings, keys must be in lower case; values in
             # the case used in the registry.
             if "Preferred-Value" in record:
                 langTagMappings[record["Tag"].lower()] = record["Preferred-Value"]
-        elif record["Type"] in ("language", "script", "region", "variant"):
-            # For langSubtagMappings, keys and values must be in the case used
+        elif record["Type"] == "language":
+            # For languageMappings, keys and values must be in the case used
             # in the registry.
             subtag = record["Subtag"]
-            if record["Type"] == "language":
-                languageSubtags.add(subtag)
+            languageSubtags.add(subtag)
+            if "Preferred-Value" in record:
+                # The 'Prefix' field is not allowed for language records.
+                # https://tools.ietf.org/html/rfc5646#section-3.1.2
+                assert "Prefix" not in record, "language subtags can't have a prefix"
+                languageMappings[subtag] = record["Preferred-Value"]
+        elif record["Type"] == "region":
+            # For regionMappings, keys and values must be in the case used in
+            # the registry.
+            subtag = record["Subtag"]
+            if "Preferred-Value" in record:
+                # The 'Prefix' field is not allowed for region records.
+                # https://tools.ietf.org/html/rfc5646#section-3.1.2
+                assert "Prefix" not in record, "region subtags can't have a prefix"
+                regionMappings[subtag] = record["Preferred-Value"]
+        elif record["Type"] == "script":
+            if "Preferred-Value" in record:
+                # The registry currently doesn't contain mappings for scripts.
+                raise Exception("Unexpected mapping for script subtags")
+        elif record["Type"] == "variant":
+            subtag = record["Subtag"]
             if "Preferred-Value" in record:
                 if subtag == "heploc":
                     # The entry for heploc is unique in its complexity; handle
                     # it as special case below.
                     continue
-                if "Prefix" in record:
-                    # This might indicate another heploc-like complex case.
-                    raise Exception("Please evaluate: subtag mapping with prefix value.")
-                langSubtagMappings[subtag] = record["Preferred-Value"]
+                # The registry currently doesn't contain mappings for variants,
+                # except for heploc which is already handled above.
+                raise Exception("Unexpected mapping for variant subtags")
         elif record["Type"] == "extlang":
             # For extlangMappings, keys must be in the case used in the
             # registry; values are records with the preferred value and the
             # prefix to be removed.
             subtag = record["Subtag"]
             extlangSubtags.add(subtag)
             if "Preferred-Value" in record:
                 preferred = record["Preferred-Value"]
+                # The 'Preferred-Value' and 'Subtag' fields MUST be identical.
+                # https://tools.ietf.org/html/rfc5646#section-2.2.2
+                assert preferred == subtag, "{0} = {1}".format(preferred, subtag)
                 prefix = record["Prefix"]
                 extlangMappings[subtag] = {"preferred": preferred, "prefix": prefix}
         else:
             # No other types are allowed by
             # https://tools.ietf.org/html/rfc5646#section-3.1.3
             assert False, "Unrecognized Type: {0}".format(record["Type"])
 
     # Check that mappings for language subtags and extlang subtags don't affect
     # each other.
-    for lang in languageSubtags:
-        if lang in extlangMappings and extlangMappings[lang]["preferred"] != lang:
-            raise Exception("Conflict: lang with extlang mapping: " + lang)
     for extlang in extlangSubtags:
-        if extlang in langSubtagMappings:
+        if extlang in languageMappings:
             raise Exception("Conflict: extlang with lang mapping: " + extlang)
 
     # Special case for heploc.
     langTagMappings["ja-latn-hepburn-heploc"] = "ja-Latn-alalc97"
 
-    # ValidateAndCanonicalizeLanguageTag in Intl.js expects langTagMappings
-    # contains no 2*3ALPHA.
+    # ValidateAndCanonicalizeLanguageTag in CommonFunctions.js expects
+    # langTagMappings contains no 2*3ALPHA.
     assert all(len(lang) > 3 for lang in langTagMappings.iterkeys())
 
     return {"fileDate": fileDate,
             "langTagMappings": langTagMappings,
-            "langSubtagMappings": langSubtagMappings,
+            "languageMappings": languageMappings,
+            "regionMappings": regionMappings,
             "extlangMappings": extlangMappings}
 
 
 def writeMappingsVar(intlData, dict, name, description, fileDate, url):
     """ Writes a variable definition with a mapping table to file intlData.
 
         Writes the contents of dictionary dict to file intlData with the given
         variable name and a comment with description, fileDate, and URL.
@@ -189,22 +209,25 @@ def writeMappingsVar(intlData, dict, nam
             prefix = dict[key]["prefix"]
             if key != preferred:
                 raise Exception("Expected '{0}' matches preferred locale '{1}'".format(key, preferred))
             value = '"{0}"'.format(prefix)
         intlData.write('    "{0}": {1},\n'.format(key, value))
     intlData.write("};\n")
 
 
-def writeLanguageTagData(intlData, fileDate, url, langTagMappings, langSubtagMappings, extlangMappings):
+def writeLanguageTagData(intlData, fileDate, url, langTagMappings, languageMappings,
+                         regionMappings, extlangMappings):
     """ Writes the language tag data to the Intl data file. """
     writeMappingsVar(intlData, langTagMappings, "langTagMappings",
                      "Mappings from complete tags to preferred values.", fileDate, url)
-    writeMappingsVar(intlData, langSubtagMappings, "langSubtagMappings",
-                     "Mappings from non-extlang subtags to preferred values.", fileDate, url)
+    writeMappingsVar(intlData, languageMappings, "languageMappings",
+                     "Mappings from language subtags to preferred values.", fileDate, url)
+    writeMappingsVar(intlData, regionMappings, "regionMappings",
+                     "Mappings from region subtags to preferred values.", fileDate, url)
     writeMappingsVar(intlData, extlangMappings, "extlangMappings",
                      ["Mappings from extlang subtags to preferred values.",
                       "All current deprecated extlang subtags have the form `<prefix>-<extlang>`",
                       "and their preferred value is exactly equal to `<extlang>`. So each key in",
                       "extlangMappings acts both as the extlang subtag and its preferred value."],
                      fileDate, url)
 
 def updateLangTags(args):
@@ -230,23 +253,25 @@ def updateLangTags(args):
         registry.write(text)
         registry.seek(0)
 
     print("Processing IANA Language Subtag Registry...")
     with closing(registry) as reg:
         data = readRegistry(reg)
     fileDate = data["fileDate"]
     langTagMappings = data["langTagMappings"]
-    langSubtagMappings = data["langSubtagMappings"]
+    languageMappings = data["languageMappings"]
+    regionMappings = data["regionMappings"]
     extlangMappings = data["extlangMappings"]
 
     print("Writing Intl data...")
     with codecs.open(out, "w", encoding="utf-8") as intlData:
         intlData.write("// Generated by make_intl_data.py. DO NOT EDIT.\n")
-        writeLanguageTagData(intlData, fileDate, url, langTagMappings, langSubtagMappings, extlangMappings)
+        writeLanguageTagData(intlData, fileDate, url, langTagMappings, languageMappings,
+                             regionMappings, extlangMappings)
 
 def flines(filepath, encoding="utf-8"):
     """ Open filepath and iterate over its content. """
     with io.open(filepath, mode="r", encoding=encoding) as f:
         for line in f:
             yield line
 
 class Zone:
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -41,17 +41,16 @@
 #include "builtin/TypedObject.h"
 #include "ctypes/Library.h"
 #include "gc/FreeOp.h"
 #include "gc/Policy.h"
 #include "gc/Zone.h"
 #include "jit/AtomicOperations.h"
 #include "js/Vector.h"
 
-#include "jsatominlines.h"
 #include "jsobjinlines.h"
 
 using namespace std;
 
 using JS::AutoCheckCannotGC;
 
 namespace js {
 namespace ctypes {
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -14,34 +14,32 @@
 #include "mozilla/DebugOnly.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/PodOperations.h"
 
 #include <string.h>
 
 #include "jsapi.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsfun.h"
 #include "jsnum.h"
 #include "jsopcode.h"
 #include "jsscript.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "ds/Nestable.h"
 #include "frontend/Parser.h"
 #include "frontend/TokenStream.h"
 #include "vm/Debugger.h"
 #include "vm/GeneratorObject.h"
 #include "vm/Stack.h"
 #include "wasm/AsmJS.h"
 
-#include "jsatominlines.h"
 #include "jsscriptinlines.h"
 
 #include "frontend/ParseNode-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 using namespace js::gc;
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -19,33 +19,31 @@
 
 #include "frontend/Parser.h"
 
 #include "mozilla/Range.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/TypeTraits.h"
 
 #include "jsapi.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsfun.h"
 #include "jsopcode.h"
 #include "jsscript.h"
 #include "jstypes.h"
 
 #include "builtin/ModuleObject.h"
 #include "builtin/SelfHostingDefines.h"
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/FoldConstants.h"
 #include "frontend/TokenStream.h"
 #include "irregexp/RegExpParser.h"
 #include "vm/RegExpObject.h"
 #include "wasm/AsmJS.h"
 
-#include "jsatominlines.h"
 #include "jsscriptinlines.h"
 
 #include "frontend/ParseContext-inl.h"
 #include "frontend/ParseNode-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 
 using namespace js;
 using namespace js::gc;
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -2,17 +2,16 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef frontend_SharedContext_h
 #define frontend_SharedContext_h
 
-#include "jsatom.h"
 #include "jsopcode.h"
 #include "jspubtd.h"
 #include "jsscript.h"
 #include "jstypes.h"
 
 #include "builtin/ModuleObject.h"
 #include "ds/InlineTable.h"
 #include "frontend/ParseNode.h"
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -13,17 +13,16 @@
 #include "mozilla/PodOperations.h"
 #include "mozilla/ScopeExit.h"
 
 #include <ctype.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
 
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jsexn.h"
 #include "jsnum.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/Parser.h"
 #include "frontend/ReservedWords.h"
--- a/js/src/gc/AllocKind.h
+++ b/js/src/gc/AllocKind.h
@@ -13,16 +13,17 @@
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/EnumeratedRange.h"
 
 #include <stdint.h>
 
 #include "js/TraceKind.h"
+#include "js/Utility.h"
 
 namespace js {
 namespace gc {
 
 // The GC allocation kinds.
 //
 // These are defined by macros which enumerate the different allocation kinds
 // and supply the following information:
--- a/js/src/gc/GCInternals.h
+++ b/js/src/gc/GCInternals.h
@@ -30,21 +30,24 @@ void FinishGC(JSContext* cx);
  * heap in order to trace through it...
  */
 class MOZ_RAII AutoTraceSession
 {
   public:
     explicit AutoTraceSession(JSRuntime* rt, JS::HeapState state = JS::HeapState::Tracing);
     ~AutoTraceSession();
 
-    // Threads with an exclusive context can hit refillFreeList while holding
-    // the exclusive access lock. To avoid deadlocking when we try to acquire
-    // this lock during GC and the other thread is waiting, make sure we hold
-    // the exclusive access lock during GC sessions.
-    AutoLockForExclusiveAccess lock;
+    // Constructing an AutoTraceSession takes the exclusive access lock, but GC
+    // may release it during a trace session if we're not collecting the atoms
+    // zone.
+    mozilla::Maybe<AutoLockForExclusiveAccess> maybeLock;
+
+    AutoLockForExclusiveAccess& lock() {
+        return maybeLock.ref();
+    }
 
   protected:
     JSRuntime* runtime;
 
   private:
     AutoTraceSession(const AutoTraceSession&) = delete;
     void operator=(const AutoTraceSession&) = delete;
 
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -6,28 +6,27 @@
 
 #ifndef gc_GCRuntime_h
 #define gc_GCRuntime_h
 
 #include "mozilla/Atomics.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/Maybe.h"
 
-#include "jsatom.h"
-
 #include "gc/ArenaList.h"
 #include "gc/AtomMarking.h"
 #include "gc/GCHelperState.h"
 #include "gc/GCMarker.h"
 #include "gc/GCParallelTask.h"
 #include "gc/Nursery.h"
 #include "gc/Statistics.h"
 #include "gc/StoreBuffer.h"
 #include "js/GCAnnotations.h"
 #include "js/UniquePtr.h"
+#include "vm/AtomsTable.h"
 
 namespace js {
 
 class AutoLockGC;
 class AutoLockGCBgAlloc;
 class AutoLockHelperThreadState;
 class VerifyPreTracer;
 
@@ -791,18 +790,18 @@ class GCRuntime
 
     void runDebugGC();
     void notifyRootsRemoved();
 
     enum TraceOrMarkRuntime {
         TraceRuntime,
         MarkRuntime
     };
-    void traceRuntime(JSTracer* trc, AutoLockForExclusiveAccess& lock);
-    void traceRuntimeForMinorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock);
+    void traceRuntime(JSTracer* trc, AutoTraceSession& session);
+    void traceRuntimeForMinorGC(JSTracer* trc, AutoTraceSession& session);
 
     void shrinkBuffers();
     void onOutOfMallocMemory();
     void onOutOfMallocMemory(const AutoLockGC& lock);
 
 #ifdef JS_GC_ZEAL
     const void* addressOfZealModeBits() { return &zealModeBits; }
     void getZealBits(uint32_t* zealBits, uint32_t* frequency, uint32_t* nextScheduled);
@@ -1053,113 +1052,113 @@ class GCRuntime
 
     friend class BackgroundAllocTask;
     bool wantBackgroundAllocation(const AutoLockGC& lock) const;
     void startBackgroundAllocTaskIfIdle();
 
     void requestMajorGC(JS::gcreason::Reason reason);
     SliceBudget defaultBudget(JS::gcreason::Reason reason, int64_t millis);
     IncrementalResult budgetIncrementalGC(bool nonincrementalByAPI, JS::gcreason::Reason reason,
-                                          SliceBudget& budget, AutoLockForExclusiveAccess& lock);
-    IncrementalResult resetIncrementalGC(AbortReason reason, AutoLockForExclusiveAccess& lock);
+                                          SliceBudget& budget, AutoTraceSession& session);
+    IncrementalResult resetIncrementalGC(AbortReason reason, AutoTraceSession& session);
 
     // Assert if the system state is such that we should never
     // receive a request to do GC work.
     void checkCanCallAPI();
 
     // Check if the system state is such that GC has been supressed
     // or otherwise delayed.
     MOZ_MUST_USE bool checkIfGCAllowedInCurrentState(JS::gcreason::Reason reason);
 
     gcstats::ZoneGCStats scanZonesBeforeGC();
     void collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::Reason reason) JS_HAZ_GC_CALL;
     MOZ_MUST_USE IncrementalResult gcCycle(bool nonincrementalByAPI, SliceBudget& budget,
                                            JS::gcreason::Reason reason);
     bool shouldRepeatForDeadZone(JS::gcreason::Reason reason);
     void incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason reason,
-                                 AutoLockForExclusiveAccess& lock);
+                                 AutoTraceSession& session);
 
     friend class AutoCallGCCallbacks;
     void maybeCallBeginCallback();
     void maybeCallEndCallback();
 
     void pushZealSelectedObjects();
-    void purgeRuntime(AutoLockForExclusiveAccess& lock);
-    MOZ_MUST_USE bool beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock);
+    void purgeRuntime();
+    MOZ_MUST_USE bool beginMarkPhase(JS::gcreason::Reason reason, AutoTraceSession& session);
     bool prepareZonesForCollection(JS::gcreason::Reason reason, bool* isFullOut,
                                    AutoLockForExclusiveAccess& lock);
     bool shouldPreserveJITCode(JSCompartment* comp, int64_t currentTime,
                                JS::gcreason::Reason reason, bool canAllocateMoreCode);
-    void traceRuntimeForMajorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock);
+    void traceRuntimeForMajorGC(JSTracer* trc, AutoTraceSession& session);
     void traceRuntimeAtoms(JSTracer* trc, AutoLockForExclusiveAccess& lock);
     void traceRuntimeCommon(JSTracer* trc, TraceOrMarkRuntime traceOrMark,
-                            AutoLockForExclusiveAccess& lock);
+                            AutoTraceSession& session);
     void maybeDoCycleCollection();
     void markCompartments();
     IncrementalProgress drainMarkStack(SliceBudget& sliceBudget, gcstats::PhaseKind phase);
     template <class CompartmentIterT> void markWeakReferences(gcstats::PhaseKind phase);
     void markWeakReferencesInCurrentGroup(gcstats::PhaseKind phase);
     template <class ZoneIterT, class CompartmentIterT> void markGrayReferences(gcstats::PhaseKind phase);
     void markBufferedGrayRoots(JS::Zone* zone);
     void markGrayReferencesInCurrentGroup(gcstats::PhaseKind phase);
     void markAllWeakReferences(gcstats::PhaseKind phase);
     void markAllGrayReferences(gcstats::PhaseKind phase);
 
-    void beginSweepPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock);
-    void groupZonesForSweeping(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock);
+    void beginSweepPhase(JS::gcreason::Reason reason, AutoTraceSession& session);
+    void groupZonesForSweeping(JS::gcreason::Reason reason);
     MOZ_MUST_USE bool findInterZoneEdges();
     void getNextSweepGroup();
     IncrementalProgress endMarkingSweepGroup(FreeOp* fop, SliceBudget& budget);
     IncrementalProgress beginSweepingSweepGroup(FreeOp* fop, SliceBudget& budget);
 #ifdef JS_GC_ZEAL
     IncrementalProgress maybeYieldForSweepingZeal(FreeOp* fop, SliceBudget& budget);
 #endif
     bool shouldReleaseObservedTypes();
     void sweepDebuggerOnMainThread(FreeOp* fop);
     void sweepJitDataOnMainThread(FreeOp* fop);
     IncrementalProgress endSweepingSweepGroup(FreeOp* fop, SliceBudget& budget);
-    IncrementalProgress performSweepActions(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
+    IncrementalProgress performSweepActions(SliceBudget& sliceBudget);
     IncrementalProgress sweepTypeInformation(FreeOp* fop, SliceBudget& budget, Zone* zone);
     IncrementalProgress releaseSweptEmptyArenas(FreeOp* fop, SliceBudget& budget, Zone* zone);
     void startSweepingAtomsTable();
     IncrementalProgress sweepAtomsTable(FreeOp* fop, SliceBudget& budget);
     IncrementalProgress sweepWeakCaches(FreeOp* fop, SliceBudget& budget);
     IncrementalProgress finalizeAllocKind(FreeOp* fop, SliceBudget& budget, Zone* zone,
                                           AllocKind kind);
     IncrementalProgress sweepShapeTree(FreeOp* fop, SliceBudget& budget, Zone* zone);
-    void endSweepPhase(bool lastGC, AutoLockForExclusiveAccess& lock);
+    void endSweepPhase(bool lastGC);
     bool allCCVisibleZonesWereCollected() const;
     void sweepZones(FreeOp* fop, ZoneGroup* group, bool lastGC);
     void sweepZoneGroups(FreeOp* fop, bool destroyingRuntime);
     void decommitAllWithoutUnlocking(const AutoLockGC& lock);
     void startDecommit();
     void queueZonesForBackgroundSweep(ZoneList& zones);
     void sweepBackgroundThings(ZoneList& zones, LifoAlloc& freeBlocks);
     void assertBackgroundSweepingFinished();
     bool shouldCompact();
     void beginCompactPhase();
     IncrementalProgress compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
-                                     AutoLockForExclusiveAccess& lock);
+                                     AutoTraceSession& session);
     void endCompactPhase(JS::gcreason::Reason reason);
     void sweepTypesAfterCompacting(Zone* zone);
     void sweepZoneAfterCompacting(Zone* zone);
     MOZ_MUST_USE bool relocateArenas(Zone* zone, JS::gcreason::Reason reason,
                                      Arena*& relocatedListOut, SliceBudget& sliceBudget);
     void updateTypeDescrObjects(MovingTracer* trc, Zone* zone);
     void updateCellPointers(MovingTracer* trc, Zone* zone, AllocKinds kinds, size_t bgTaskCount);
     void updateAllCellPointers(MovingTracer* trc, Zone* zone);
-    void updateZonePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess& lock);
-    void updateRuntimePointersToRelocatedCells(AutoLockForExclusiveAccess& lock);
+    void updateZonePointersToRelocatedCells(Zone* zone);
+    void updateRuntimePointersToRelocatedCells(AutoTraceSession& session);
     void protectAndHoldArenas(Arena* arenaList);
     void unprotectHeldRelocatedArenas();
     void releaseRelocatedArenas(Arena* arenaList);
     void releaseRelocatedArenasWithoutUnlocking(Arena* arenaList, const AutoLockGC& lock);
     void finishCollection(JS::gcreason::Reason reason);
 
-    void computeNonIncrementalMarkingForValidation(AutoLockForExclusiveAccess& lock);
+    void computeNonIncrementalMarkingForValidation(AutoTraceSession& session);
     void validateIncrementalMarking();
     void finishMarkingValidation();
 
 #ifdef DEBUG
     void checkForCompartmentMismatches();
 #endif
 
     void callFinalizeCallbacks(FreeOp* fop, JSFinalizeStatus status) const;
--- a/js/src/gc/Iteration-inl.h
+++ b/js/src/gc/Iteration-inl.h
@@ -52,22 +52,24 @@ class GrayObjectIter : public ZoneCellIt
 
     JSObject* get() const { return ZoneCellIter<js::gc::TenuredCell>::get<JSObject>(); }
     operator JSObject*() const { return get(); }
     JSObject* operator ->() const { return get(); }
 };
 
 class GCZonesIter
 {
-  private:
     ZonesIter zone;
 
   public:
     explicit GCZonesIter(JSRuntime* rt, ZoneSelector selector = WithAtoms) : zone(rt, selector) {
         MOZ_ASSERT(JS::CurrentThreadIsHeapBusy());
+        MOZ_ASSERT_IF(rt->gc.atomsZone->isCollectingFromAnyThread(),
+                      !rt->hasHelperThreadZones());
+
         if (!zone->isCollectingFromAnyThread())
             next();
     }
 
     bool done() const { return zone.done(); }
 
     void next() {
         MOZ_ASSERT(!done());
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -804,17 +804,17 @@ js::Nursery::doCollection(JS::gcreason::
     sb.traceWholeCells(mover);
     endProfile(ProfileKey::TraceWholeCells);
 
     startProfile(ProfileKey::TraceGenericEntries);
     sb.traceGenericEntries(&mover);
     endProfile(ProfileKey::TraceGenericEntries);
 
     startProfile(ProfileKey::MarkRuntime);
-    rt->gc.traceRuntimeForMinorGC(&mover, session.lock);
+    rt->gc.traceRuntimeForMinorGC(&mover, session);
     endProfile(ProfileKey::MarkRuntime);
 
     startProfile(ProfileKey::MarkDebugger);
     {
         gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::MARK_ROOTS);
         Debugger::traceAllForMovingGC(&mover);
     }
     endProfile(ProfileKey::MarkDebugger);
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -252,82 +252,84 @@ PropertyDescriptor::trace(JSTracer* trc)
     if ((attrs & JSPROP_SETTER) && setter) {
         JSObject* tmp = JS_FUNC_TO_DATA_PTR(JSObject*, setter);
         TraceRoot(trc, &tmp, "Descriptor::set");
         setter = JS_DATA_TO_FUNC_PTR(JSSetterOp, tmp);
     }
 }
 
 void
-js::gc::GCRuntime::traceRuntimeForMajorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock)
+js::gc::GCRuntime::traceRuntimeForMajorGC(JSTracer* trc, AutoTraceSession& session)
 {
+    MOZ_ASSERT_IF(atomsZone->isCollecting(), session.maybeLock.isSome());
+
     // FinishRoots will have asserted that every root that we do not expect
     // is gone, so we can simply skip traceRuntime here.
     if (rt->isBeingDestroyed())
         return;
 
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS);
-    if (rt->atomsCompartment(lock)->zone()->isCollecting())
-        traceRuntimeAtoms(trc, lock);
+    if (atomsZone->isCollecting())
+        traceRuntimeAtoms(trc, session.lock());
     JSCompartment::traceIncomingCrossCompartmentEdgesForZoneGC(trc);
-    traceRuntimeCommon(trc, MarkRuntime, lock);
+    traceRuntimeCommon(trc, MarkRuntime, session);
 }
 
 void
-js::gc::GCRuntime::traceRuntimeForMinorGC(JSTracer* trc, AutoLockForExclusiveAccess& lock)
+js::gc::GCRuntime::traceRuntimeForMinorGC(JSTracer* trc, AutoTraceSession& session)
 {
     // Note that we *must* trace the runtime during the SHUTDOWN_GC's minor GC
     // despite having called FinishRoots already. This is because FinishRoots
     // does not clear the crossCompartmentWrapper map. It cannot do this
     // because Proxy's trace for CrossCompartmentWrappers asserts presence in
     // the map. And we can reach its trace function despite having finished the
     // roots via the edges stored by the pre-barrier verifier when we finish
     // the verifier for the last time.
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS);
 
     jit::JitRuntime::TraceJitcodeGlobalTableForMinorGC(trc);
 
-    traceRuntimeCommon(trc, TraceRuntime, lock);
+    traceRuntimeCommon(trc, TraceRuntime, session);
 }
 
 void
 js::TraceRuntime(JSTracer* trc)
 {
     MOZ_ASSERT(!trc->isMarkingTracer());
 
     JSRuntime* rt = trc->runtime();
     EvictAllNurseries(rt);
     AutoPrepareForTracing prep(TlsContext.get(), WithAtoms);
     gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
-    rt->gc.traceRuntime(trc, prep.session().lock);
+    rt->gc.traceRuntime(trc, prep.session());
 }
 
 void
-js::gc::GCRuntime::traceRuntime(JSTracer* trc, AutoLockForExclusiveAccess& lock)
+js::gc::GCRuntime::traceRuntime(JSTracer* trc, AutoTraceSession& session)
 {
     MOZ_ASSERT(!rt->isBeingDestroyed());
 
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_ROOTS);
-    traceRuntimeAtoms(trc, lock);
-    traceRuntimeCommon(trc, TraceRuntime, lock);
+    traceRuntimeAtoms(trc, session.lock());
+    traceRuntimeCommon(trc, TraceRuntime, session);
 }
 
 void
 js::gc::GCRuntime::traceRuntimeAtoms(JSTracer* trc, AutoLockForExclusiveAccess& lock)
 {
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_RUNTIME_DATA);
     TracePermanentAtoms(trc);
     TraceAtoms(trc, lock);
     TraceWellKnownSymbols(trc);
     jit::JitRuntime::Trace(trc, lock);
 }
 
 void
 js::gc::GCRuntime::traceRuntimeCommon(JSTracer* trc, TraceOrMarkRuntime traceOrMark,
-                                      AutoLockForExclusiveAccess& lock)
+                                      AutoTraceSession& session)
 {
     MOZ_ASSERT(!TlsContext.get()->suppressGC);
 
     {
         gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_STACK);
 
         JSContext* cx = TlsContext.get();
         for (const CooperatingContext& target : rt->cooperatingContexts()) {
@@ -362,17 +364,17 @@ js::gc::GCRuntime::traceRuntimeCommon(JS
         target.context()->trace(trc);
 
     // Trace all compartment roots, but not the compartment itself; it is
     // traced via the parent pointer if traceRoots actually traces anything.
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
         c->traceRoots(trc, traceOrMark);
 
     // Trace helper thread roots.
-    HelperThreadState().trace(trc);
+    HelperThreadState().trace(trc, session);
 
     // Trace the embedding's black and gray roots.
     if (!JS::CurrentThreadIsHeapMinorCollecting()) {
         gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK_EMBEDDING);
 
         /*
          * The embedding can register additional roots here.
          *
@@ -428,17 +430,17 @@ js::gc::GCRuntime::finishRoots()
     // The nsWrapperCache may not be empty before our shutdown GC, so we have
     // to skip that table when verifying that we are fully unrooted.
     auto prior = grayRootTracer;
     grayRootTracer = Callback<JSTraceDataOp>(nullptr, nullptr);
 
     AssertNoRootsTracer trc(rt, TraceWeakMapKeysValues);
     AutoPrepareForTracing prep(TlsContext.get(), WithAtoms);
     gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
-    traceRuntime(&trc, prep.session().lock);
+    traceRuntime(&trc, prep.session());
 
     // Restore the wrapper tracing so that we leak instead of leaving dangling
     // pointers.
     grayRootTracer = prior;
 #endif // DEBUG
 }
 
 // Append traced things to a buffer on the zone for use later in the GC.
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -216,17 +216,17 @@ gc::GCRuntime::startVerifyPreBarriers()
         goto oom;
 
     /* Create the root node. */
     trc->curnode = MakeNode(trc, nullptr, JS::TraceKind(0));
 
     incrementalState = State::MarkRoots;
 
     /* Make all the roots be edges emanating from the root node. */
-    traceRuntime(trc, prep.session().lock);
+    traceRuntime(trc, prep.session());
 
     VerifyNode* node;
     node = trc->curnode;
     if (trc->edgeptr == trc->term)
         goto oom;
 
     /* For each edge, make a node for it if one doesn't already exist. */
     while ((char*)node < trc->edgeptr) {
@@ -453,17 +453,17 @@ js::gc::GCRuntime::finishVerifier()
 
 #if defined(JSGC_HASH_TABLE_CHECKS) || defined(DEBUG)
 
 class HeapCheckTracerBase : public JS::CallbackTracer
 {
   public:
     explicit HeapCheckTracerBase(JSRuntime* rt, WeakMapTraceKind weakTraceKind);
     bool init();
-    bool traceHeap(AutoLockForExclusiveAccess& lock);
+    bool traceHeap(AutoTraceSession& session);
     virtual void checkCell(Cell* cell) = 0;
 
   protected:
     void dumpCellInfo(Cell* cell);
     void dumpCellPath();
 
     Cell* parentCell() {
         return parentIndex == -1 ? nullptr : stack[parentIndex].thing.asCell();
@@ -534,22 +534,22 @@ HeapCheckTracerBase::onChild(const JS::G
         return;
 
     WorkItem item(thing, contextName(), parentIndex);
     if (!stack.append(item))
         oom = true;
 }
 
 bool
-HeapCheckTracerBase::traceHeap(AutoLockForExclusiveAccess& lock)
+HeapCheckTracerBase::traceHeap(AutoTraceSession& session)
 {
     // The analysis thinks that traceRuntime might GC by calling a GC callback.
     JS::AutoSuppressGCAnalysis nogc;
     if (!rt->isBeingDestroyed())
-        rt->gc.traceRuntime(this, lock);
+        rt->gc.traceRuntime(this, session);
 
     while (!stack.empty() && !oom) {
         WorkItem item = stack.back();
         if (item.processed) {
             stack.popBack();
         } else {
             parentIndex = stack.length() - 1;
             stack.back().processed = true;
@@ -602,17 +602,17 @@ HeapCheckTracerBase::dumpCellPath()
 #endif // defined(JSGC_HASH_TABLE_CHECKS) || defined(DEBUG)
 
 #ifdef JSGC_HASH_TABLE_CHECKS
 
 class CheckHeapTracer final : public HeapCheckTracerBase
 {
   public:
     explicit CheckHeapTracer(JSRuntime* rt);
-    void check(AutoLockForExclusiveAccess& lock);
+    void check(AutoTraceSession& session);
 
   private:
     void checkCell(Cell* cell) override;
 };
 
 CheckHeapTracer::CheckHeapTracer(JSRuntime* rt)
   : HeapCheckTracerBase(rt, TraceWeakMapKeysValues)
 {}
@@ -629,44 +629,44 @@ CheckHeapTracer::checkCell(Cell* cell)
     if (!IsValidGCThingPointer(cell) || !IsGCThingValidAfterMovingGC(cell)) {
         failures++;
         fprintf(stderr, "Bad pointer %p\n", cell);
         dumpCellPath();
     }
 }
 
 void
-CheckHeapTracer::check(AutoLockForExclusiveAccess& lock)
+CheckHeapTracer::check(AutoTraceSession& session)
 {
-    if (!traceHeap(lock))
+    if (!traceHeap(session))
         return;
 
     if (failures)
         fprintf(stderr, "Heap check: %zu failure(s)\n", failures);
     MOZ_RELEASE_ASSERT(failures == 0);
 }
 
 void
 js::gc::CheckHeapAfterGC(JSRuntime* rt)
 {
     AutoTraceSession session(rt, JS::HeapState::Tracing);
     CheckHeapTracer tracer(rt);
     if (tracer.init())
-        tracer.check(session.lock);
+        tracer.check(session);
 }
 
 #endif /* JSGC_HASH_TABLE_CHECKS */
 
 #if defined(JS_GC_ZEAL) || defined(DEBUG)
 
 class CheckGrayMarkingTracer final : public HeapCheckTracerBase
 {
   public:
     explicit CheckGrayMarkingTracer(JSRuntime* rt);
-    bool check(AutoLockForExclusiveAccess& lock);
+    bool check(AutoTraceSession& session);
 
   private:
     void checkCell(Cell* cell) override;
 };
 
 CheckGrayMarkingTracer::CheckGrayMarkingTracer(JSRuntime* rt)
   : HeapCheckTracerBase(rt, DoNotTraceWeakMaps)
 {
@@ -694,19 +694,19 @@ CheckGrayMarkingTracer::checkCell(Cell* 
             fprintf(stderr, "\n");
             DumpObject(cell->as<JSObject>(), stderr);
         }
 #endif
     }
 }
 
 bool
-CheckGrayMarkingTracer::check(AutoLockForExclusiveAccess& lock)
+CheckGrayMarkingTracer::check(AutoTraceSession& session)
 {
-    if (!traceHeap(lock))
+    if (!traceHeap(session))
         return true; // Ignore failure.
 
     return failures == 0;
 }
 
 JS_FRIEND_API(bool)
 js::CheckGrayMarkingState(JSRuntime* rt)
 {
@@ -716,12 +716,12 @@ js::CheckGrayMarkingState(JSRuntime* rt)
         return true;
 
     gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
     AutoTraceSession session(rt, JS::HeapState::Tracing);
     CheckGrayMarkingTracer tracer(rt);
     if (!tracer.init())
         return true; // Ignore failure
 
-    return tracer.check(session.lock);
+    return tracer.check(session);
 }
 
 #endif // defined(JS_GC_ZEAL) || defined(DEBUG)
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -31,17 +31,16 @@ JS::Zone::Zone(JSRuntime* rt, ZoneGroup*
     arenas(rt, group),
     types(this),
     gcWeakMapList_(group),
     compartments_(),
     gcGrayRoots_(group),
     gcWeakRefs_(group),
     weakCaches_(group),
     gcWeakKeys_(group, SystemAllocPolicy(), rt->randomHashCodeScrambler()),
-    gcSweepGroupEdges_(group),
     typeDescrObjects_(group, this),
     regExps(this),
     markedAtoms_(group),
     atomCache_(group),
     externalStringCache_(group),
     functionToStringCache_(group),
     usage(&rt->gc.usage),
     threshold(),
@@ -55,17 +54,17 @@ JS::Zone::Zone(JSRuntime* rt, ZoneGroup*
 #ifdef DEBUG
     gcLastSweepGroupIndex(group, 0),
 #endif
     jitZone_(group, nullptr),
     gcScheduled_(false),
     gcScheduledSaved_(false),
     gcPreserveCode_(group, false),
     keepShapeTables_(group, false),
-    listNext_(group, NotOnList)
+    listNext_(NotOnList)
 {
     /* Ensure that there are no vtables to mess us up here. */
     MOZ_ASSERT(reinterpret_cast<JS::shadow::Zone*>(this) ==
                static_cast<JS::shadow::Zone*>(this));
 
     AutoLockGC lock(rt);
     threshold.updateAfterGC(8192, GC_NORMAL, rt->gc.tunables, rt->gc.schedulingState, lock);
     setGCMaxMallocBytes(rt->gc.tunables.maxMallocBytes(), lock);
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -63,21 +63,21 @@ class ZoneHeapThreshold
     static size_t computeZoneTriggerBytes(double growthFactor, size_t lastBytes,
                                           JSGCInvocationKind gckind,
                                           const GCSchedulingTunables& tunables,
                                           const AutoLockGC& lock);
 };
 
 struct ZoneComponentFinder : public ComponentFinder<JS::Zone, ZoneComponentFinder>
 {
-    ZoneComponentFinder(uintptr_t sl, AutoLockForExclusiveAccess& lock)
-      : ComponentFinder<JS::Zone, ZoneComponentFinder>(sl), lock(lock)
+    ZoneComponentFinder(uintptr_t sl, JS::Zone* maybeAtomsZone)
+      : ComponentFinder<JS::Zone, ZoneComponentFinder>(sl), maybeAtomsZone(maybeAtomsZone)
     {}
 
-    AutoLockForExclusiveAccess& lock;
+    JS::Zone* maybeAtomsZone;
 };
 
 struct UniqueIdGCPolicy {
     static bool needsSweep(Cell** cell, uint64_t* value);
 };
 
 // Maps a Cell* to a unique, 64bit id.
 using UniqueIdMap = GCHashMap<Cell*,
@@ -395,17 +395,17 @@ struct Zone : public JS::shadow::Zone,
   public:
     js::gc::WeakKeyTable& gcWeakKeys() { return gcWeakKeys_.ref(); }
 
   private:
     // A set of edges from this zone to other zones.
     //
     // This is used during GC while calculating sweep groups to record edges
     // that can't be determined by examining this zone by itself.
-    js::ZoneGroupData<ZoneSet> gcSweepGroupEdges_;
+    js::ActiveThreadData<ZoneSet> gcSweepGroupEdges_;
 
   public:
     ZoneSet& gcSweepGroupEdges() { return gcSweepGroupEdges_.ref(); }
 
     // Keep track of all TypeDescr and related objects in this compartment.
     // This is used by the GC to trace them all first when compacting, since the
     // TypedObject trace hook may access these objects.
     //
@@ -716,17 +716,17 @@ struct Zone : public JS::shadow::Zone,
     js::ActiveThreadData<bool> gcScheduled_;
     js::ActiveThreadData<bool> gcScheduledSaved_;
     js::ZoneGroupData<bool> gcPreserveCode_;
     js::ZoneGroupData<bool> keepShapeTables_;
 
     // Allow zones to be linked into a list
     friend class js::gc::ZoneList;
     static Zone * const NotOnList;
-    js::ZoneGroupOrGCTaskData<Zone*> listNext_;
+    js::ActiveThreadOrGCTaskData<Zone*> listNext_;
     bool isOnList() const;
     Zone* nextZone() const;
 
     friend bool js::CurrentThreadCanAccessZone(Zone* zone);
     friend class js::gc::GCRuntime;
 };
 
 } // namespace JS
--- a/js/src/gdb/tests/test-JSString.cpp
+++ b/js/src/gdb/tests/test-JSString.cpp
@@ -1,10 +1,9 @@
 #include "gdb-tests.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 
 // When JSGC_ANALYSIS is #defined, Rooted<JSFlatString*> needs the definition
 // of JSFlatString in order to figure out its ThingRootKind
 #include "vm/String.h"
 
 FRAGMENT(JSString, simple) {
   JS::Rooted<JSString*> empty(cx, JS_NewStringCopyN(cx, nullptr, 0));
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/ion-error-throw.js
@@ -0,0 +1,44 @@
+const options = getJitCompilerOptions();
+
+// These tests need at least baseline to make sense.
+if (!options['baseline.enable'])
+    quit();
+
+const { assertStackTrace, startProfiling, endProfiling, assertEqPreciseStacks } = WasmHelpers;
+
+enableGeckoProfiling();
+
+let { add } = wasmEvalText(`(module
+    (func $add (export "add") (result i32) (param i32) (param i32)
+     get_local 0
+     i32.const 42
+     i32.eq
+     if
+         unreachable
+     end
+
+     get_local 0
+     get_local 1
+     i32.add
+    )
+)`).exports;
+
+const SLOW_ENTRY_STACK = ['', '!>', '0,!>', '!>', ''];
+const FAST_ENTRY_STACK = ['', '>', '0,>', '>', ''];
+
+function main() {
+    for (let i = 0; i < 50; i++) {
+        startProfiling();
+        try {
+            assertEq(add(i, i+1), 2*i+1);
+        } catch (e) {
+            assertEq(i, 42);
+            assertEq(e.message.includes("unreachable"), true);
+            assertStackTrace(e, ['wasm-function[0]', 'main', '']);
+        }
+        let stack = endProfiling();
+        assertEqPreciseStacks(stack, [FAST_ENTRY_STACK, SLOW_ENTRY_STACK]);
+    }
+}
+
+main();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/regress/caller-property.js
@@ -0,0 +1,7 @@
+const { g } = wasmEvalText(`(module (func $f) (export "g" $f))`).exports;
+
+function testCaller() {
+  return g.caller;
+}
+
+assertErrorMessage(testCaller, TypeError, /caller/);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/regress/ion-error-gc-fakeexitframe.js
@@ -0,0 +1,81 @@
+var lfLogBuffer = `
+//corefuzz-dcd-endofdata
+for (var i = 0; gczeal(4,10); g(buffer))
+  assertEq(assignParameterGetElement(42), 17);
+//corefuzz-dcd-endofdata
+//corefuzz-dcd-endofdata
+//corefuzz-dcd-endofdata
+g = newGlobal();
+g.parent = this
+g.eval("Debugger(parent).onExceptionUnwind=(function(){})")
+`;
+lfLogBuffer = lfLogBuffer.split('\n');
+
+gcPreserveCode();
+
+var letext =`(module
+  (type $type0 (func (param i32 i64)))
+  (type $type1 (func (param i32) (result i64)))
+  (type $type2 (func (result i32)))
+  (memory 1)
+  (export "store" $func0)
+  (export "load" $func1)
+  (export "assert_0" $func2)
+  (func $func0 (param $var0 i32) (param $var1 i64)
+    get_local $var0
+    get_local $var1
+    i64.store16 offset=16
+  )
+  (func $func1 (param $var0 i32) (result i64)
+    get_local $var0
+    i64.load16_s offset=16
+  )
+  (func $func2 (result i32)
+    i32.const 65519
+    i64.const -32768
+    call $func0
+    i32.const 1
+  )
+  (data (i32.const 0)
+    "\\00\\01\\02\\03\\04\\05\\06\\07\\08\t\n\\0b\\0c\\0d\\0e\\0f"
+  )
+  (data (i32.const 16)
+    "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff"
+  )
+)`;
+
+var binary = wasmTextToBinary(letext);
+var module = new WebAssembly.Module(binary);
+
+var lfCodeBuffer = "";
+while (true) {
+    var line = lfLogBuffer.shift();
+    if (line == null) {
+        break;
+    } else if (line == "//corefuzz-dcd-endofdata") {
+        processCode(lfCodeBuffer);
+    } else {
+        lfCodeBuffer += line + "\n";
+    }
+}
+
+if (lfCodeBuffer) processCode(lfCodeBuffer);
+
+function processCode(code) {
+    evaluate(code);
+    while (true) {
+        imports = {}
+        try {
+            instance = new WebAssembly.Instance(module, imports);
+            break;
+        } catch (exc) {}
+    }
+    for (let descriptor of WebAssembly.Module.exports(module)) {
+        switch (descriptor.kind) {
+            case "function":
+                try {
+                    print(instance.exports[descriptor.name]())
+                } catch (exc1) {}
+        }
+    }
+}
--- a/js/src/jit/BacktrackingAllocator.cpp
+++ b/js/src/jit/BacktrackingAllocator.cpp
@@ -969,35 +969,47 @@ BacktrackingAllocator::tryMergeBundles(L
     // Move all ranges from bundle1 into bundle0.
     while (LiveRange* range = bundle1->popFirstRange())
         bundle0->addRange(range);
 
     return true;
 }
 
 static inline LDefinition*
-FindReusingDefOrTemp(LNode* ins, LAllocation* alloc)
+FindReusingDefOrTemp(LNode* node, LAllocation* alloc)
 {
+    if (node->isPhi()) {
+        MOZ_ASSERT(node->toPhi()->numDefs() == 1);
+        MOZ_ASSERT(node->toPhi()->getDef(0)->policy() != LDefinition::MUST_REUSE_INPUT);
+        return nullptr;
+    }
+
+    LInstruction* ins = node->toInstruction();
+
     for (size_t i = 0; i < ins->numDefs(); i++) {
         LDefinition* def = ins->getDef(i);
         if (def->policy() == LDefinition::MUST_REUSE_INPUT &&
             ins->getOperand(def->getReusedInput()) == alloc)
+        {
             return def;
+        }
     }
     for (size_t i = 0; i < ins->numTemps(); i++) {
         LDefinition* def = ins->getTemp(i);
         if (def->policy() == LDefinition::MUST_REUSE_INPUT &&
             ins->getOperand(def->getReusedInput()) == alloc)
+        {
             return def;
+        }
     }
     return nullptr;
 }
 
 static inline size_t
-NumReusingDefs(LNode* ins)
+NumReusingDefs(LInstruction* ins)
 {
     size_t num = 0;
     for (size_t i = 0; i < ins->numDefs(); i++) {
         LDefinition* def = ins->getDef(i);
         if (def->policy() == LDefinition::MUST_REUSE_INPUT)
             num++;
     }
     return num;
@@ -2044,17 +2056,17 @@ BacktrackingAllocator::reifyAllocations(
                 if (LDefinition* def = FindReusingDefOrTemp(ins, alloc)) {
                     LiveRange* outputRange = vreg(def).rangeFor(outputOf(ins));
                     LAllocation res = outputRange->bundle()->allocation();
                     LAllocation sourceAlloc = range->bundle()->allocation();
 
                     if (res != *alloc) {
                         if (!this->alloc().ensureBallast())
                             return false;
-                        if (NumReusingDefs(ins) <= 1) {
+                        if (NumReusingDefs(ins->toInstruction()) <= 1) {
                             LMoveGroup* group = getInputMoveGroup(ins->toInstruction());
                             if (!group->addAfter(sourceAlloc, res, reg.type()))
                                 return false;
                         } else {
                             LMoveGroup* group = getFixReuseMoveGroup(ins->toInstruction());
                             if (!group->add(sourceAlloc, res, reg.type()))
                                 return false;
                         }
--- a/js/src/jit/C1Spewer.cpp
+++ b/js/src/jit/C1Spewer.cpp
@@ -85,17 +85,20 @@ DumpLIR(GenericPrinter& out, LNode* ins)
     ins->dump(out);
     out.printf(" <|@\n");
 }
 
 void
 C1Spewer::spewRanges(GenericPrinter& out, BacktrackingAllocator* regalloc, LNode* ins)
 {
     for (size_t k = 0; k < ins->numDefs(); k++) {
-        uint32_t id = ins->getDef(k)->virtualRegister();
+        const LDefinition* def = ins->isPhi()
+            ? ins->toPhi()->getDef(k)
+            : ins->toInstruction()->getDef(k);
+        uint32_t id = def->virtualRegister();
         VirtualRegister* vreg = &regalloc->vregs[id];
 
         for (LiveRange::RegisterLinkIterator iter = vreg->rangesBegin(); iter; iter++) {
             LiveRange* range = LiveRange::get(*iter);
             out.printf("%d object \"", id);
             out.printf("%s", range->bundle()->allocation().toString().get());
             out.printf("\" %d -1", id);
             out.printf(" [%u, %u[", range->from().bits(), range->to().bits());
--- a/js/src/jit/JSONSpewer.cpp
+++ b/js/src/jit/JSONSpewer.cpp
@@ -189,18 +189,22 @@ JSONSpewer::spewLIns(LNode* ins)
     property("id", ins->id());
 
     propertyName("opcode");
     out_.printf("\"");
     ins->dump(out_);
     out_.printf("\"");
 
     beginListProperty("defs");
-    for (size_t i = 0; i < ins->numDefs(); i++)
-        value(ins->getDef(i)->virtualRegister());
+    for (size_t i = 0; i < ins->numDefs(); i++) {
+        if (ins->isPhi())
+            value(ins->toPhi()->getDef(i)->virtualRegister());
+        else
+            value(ins->toInstruction()->getDef(i)->virtualRegister());
+    }
     endList();
 
     endObject();
 }
 
 void
 JSONSpewer::spewLIR(MIRGraph* mir)
 {
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -831,18 +831,17 @@ TraceThisAndArguments(JSTracer* trc, con
     if (!CalleeTokenIsFunction(layout->calleeToken()))
         return;
 
     size_t nargs = layout->numActualArgs();
     size_t nformals = 0;
 
     JSFunction* fun = CalleeTokenToFunction(layout->calleeToken());
     if (frame.type() != JitFrame_JSJitToWasm &&
-        !frame.isExitFrameLayout<LazyLinkExitFrameLayout>() &&
-        !frame.isExitFrameLayout<InterpreterStubExitFrameLayout>() &&
+        !frame.isExitFrameLayout<CalledFromJitExitFrameLayout>() &&
         !fun->nonLazyScript()->mayReadFrameArgsDirectly())
     {
         nformals = fun->nargs();
     }
 
     size_t newTargetOffset = Max(nargs, fun->nargs());
 
     Value* argv = layout->argv();
@@ -1128,26 +1127,18 @@ TraceJitExitFrame(JSTracer* trc, const J
             Value* vp = method->vp();
             TraceRootRange(trc, len, vp, "ion-dom-args");
         } else {
             TraceRoot(trc, dom->vp(), "ion-dom-args");
         }
         return;
     }
 
-    if (frame.isExitFrameLayout<LazyLinkExitFrameLayout>()) {
-        auto* layout = frame.exitFrame()->as<LazyLinkExitFrameLayout>();
-        JitFrameLayout* jsLayout = layout->jsFrame();
-        jsLayout->replaceCalleeToken(TraceCalleeToken(trc, jsLayout->calleeToken()));
-        TraceThisAndArguments(trc, frame, jsLayout);
-        return;
-    }
-
-    if (frame.isExitFrameLayout<InterpreterStubExitFrameLayout>()) {
-        auto* layout = frame.exitFrame()->as<InterpreterStubExitFrameLayout>();
+    if (frame.isExitFrameLayout<CalledFromJitExitFrameLayout>()) {
+        auto* layout = frame.exitFrame()->as<CalledFromJitExitFrameLayout>();
         JitFrameLayout* jsLayout = layout->jsFrame();
         jsLayout->replaceCalleeToken(TraceCalleeToken(trc, jsLayout->calleeToken()));
         TraceThisAndArguments(trc, frame, jsLayout);
         return;
     }
 
     if (frame.isBareExit()) {
         // Nothing to trace. Fake exit frame pushed for VM functions with
--- a/js/src/jit/JitFrames.h
+++ b/js/src/jit/JitFrames.h
@@ -819,75 +819,69 @@ ExitFrameLayout::as<IonDOMExitFrameLayou
 struct IonDOMMethodExitFrameLayoutTraits {
     static const size_t offsetOfArgcFromArgv =
         offsetof(IonDOMMethodExitFrameLayout, argc_) -
         offsetof(IonDOMMethodExitFrameLayout, argv_);
 };
 
 // Cannot inherit implementation since we need to extend the top of
 // ExitFrameLayout.
-class LazyLinkExitFrameLayout
+class CalledFromJitExitFrameLayout
 {
   protected: // silence clang warning about unused private fields
     ExitFooterFrame footer_;
     JitFrameLayout exit_;
 
   public:
-    static ExitFrameType Type() { return ExitFrameType::LazyLink; }
-
     static inline size_t Size() {
-        return sizeof(LazyLinkExitFrameLayout);
-    }
-
-    inline JitFrameLayout* jsFrame() {
-        return &exit_;
-    }
-    static size_t offsetOfExitFrame() {
-        return offsetof(LazyLinkExitFrameLayout, exit_);
-    }
-};
-
-template <>
-inline LazyLinkExitFrameLayout*
-ExitFrameLayout::as<LazyLinkExitFrameLayout>()
-{
-    MOZ_ASSERT(is<LazyLinkExitFrameLayout>());
-    uint8_t* sp = reinterpret_cast<uint8_t*>(this);
-    sp -= LazyLinkExitFrameLayout::offsetOfExitFrame();
-    return reinterpret_cast<LazyLinkExitFrameLayout*>(sp);
-}
-
-class InterpreterStubExitFrameLayout
-{
-  protected: // silence clang warning about unused private fields
-    ExitFooterFrame footer_;
-    JitFrameLayout exit_;
-
-  public:
-    static ExitFrameType Type() { return ExitFrameType::InterpreterStub; }
-
-    static inline size_t Size() {
-        return sizeof(InterpreterStubExitFrameLayout);
+        return sizeof(CalledFromJitExitFrameLayout);
     }
     inline JitFrameLayout* jsFrame() {
         return &exit_;
     }
     static size_t offsetOfExitFrame() {
-        return offsetof(InterpreterStubExitFrameLayout, exit_);
+        return offsetof(CalledFromJitExitFrameLayout, exit_);
     }
 };
 
-template <>
-inline InterpreterStubExitFrameLayout*
-ExitFrameLayout::as<InterpreterStubExitFrameLayout>()
+class LazyLinkExitFrameLayout : public CalledFromJitExitFrameLayout
+{
+  public:
+    static ExitFrameType Type() { return ExitFrameType::LazyLink; }
+};
+
+class InterpreterStubExitFrameLayout : public CalledFromJitExitFrameLayout
+{
+  public:
+    static ExitFrameType Type() { return ExitFrameType::InterpreterStub; }
+};
+
+class WasmExitFrameLayout : CalledFromJitExitFrameLayout
 {
-    MOZ_ASSERT(is<InterpreterStubExitFrameLayout>());
+  public:
+    static ExitFrameType Type() { return ExitFrameType::WasmJitEntry; }
+};
+
+template<>
+inline bool
+ExitFrameLayout::is<CalledFromJitExitFrameLayout>()
+{
+    return is<InterpreterStubExitFrameLayout>() ||
+           is<LazyLinkExitFrameLayout>() ||
+           is<WasmExitFrameLayout>();
+}
+
+template <>
+inline CalledFromJitExitFrameLayout*
+ExitFrameLayout::as<CalledFromJitExitFrameLayout>()
+{
+    MOZ_ASSERT(is<CalledFromJitExitFrameLayout>());
     uint8_t* sp = reinterpret_cast<uint8_t*>(this);
-    sp -= InterpreterStubExitFrameLayout::offsetOfExitFrame();
-    return reinterpret_cast<InterpreterStubExitFrameLayout*>(sp);
+    sp -= CalledFromJitExitFrameLayout::offsetOfExitFrame();
+    return reinterpret_cast<CalledFromJitExitFrameLayout*>(sp);
 }
 
 class ICStub;
 
 class JitStubFrameLayout : public CommonFrameLayout
 {
     // Info on the stack
     //
--- a/js/src/jit/LIR.cpp
+++ b/js/src/jit/LIR.cpp
@@ -512,34 +512,39 @@ LInstruction::assignSnapshot(LSnapshot* 
 }
 
 void
 LNode::dump(GenericPrinter& out)
 {
     if (numDefs() != 0) {
         out.printf("{");
         for (size_t i = 0; i < numDefs(); i++) {
-            out.printf("%s", getDef(i)->toString().get());
+            const LDefinition* def = isPhi() ? toPhi()->getDef(i) : toInstruction()->getDef(i);
+            out.printf("%s", def->toString().get());
             if (i != numDefs() - 1)
                 out.printf(", ");
         }
         out.printf("} <- ");
     }
 
     printName(out);
     printOperands(out);
 
-    if (numTemps()) {
-        out.printf(" t=(");
-        for (size_t i = 0; i < numTemps(); i++) {
-            out.printf("%s", getTemp(i)->toString().get());
-            if (i != numTemps() - 1)
-                out.printf(", ");
+    if (isInstruction()) {
+        LInstruction* ins = toInstruction();
+        size_t numTemps = ins->numTemps();
+        if (numTemps > 0) {
+            out.printf(" t=(");
+            for (size_t i = 0; i < numTemps; i++) {
+                out.printf("%s", ins->getTemp(i)->toString().get());
+                if (i != numTemps - 1)
+                    out.printf(", ");
+            }
+            out.printf(")");
         }
-        out.printf(")");
     }
 
     if (numSuccessors()) {
         out.printf(" s=(");
         for (size_t i = 0; i < numSuccessors(); i++) {
             out.printf("block%u", getSuccessor(i)->id());
             if (i != numSuccessors() - 1)
                 out.printf(", ");
--- a/js/src/jit/LIR.h
+++ b/js/src/jit/LIR.h
@@ -720,36 +720,25 @@ class LNode
     inline LInstruction* toInstruction();
     inline const LInstruction* toInstruction() const;
 
     // Returns the number of outputs of this instruction. If an output is
     // unallocated, it is an LDefinition, defining a virtual register.
     size_t numDefs() const {
         return numDefs_;
     }
-    virtual LDefinition* getDef(size_t index) = 0;
-    virtual void setDef(size_t index, const LDefinition& def) = 0;
 
     // Returns information about operands.
     virtual LAllocation* getOperand(size_t index) = 0;
     virtual void setOperand(size_t index, const LAllocation& a) = 0;
 
-    // Returns information about temporary registers needed. Each temporary
-    // register is an LDefinition with a fixed or virtual register and
-    // either GENERAL, FLOAT32, or DOUBLE type.
-    size_t numTemps() const {
-        return numTemps_;
-    }
-    inline LDefinition* getTemp(size_t index);
-
     // Returns the number of successors of this instruction, if it is a control
     // transfer instruction, or zero otherwise.
     virtual size_t numSuccessors() const = 0;
     virtual MBasicBlock* getSuccessor(size_t i) const = 0;
-    virtual void setSuccessor(size_t i, MBasicBlock* successor) = 0;
 
     bool isCall() const {
         return isCall_;
     }
 
     // Does this call preserve the given register?
     // By default, it is assumed that all registers are clobbered by a call.
     inline bool isCallPreserved(AnyRegister reg) const;
@@ -836,16 +825,30 @@ class LInstruction
         movesAfter_(nullptr)
     { }
 
     void setIsCall() {
         isCall_ = true;
     }
 
   public:
+    inline LDefinition* getDef(size_t index);
+
+    void setDef(size_t index, const LDefinition& def) {
+        *getDef(index) = def;
+    }
+
+    // Returns information about temporary registers needed. Each temporary
+    // register is an LDefinition with a fixed or virtual register and
+    // either GENERAL, FLOAT32, or DOUBLE type.
+    size_t numTemps() const {
+        return numTemps_;
+    }
+    inline LDefinition* getTemp(size_t index);
+
     LSnapshot* snapshot() const {
         return snapshot_;
     }
     LSafepoint* safepoint() const {
         return safepoint_;
     }
     LMoveGroup* inputMoves() const {
         return inputMoves_;
@@ -943,21 +946,21 @@ class LPhi final : public LNode
       : LNode(/* nonPhiNumOperands = */ 0,
               /* numDefs = */ 1,
               /* numTemps = */ 0),
         inputs_(inputs)
     {
         setMir(ins);
     }
 
-    LDefinition* getDef(size_t index) override {
+    LDefinition* getDef(size_t index) {
         MOZ_ASSERT(index == 0);
         return &def_;
     }
-    void setDef(size_t index, const LDefinition& def) override {
+    void setDef(size_t index, const LDefinition& def) {
         MOZ_ASSERT(index == 0);
         def_ = def;
     }
     size_t numOperands() const {
         return mir_->toPhi()->numOperands();
     }
     LAllocation* getOperand(size_t index) override {
         MOZ_ASSERT(index < numOperands());
@@ -973,19 +976,16 @@ class LPhi final : public LNode
     LDefinition* getTemp(size_t index) = delete;
 
     size_t numSuccessors() const override {
         return 0;
     }
     MBasicBlock* getSuccessor(size_t i) const override {
         MOZ_CRASH("no successors");
     }
-    void setSuccessor(size_t i, MBasicBlock*) override {
-        MOZ_CRASH("no successors");
-    }
 };
 
 class LMoveGroup;
 class LBlock
 {
     MBasicBlock* block_;
     FixedList<LPhi> phis_;
     InlineList<LInstruction> instructions_;
@@ -1087,26 +1087,26 @@ namespace details {
         mozilla::Array<LDefinition, Defs + Temps> defsAndTemps_;
 
       protected:
         explicit LInstructionFixedDefsTempsHelper(uint32_t numOperands)
           : LInstruction(numOperands, Defs, Temps)
         {}
 
       public:
-        LDefinition* getDef(size_t index) final override {
+        LDefinition* getDef(size_t index) {
             MOZ_ASSERT(index < Defs);
             return &defsAndTemps_[index];
         }
         LDefinition* getTemp(size_t index) {
             MOZ_ASSERT(index < Temps);
             return &defsAndTemps_[Defs + index];
         }
 
-        void setDef(size_t index, const LDefinition& def) final override {
+        void setDef(size_t index, const LDefinition& def) {
             MOZ_ASSERT(index < Defs);
             defsAndTemps_[index] = def;
         }
         void setTemp(size_t index, const LDefinition& a) {
             MOZ_ASSERT(index < Temps);
             defsAndTemps_[Defs + index] = a;
         }
         void setInt64Temp(size_t index, const LInt64Definition& a) {
@@ -1119,38 +1119,48 @@ namespace details {
         }
 
         size_t numSuccessors() const override {
             return 0;
         }
         MBasicBlock* getSuccessor(size_t i) const override {
             MOZ_CRASH("no successors");
         }
-        void setSuccessor(size_t i, MBasicBlock* successor) override {
-            MOZ_CRASH("no successors");
-        }
 
         // Default accessors, assuming a single input and output, respectively.
         const LAllocation* input() {
             MOZ_ASSERT(numOperands() == 1);
             return getOperand(0);
         }
         const LDefinition* output() {
             MOZ_ASSERT(numDefs() == 1);
             return getDef(0);
         }
+        static size_t offsetOfDef(size_t index) {
+            using T = LInstructionFixedDefsTempsHelper<0, 0>;
+            return offsetof(T, defsAndTemps_) + index * sizeof(LDefinition);
+        }
         static size_t offsetOfTemp(uint32_t numDefs, uint32_t index) {
             using T = LInstructionFixedDefsTempsHelper<0, 0>;
             return offsetof(T, defsAndTemps_) + (numDefs + index) * sizeof(LDefinition);
         }
     };
 } // namespace details
 
 inline LDefinition*
-LNode::getTemp(size_t index)
+LInstruction::getDef(size_t index)
+{
+    MOZ_ASSERT(index < numDefs());
+    using T = details::LInstructionFixedDefsTempsHelper<0, 0>;
+    uint8_t* p = reinterpret_cast<uint8_t*>(this) + T::offsetOfDef(index);
+    return reinterpret_cast<LDefinition*>(p);
+}
+
+inline LDefinition*
+LInstruction::getTemp(size_t index)
 {
     MOZ_ASSERT(index < numTemps());
     using T = details::LInstructionFixedDefsTempsHelper<0, 0>;
     uint8_t* p = reinterpret_cast<uint8_t*>(this) + T::offsetOfTemp(numDefs(), index);
     return reinterpret_cast<LDefinition*>(p);
 }
 
 template <size_t Defs, size_t Operands, size_t Temps>
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -20,17 +20,16 @@
 #include "jit/AtomicOperations.h"
 #include "jit/BaselineInspector.h"
 #include "jit/IonBuilder.h"
 #include "jit/JitSpewer.h"
 #include "jit/MIRGraph.h"
 #include "jit/RangeAnalysis.h"
 #include "js/Conversions.h"
 
-#include "jsatominlines.h"
 #include "jsboolinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 #include "vm/UnboxedObject-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -978,17 +978,17 @@ class LControlInstructionHelper : public
 
   public:
     virtual size_t numSuccessors() const final override { return Succs; }
 
     virtual MBasicBlock* getSuccessor(size_t i) const final override {
         return successors_[i];
     }
 
-    virtual void setSuccessor(size_t i, MBasicBlock* successor) final override {
+    void setSuccessor(size_t i, MBasicBlock* successor) {
         successors_[i] = successor;
     }
 };
 
 // Jumps to the start of a basic block.
 class LGoto : public LControlInstructionHelper<1, 0, 0>
 {
   public:
--- a/js/src/jsapi-tests/testAssemblerBuffer.cpp
+++ b/js/src/jsapi-tests/testAssemblerBuffer.cpp
@@ -1,16 +1,14 @@
 /* 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 <stdlib.h>
 
-#include "jsatom.h"
-
 #include "jit/shared/IonAssemblerBufferWithConstantPools.h"
 
 #include "jsapi-tests/tests.h"
 
 // Tests for classes in:
 //
 //   jit/shared/IonAssemblerBuffer.h
 //   jit/shared/IonAssemblerBufferWithConstantPools.h
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -19,17 +19,16 @@
 #ifdef __linux__
 # include <dlfcn.h>
 #endif
 #include <stdarg.h>
 #include <string.h>
 #include <sys/stat.h>
 
 #include "jsarray.h"
-#include "jsatom.h"
 #include "jsbool.h"
 #include "jscntxt.h"
 #include "jsdate.h"
 #include "jsexn.h"
 #include "jsfriendapi.h"
 #include "jsfun.h"
 #include "jsiter.h"
 #include "jsmath.h"
@@ -88,17 +87,16 @@
 #include "vm/String.h"
 #include "vm/StringBuffer.h"
 #include "vm/Symbol.h"
 #include "vm/WrapperObject.h"
 #include "vm/Xdr.h"
 #include "wasm/AsmJS.h"
 #include "wasm/WasmModule.h"
 
-#include "jsatominlines.h"
 #include "jsfuninlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/Interpreter-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/SavedStacks-inl.h"
 #include "vm/String-inl.h"
 
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -10,17 +10,16 @@
 #include "mozilla/CheckedInt.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/MathAlgorithms.h"
 
 #include <algorithm>
 
 #include "jsapi.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsfriendapi.h"
 #include "jsfun.h"
 #include "jsiter.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jstypes.h"
 #include "jsutil.h"
@@ -33,18 +32,16 @@
 #include "vm/ArgumentsObject.h"
 #include "vm/Interpreter.h"
 #include "vm/SelfHosting.h"
 #include "vm/Shape.h"
 #include "vm/StringBuffer.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/WrapperObject.h"
 
-#include "jsatominlines.h"
-
 #include "vm/ArgumentsObject-inl.h"
 #include "vm/ArrayObject-inl.h"
 #include "vm/Caches-inl.h"
 #include "vm/GeckoProfiler-inl.h"
 #include "vm/Interpreter-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/UnboxedObject-inl.h"
 
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -35,16 +35,90 @@ using namespace js;
 using namespace js::gc;
 
 using mozilla::ArrayEnd;
 using mozilla::ArrayLength;
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::RangedPtr;
 
+struct js::AtomHasher::Lookup
+{
+    union {
+        const JS::Latin1Char* latin1Chars;
+        const char16_t* twoByteChars;
+    };
+    bool isLatin1;
+    size_t length;
+    const JSAtom* atom; /* Optional. */
+    JS::AutoCheckCannotGC nogc;
+
+    HashNumber hash;
+
+    MOZ_ALWAYS_INLINE Lookup(const char16_t* chars, size_t length)
+      : twoByteChars(chars), isLatin1(false), length(length), atom(nullptr),
+        hash(mozilla::HashString(chars, length))
+    {}
+
+    MOZ_ALWAYS_INLINE Lookup(const JS::Latin1Char* chars, size_t length)
+      : latin1Chars(chars), isLatin1(true), length(length), atom(nullptr),
+        hash(mozilla::HashString(chars, length))
+    {}
+
+    inline explicit Lookup(const JSAtom* atom)
+      : isLatin1(atom->hasLatin1Chars()), length(atom->length()), atom(atom),
+        hash(atom->hash())
+    {
+        if (isLatin1) {
+            latin1Chars = atom->latin1Chars(nogc);
+            MOZ_ASSERT(mozilla::HashString(latin1Chars, length) == hash);
+        } else {
+            twoByteChars = atom->twoByteChars(nogc);
+            MOZ_ASSERT(mozilla::HashString(twoByteChars, length) == hash);
+        }
+    }
+};
+
+inline HashNumber
+js::AtomHasher::hash(const Lookup& l)
+{
+    return l.hash;
+}
+
+MOZ_ALWAYS_INLINE bool
+js::AtomHasher::match(const AtomStateEntry& entry, const Lookup& lookup)
+{
+    JSAtom* key = entry.asPtrUnbarriered();
+    if (lookup.atom)
+        return lookup.atom == key;
+    if (key->length() != lookup.length || key->hash() != lookup.hash)
+        return false;
+
+    if (key->hasLatin1Chars()) {
+        const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
+        if (lookup.isLatin1)
+            return mozilla::PodEqual(keyChars, lookup.latin1Chars, lookup.length);
+        return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
+    }
+
+    const char16_t* keyChars = key->twoByteChars(lookup.nogc);
+    if (lookup.isLatin1)
+        return EqualChars(lookup.latin1Chars, keyChars, lookup.length);
+    return mozilla::PodEqual(keyChars, lookup.twoByteChars, lookup.length);
+}
+
+inline JSAtom*
+js::AtomStateEntry::asPtr(JSContext* cx) const
+{
+    JSAtom* atom = asPtrUnbarriered();
+    if (!cx->helperThread())
+        JSString::readBarrier(atom);
+    return atom;
+}
+
 const char*
 js::AtomToPrintableString(JSContext* cx, JSAtom* atom, JSAutoByteString* bytes)
 {
     JSString* str = QuoteString(cx, atom, 0);
     if (!str)
         return nullptr;
     return bytes->encodeLatin1(cx, str);
 }
@@ -705,8 +779,29 @@ js::XDRAtom(XDRState<mode>* xdr, Mutable
     return true;
 }
 
 template bool
 js::XDRAtom(XDRState<XDR_ENCODE>* xdr, MutableHandleAtom atomp);
 
 template bool
 js::XDRAtom(XDRState<XDR_DECODE>* xdr, MutableHandleAtom atomp);
+
+Handle<PropertyName*>
+js::ClassName(JSProtoKey key, JSContext* cx)
+{
+    return ClassName(key, cx->names());
+}
+
+void
+js::gc::MergeAtomsAddedWhileSweeping(JSRuntime* rt)
+{
+    // Add atoms that were added to the secondary table while we were sweeping
+    // the main table.
+
+    AutoEnterOOMUnsafeRegion oomUnsafe;
+    AtomSet* atomsTable = rt->atomsForSweeping();
+    MOZ_ASSERT(atomsTable);
+    for (auto r = rt->atomsAddedWhileSweeping()->all(); !r.empty(); r.popFront()) {
+        if (!atomsTable->putNew(AtomHasher::Lookup(r.front().asPtrUnbarriered()), r.front()))
+            oomUnsafe.crash("Adding atom from secondary table after sweep");
+    }
+}
--- a/js/src/jsatom.h
+++ b/js/src/jsatom.h
@@ -2,23 +2,19 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jsatom_h
 #define jsatom_h
 
-#include "mozilla/HashFunctions.h"
 #include "mozilla/Maybe.h"
 
-#include "jsalloc.h"
-
 #include "gc/Rooting.h"
-#include "js/GCHashTable.h"
 #include "vm/CommonPropertyNames.h"
 
 class JSAtom;
 class JSAutoByteString;
 
 namespace JS {
 class Value;
 struct Zone;
@@ -28,86 +24,16 @@ namespace js {
 
 /*
  * Return a printable, lossless char[] representation of a string-type atom.
  * The lifetime of the result matches the lifetime of bytes.
  */
 extern const char*
 AtomToPrintableString(JSContext* cx, JSAtom* atom, JSAutoByteString* bytes);
 
-class AtomStateEntry
-{
-    uintptr_t bits;
-
-    static const uintptr_t NO_TAG_MASK = uintptr_t(-1) - 1;
-
-  public:
-    AtomStateEntry() : bits(0) {}
-    AtomStateEntry(const AtomStateEntry& other) : bits(other.bits) {}
-    AtomStateEntry(JSAtom* ptr, bool tagged)
-      : bits(uintptr_t(ptr) | uintptr_t(tagged))
-    {
-        MOZ_ASSERT((uintptr_t(ptr) & 0x1) == 0);
-    }
-
-    bool isPinned() const {
-        return bits & 0x1;
-    }
-
-    /*
-     * Non-branching code sequence. Note that the const_cast is safe because
-     * the hash function doesn't consider the tag to be a portion of the key.
-     */
-    void setPinned(bool pinned) const {
-        const_cast<AtomStateEntry*>(this)->bits |= uintptr_t(pinned);
-    }
-
-    JSAtom* asPtr(JSContext* cx) const;
-    JSAtom* asPtrUnbarriered() const;
-
-    bool needsSweep() {
-        JSAtom* atom = asPtrUnbarriered();
-        return gc::IsAboutToBeFinalizedUnbarriered(&atom);
-    }
-};
-
-struct AtomHasher
-{
-    struct Lookup;
-    static inline HashNumber hash(const Lookup& l);
-    static MOZ_ALWAYS_INLINE bool match(const AtomStateEntry& entry, const Lookup& lookup);
-    static void rekey(AtomStateEntry& k, const AtomStateEntry& newKey) { k = newKey; }
-};
-
-using AtomSet = JS::GCHashSet<AtomStateEntry, AtomHasher, SystemAllocPolicy>;
-
-// This class is a wrapper for AtomSet that is used to ensure the AtomSet is
-// not modified. It should only expose read-only methods from AtomSet.
-// Note however that the atoms within the table can be marked during GC.
-class FrozenAtomSet
-{
-    AtomSet* mSet;
-
-public:
-    // This constructor takes ownership of the passed-in AtomSet.
-    explicit FrozenAtomSet(AtomSet* set) { mSet = set; }
-
-    ~FrozenAtomSet() { js_delete(mSet); }
-
-    MOZ_ALWAYS_INLINE AtomSet::Ptr readonlyThreadsafeLookup(const AtomSet::Lookup& l) const;
-
-    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
-        return mSet->sizeOfIncludingThis(mallocSizeOf);
-    }
-
-    typedef AtomSet::Range Range;
-
-    AtomSet::Range all() const { return mSet->all(); }
-};
-
 class PropertyName;
 
 }  /* namespace js */
 
 extern bool
 AtomIsPinned(JSContext* cx, JSAtom* atom);
 
 #ifdef DEBUG
@@ -182,16 +108,23 @@ enum XDRMode {
 
 template <XDRMode mode>
 class XDRState;
 
 template<XDRMode mode>
 bool
 XDRAtom(XDRState<mode>* xdr, js::MutableHandleAtom atomp);
 
+extern JS::Handle<PropertyName*>
+ClassName(JSProtoKey key, JSContext* cx);
+
+namespace gc {
+void MergeAtomsAddedWhileSweeping(JSRuntime* rt);
+} // namespace gc
+
 #ifdef DEBUG
 
 bool AtomIsMarked(JS::Zone* zone, JSAtom* atom);
 bool AtomIsMarked(JS::Zone* zone, jsid id);
 bool AtomIsMarked(JS::Zone* zone, const JS::Value& value);
 
 #endif // DEBUG
 
--- a/js/src/jsatominlines.h
+++ b/js/src/jsatominlines.h
@@ -4,74 +4,25 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jsatominlines_h
 #define jsatominlines_h
 
 #include "jsatom.h"
 
-#include "mozilla/PodOperations.h"
 #include "mozilla/RangedPtr.h"
 
-#include "jscntxt.h"
 #include "jsnum.h"
 
+#include "vm/Runtime.h"
 #include "vm/String.h"
 
-inline JSAtom*
-js::AtomStateEntry::asPtr(JSContext* cx) const
-{
-    JSAtom* atom = asPtrUnbarriered();
-    if (!cx->helperThread())
-        JSString::readBarrier(atom);
-    return atom;
-}
-
-inline JSAtom*
-js::AtomStateEntry::asPtrUnbarriered() const
-{
-    MOZ_ASSERT(bits != 0);
-    return reinterpret_cast<JSAtom*>(bits & NO_TAG_MASK);
-}
-
 namespace js {
 
-struct AtomHasher::Lookup
-{
-    union {
-        const JS::Latin1Char* latin1Chars;
-        const char16_t* twoByteChars;
-    };
-    bool isLatin1;
-    size_t length;
-    const JSAtom* atom; /* Optional. */
-    JS::AutoCheckCannotGC nogc;
-
-    HashNumber hash;
-
-    MOZ_ALWAYS_INLINE Lookup(const char16_t* chars, size_t length)
-      : twoByteChars(chars), isLatin1(false), length(length), atom(nullptr)
-        {
-            hash = mozilla::HashString(chars, length);
-        }
-    MOZ_ALWAYS_INLINE Lookup(const JS::Latin1Char* chars, size_t length)
-      : latin1Chars(chars), isLatin1(true), length(length), atom(nullptr)
-        {
-            hash = mozilla::HashString(chars, length);
-        }
-    inline explicit Lookup(const JSAtom* atom);
-};
-
-inline HashNumber
-AtomHasher::hash(const Lookup& l)
-{
-    return l.hash;
-}
-
 inline jsid
 AtomToId(JSAtom* atom)
 {
     JS_STATIC_ASSERT(JSID_INT_MIN == 0);
 
     uint32_t index;
     if (atom->isIndex(&index) && index <= JSID_INT_MAX)
         return INT_TO_JSID(int32_t(index));
@@ -194,52 +145,16 @@ IdToString(JSContext* cx, jsid id)
     RootedValue idv(cx, IdToValue(id));
     JSString* str = ToStringSlow<CanGC>(cx, idv);
     if (!str)
         return nullptr;
 
     return str->ensureFlat(cx);
 }
 
-inline
-AtomHasher::Lookup::Lookup(const JSAtom* atom)
-  : isLatin1(atom->hasLatin1Chars()), length(atom->length()), atom(atom)
-{
-    hash = atom->hash();
-    if (isLatin1) {
-        latin1Chars = atom->latin1Chars(nogc);
-        MOZ_ASSERT(mozilla::HashString(latin1Chars, length) == hash);
-    } else {
-        twoByteChars = atom->twoByteChars(nogc);
-        MOZ_ASSERT(mozilla::HashString(twoByteChars, length) == hash);
-    }
-}
-
-MOZ_ALWAYS_INLINE bool
-AtomHasher::match(const AtomStateEntry& entry, const Lookup& lookup)
-{
-    JSAtom* key = entry.asPtrUnbarriered();
-    if (lookup.atom)
-        return lookup.atom == key;
-    if (key->length() != lookup.length || key->hash() != lookup.hash)
-        return false;
-
-    if (key->hasLatin1Chars()) {
-        const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
-        if (lookup.isLatin1)
-            return mozilla::PodEqual(keyChars, lookup.latin1Chars, lookup.length);
-        return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
-    }
-
-    const char16_t* keyChars = key->twoByteChars(lookup.nogc);
-    if (lookup.isLatin1)
-        return EqualChars(lookup.latin1Chars, keyChars, lookup.length);
-    return mozilla::PodEqual(keyChars, lookup.twoByteChars, lookup.length);
-}
-
 inline Handle<PropertyName*>
 TypeName(JSType type, const JSAtomState& names)
 {
     MOZ_ASSERT(type < JSTYPE_LIMIT);
     JS_STATIC_ASSERT(offsetof(JSAtomState, undefined) +
                      JSTYPE_LIMIT * sizeof(ImmutablePropertyNamePtr) <=
                      sizeof(JSAtomState));
     JS_STATIC_ASSERT(JSTYPE_UNDEFINED == 0);
@@ -252,17 +167,11 @@ ClassName(JSProtoKey key, JSAtomState& a
     MOZ_ASSERT(key < JSProto_LIMIT);
     JS_STATIC_ASSERT(offsetof(JSAtomState, Null) +
                      JSProto_LIMIT * sizeof(ImmutablePropertyNamePtr) <=
                      sizeof(JSAtomState));
     JS_STATIC_ASSERT(JSProto_Null == 0);
     return (&atomState.Null)[key];
 }
 
-inline Handle<PropertyName*>
-ClassName(JSProtoKey key, JSContext* cx)
-{
-    return ClassName(key, cx->names());
-}
-
 } // namespace js
 
 #endif /* jsatominlines_h */
--- a/js/src/jsbool.cpp
+++ b/js/src/jsbool.cpp
@@ -6,17 +6,16 @@
 
 /*
  * JS boolean implementation.
  */
 
 #include "jsboolinlines.h"
 
 #include "jsapi.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsobj.h"
 #include "jstypes.h"
 
 #include "jit/InlinableNatives.h"
 #include "vm/GlobalObject.h"
 #include "vm/ProxyObject.h"
 #include "vm/StringBuffer.h"
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -23,17 +23,16 @@
 # include <android/log.h>
 # include <fstream>
 # include <string>
 #endif // ANDROID
 #ifdef XP_WIN
 #include <processthreadsapi.h>
 #endif // XP_WIN
 
-#include "jsatom.h"
 #include "jscompartment.h"
 #include "jsdtoa.h"
 #include "jsexn.h"
 #include "jsfun.h"
 #include "jsiter.h"
 #include "jsnativestack.h"
 #include "jsobj.h"
 #include "jsopcode.h"
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -21,17 +21,16 @@
 #include "jit/JitOptions.h"
 #include "js/Date.h"
 #include "js/Proxy.h"
 #include "js/RootingAPI.h"
 #include "proxy/DeadObjectProxy.h"
 #include "vm/Debugger.h"
 #include "vm/WrapperObject.h"
 
-#include "jsatominlines.h"
 #include "jsfuninlines.h"
 #include "jsgcinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "gc/Marking-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/UnboxedObject-inl.h"
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1224,17 +1224,17 @@ js::DumpHeap(JSContext* cx, FILE* fp, js
 
     DumpHeapTracer dtrc(fp, cx);
 
     fprintf(dtrc.output, "# Roots.\n");
     {
         JSRuntime* rt = cx->runtime();
         js::gc::AutoPrepareForTracing prep(cx, WithAtoms);
         gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PhaseKind::TRACE_HEAP);
-        rt->gc.traceRuntime(&dtrc, prep.session().lock);
+        rt->gc.traceRuntime(&dtrc, prep.session());
     }
 
     fprintf(dtrc.output, "# Weak maps.\n");
     WeakMapBase::traceAllMappings(&dtrc);
 
     fprintf(dtrc.output, "==========\n");
 
     dtrc.prefix = "> ";
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -15,17 +15,16 @@
 #include "mozilla/Maybe.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Range.h"
 
 #include <string.h>
 
 #include "jsapi.h"
 #include "jsarray.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsobj.h"
 #include "jsscript.h"
 #include "jsstr.h"
 #include "jstypes.h"
 #include "jswrapper.h"
 
 #include "builtin/Eval.h"
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -252,17 +252,17 @@ class JSFunction : public js::NativeObje
         return nonLazyScript()->hasBaselineScript() || nonLazyScript()->hasIonScript();
     }
     bool hasJitEntry() const {
         return hasScript() || isNativeWithJitEntry();
     }
 
     /* Compound attributes: */
     bool isBuiltin() const {
-        return isBuiltinNative() || isSelfHostedBuiltin();
+        return isBuiltinNative() || isNativeWithJitEntry() || isSelfHostedBuiltin();
     }
 
     bool isNamedLambda() const {
         return isLambda() && displayAtom() && !hasCompileTimeName() && !hasGuessedAtom();
     }
 
     bool hasLexicalThis() const {
         return isArrow();
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -201,17 +201,16 @@
 #include <initializer_list>
 #include <string.h>
 #ifndef XP_WIN
 # include <sys/mman.h>
 # include <unistd.h>
 #endif
 
 #include "jsapi.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jsfriendapi.h"
 #include "jsobj.h"
 #include "jsprf.h"
 #include "jsscript.h"
 #include "jstypes.h"
 #include "jsutil.h"
@@ -2816,17 +2815,17 @@ GCRuntime::updateAllCellPointers(MovingT
 /*
  * Update pointers to relocated cells in a single zone by doing a traversal of
  * that zone's arenas and calling per-zone sweep hooks.
  *
  * The latter is necessary to update weak references which are not marked as
  * part of the traversal.
  */
 void
-GCRuntime::updateZonePointersToRelocatedCells(Zone* zone, AutoLockForExclusiveAccess& lock)
+GCRuntime::updateZonePointersToRelocatedCells(Zone* zone)
 {
     MOZ_ASSERT(!rt->isBeingDestroyed());
     MOZ_ASSERT(zone->isGCCompacting());
 
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::COMPACT_UPDATE);
     MovingTracer trc(rt);
 
     zone->fixupAfterMovingGC();
@@ -2857,28 +2856,28 @@ GCRuntime::updateZonePointersToRelocated
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
         callWeakPointerCompartmentCallbacks(comp);
 }
 
 /*
  * Update runtime-wide pointers to relocated cells.
  */
 void
-GCRuntime::updateRuntimePointersToRelocatedCells(AutoLockForExclusiveAccess& lock)
+GCRuntime::updateRuntimePointersToRelocatedCells(AutoTraceSession& session)
 {
     MOZ_ASSERT(!rt->isBeingDestroyed());
 
     gcstats::AutoPhase ap1(stats(), gcstats::PhaseKind::COMPACT_UPDATE);
     MovingTracer trc(rt);
 
     JSCompartment::fixupCrossCompartmentWrappersAfterMovingGC(&trc);
 
     rt->geckoProfiler().fixupStringsMapAfterMovingGC();
 
-    traceRuntimeForMajorGC(&trc, lock);
+    traceRuntimeForMajorGC(&trc, session);
 
     // Mark roots to update them.
     {
         gcstats::AutoPhase ap2(stats(), gcstats::PhaseKind::MARK_ROOTS);
         Debugger::traceAllForMovingGC(&trc);
         Debugger::traceIncomingCrossCompartmentEdges(&trc);
 
         // Mark all gray roots, making sure we call the trace callback to get the
@@ -3924,17 +3923,17 @@ class MOZ_RAII js::gc::AutoRunParallelTa
     }
 
     void run() override {
         func_(runtime());
     }
 };
 
 void
-GCRuntime::purgeRuntime(AutoLockForExclusiveAccess& lock)
+GCRuntime::purgeRuntime()
 {
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::PURGE);
 
     for (GCCompartmentsIter comp(rt); !comp.done(); comp.next())
         comp->purge();
 
     for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
         zone->atomCache().clearAndShrink();
@@ -4243,26 +4242,32 @@ UnmarkCollectedZones(JSRuntime* rt)
 
 static void
 BufferGrayRoots(JSRuntime* rt)
 {
     rt->gc.bufferGrayRoots();
 }
 
 bool
-GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock)
-{
+GCRuntime::beginMarkPhase(JS::gcreason::Reason reason, AutoTraceSession& session)
+{
+    MOZ_ASSERT(session.maybeLock.isSome());
+
 #ifdef DEBUG
     if (fullCompartmentChecks)
         checkForCompartmentMismatches();
 #endif
 
-    if (!prepareZonesForCollection(reason, &isFull.ref(), lock))
+    if (!prepareZonesForCollection(reason, &isFull.ref(), session.lock()))
         return false;
 
+    /* If we're not collecting the atoms zone we can release the lock now. */
+    if (!atomsZone->isCollecting())
+        session.maybeLock.reset();
+
     /*
      * Ensure that after the start of a collection we don't allocate into any
      * existing arenas, as this can cause unreachable things to be marked.
      */
     if (isIncremental) {
         for (GCZonesIter zone(rt); !zone.done(); zone.next())
             zone->arenas.prepareForIncrementalGC();
     }
@@ -4316,24 +4321,24 @@ GCRuntime::beginMarkPhase(JS::gcreason::
          * We must purge the runtime at the beginning of an incremental GC. The
          * danger if we purge later is that the snapshot invariant of
          * incremental GC will be broken, as follows. If some object is
          * reachable only through some cache (say the dtoaCache) then it will
          * not be part of the snapshot.  If we purge after root marking, then
          * the mutator could obtain a pointer to the object and start using
          * it. This object might never be marked, so a GC hazard would exist.
          */
-        purgeRuntime(lock);
+        purgeRuntime();
     }
 
     /*
      * Mark phase.
      */
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::MARK);
-    traceRuntimeForMajorGC(gcmarker, lock);
+    traceRuntimeForMajorGC(gcmarker, session);
 
     if (isIncremental)
         markCompartments();
 
     updateMallocCountersOnGCStart();
 
     /*
      * Process any queued source compressions during the start of a major
@@ -4523,17 +4528,17 @@ struct GCChunkHasher {
     }
 };
 
 class js::gc::MarkingValidator
 {
   public:
     explicit MarkingValidator(GCRuntime* gc);
     ~MarkingValidator();
-    void nonIncrementalMark(AutoLockForExclusiveAccess& lock);
+    void nonIncrementalMark(AutoTraceSession& session);
     void validate();
 
   private:
     GCRuntime* gc;
     bool initialized;
 
     typedef HashMap<Chunk*, ChunkBitmap*, GCChunkHasher, SystemAllocPolicy> BitmapMap;
     BitmapMap map;
@@ -4549,17 +4554,17 @@ js::gc::MarkingValidator::~MarkingValida
     if (!map.initialized())
         return;
 
     for (BitmapMap::Range r(map.all()); !r.empty(); r.popFront())
         js_delete(r.front().value());
 }
 
 void
-js::gc::MarkingValidator::nonIncrementalMark(AutoLockForExclusiveAccess& lock)
+js::gc::MarkingValidator::nonIncrementalMark(AutoTraceSession& session)
 {
     /*
      * Perform a non-incremental mark for all collecting zones and record
      * the results for later comparison.
      *
      * Currently this does not validate gray marking.
      */
 
@@ -4643,17 +4648,17 @@ js::gc::MarkingValidator::nonIncremental
             for (auto chunk = gc->allNonEmptyChunks(lock); !chunk.done(); chunk.next())
                 chunk->bitmap.clear();
         }
     }
 
     {
         gcstats::AutoPhase ap(gc->stats(), gcstats::PhaseKind::MARK);
 
-        gc->traceRuntimeForMajorGC(gcmarker, lock);
+        gc->traceRuntimeForMajorGC(gcmarker, session);
 
         gc->incrementalState = State::Mark;
         auto unlimited = SliceBudget::unlimited();
         MOZ_RELEASE_ASSERT(gc->marker.drainMarkStack(unlimited));
     }
 
     gc->incrementalState = State::Sweep;
     {
@@ -4764,24 +4769,24 @@ js::gc::MarkingValidator::validate()
             }
         }
     }
 }
 
 #endif // JS_GC_ZEAL
 
 void
-GCRuntime::computeNonIncrementalMarkingForValidation(AutoLockForExclusiveAccess& lock)
+GCRuntime::computeNonIncrementalMarkingForValidation(AutoTraceSession& session)
 {
 #ifdef JS_GC_ZEAL
     MOZ_ASSERT(!markingValidator);
     if (isIncremental && hasZealMode(ZealMode::IncrementalMarkingValidator))
         markingValidator = js_new<MarkingValidator>(this);
     if (markingValidator)
-        markingValidator->nonIncrementalMark(lock);
+        markingValidator->nonIncrementalMark(session);
 #endif
 }
 
 void
 GCRuntime::validateIncrementalMarking()
 {
 #ifdef JS_GC_ZEAL
     if (markingValidator)
@@ -4872,20 +4877,20 @@ JSCompartment::findOutgoingEdges(ZoneCom
 
 void
 Zone::findOutgoingEdges(ZoneComponentFinder& finder)
 {
     /*
      * Any compartment may have a pointer to an atom in the atoms
      * compartment, and these aren't in the cross compartment map.
      */
-    JSRuntime* rt = runtimeFromActiveCooperatingThread();
-    Zone* atomsZone = rt->atomsCompartment(finder.lock)->zone();
-    if (atomsZone->isGCMarking())
-        finder.addEdgeTo(atomsZone);
+    if (Zone* zone = finder.maybeAtomsZone) {
+        MOZ_ASSERT(zone->isCollecting());
+        finder.addEdgeTo(zone);
+    }
 
     for (CompartmentsInZoneIter comp(this); !comp.done(); comp.next())
         comp->findOutgoingEdges(finder);
 
     for (ZoneSet::Range r = gcSweepGroupEdges().all(); !r.empty(); r.popFront()) {
         if (r.front()->isGCMarking())
             finder.addEdgeTo(r.front());
     }
@@ -4910,25 +4915,26 @@ GCRuntime::findInterZoneEdges()
         if (!WeakMapBase::findInterZoneEdges(zone))
             return false;
     }
 
     return true;
 }
 
 void
-GCRuntime::groupZonesForSweeping(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock)
+GCRuntime::groupZonesForSweeping(JS::gcreason::Reason reason)
 {
 #ifdef DEBUG
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next())
         MOZ_ASSERT(zone->gcSweepGroupEdges().empty());
 #endif
 
     JSContext* cx = TlsContext.get();
-    ZoneComponentFinder finder(cx->nativeStackLimit[JS::StackForSystemCode], lock);
+    Zone* maybeAtomsZone = atomsZone->wasGCStarted() ? atomsZone.ref() : nullptr;
+    ZoneComponentFinder finder(cx->nativeStackLimit[JS::StackForSystemCode], maybeAtomsZone);
     if (!isIncremental || !findInterZoneEdges())
         finder.useOneComponent();
 
 #ifdef JS_GC_ZEAL
     // Use one component for IncrementalSweepThenFinish zeal mode.
     if (isIncremental && reason == JS::gcreason::DEBUG_GC &&
         hasZealMode(ZealMode::IncrementalSweepThenFinish))
     {
@@ -5730,45 +5736,45 @@ GCRuntime::endSweepingSweepGroup(FreeOp*
         arenasAllocatedDuringSweep = arena->getNextAllocDuringSweep();
         arena->unsetAllocDuringSweep();
     }
 
     return Finished;
 }
 
 void
-GCRuntime::beginSweepPhase(JS::gcreason::Reason reason, AutoLockForExclusiveAccess& lock)
+GCRuntime::beginSweepPhase(JS::gcreason::Reason reason, AutoTraceSession& session)
 {
     /*
      * Sweep phase.
      *
      * Finalize as we sweep, outside of lock but with CurrentThreadIsHeapBusy()
      * true so that any attempt to allocate a GC-thing from a finalizer will
      * fail, rather than nest badly and leave the unmarked newborn to be swept.
      */
 
     MOZ_ASSERT(!abortSweepAfterCurrentGroup);
 
     AutoSetThreadIsSweeping threadIsSweeping;
 
     releaseHeldRelocatedArenas();
 
-    computeNonIncrementalMarkingForValidation(lock);
+    computeNonIncrementalMarkingForValidation(session);
 
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP);
 
     sweepOnBackgroundThread =
         reason != JS::gcreason::DESTROY_RUNTIME && !TraceEnabled() && CanUseExtraThreads();
 
     releaseObservedTypes = shouldReleaseObservedTypes();
 
     AssertNoWrappersInGrayList(rt);
     DropStringWrappers(rt);
 
-    groupZonesForSweeping(reason, lock);
+    groupZonesForSweeping(reason);
 
     sweepActions->assertFinished();
 
     // We must not yield after this point until we start sweeping the first sweep
     // group.
     safeToYield = false;
 }
 
@@ -5935,24 +5941,17 @@ GCRuntime::sweepAtomsTable(FreeOp* fop, 
             return NotFinished;
 
         JSAtom* atom = atomsToSweep.front().asPtrUnbarriered();
         if (IsAboutToBeFinalizedUnbarriered(&atom))
             atomsToSweep.removeFront();
         atomsToSweep.popFront();
     }
 
-    // Add any new atoms from the secondary table.
-    AutoEnterOOMUnsafeRegion oomUnsafe;
-    AtomSet* atomsTable = rt->atomsForSweeping();
-    MOZ_ASSERT(atomsTable);
-    for (auto r = rt->atomsAddedWhileSweeping()->all(); !r.empty(); r.popFront()) {
-        if (!atomsTable->putNew(AtomHasher::Lookup(r.front().asPtrUnbarriered()), r.front()))
-            oomUnsafe.crash("Adding atom from secondary table after sweep");
-    }
+    MergeAtomsAddedWhileSweeping(rt);
     rt->destroyAtomsAddedWhileSweepingTable();
 
     maybeAtoms.reset();
     return Finished;
 }
 
 class js::gc::WeakCacheSweepIterator
 {
@@ -6440,17 +6439,17 @@ GCRuntime::initSweepActions()
                         Call(&GCRuntime::sweepShapeTree),
                         Call(&GCRuntime::releaseSweptEmptyArenas))),
                 Call(&GCRuntime::endSweepingSweepGroup)));
 
     return sweepActions != nullptr;
 }
 
 IncrementalProgress
-GCRuntime::performSweepActions(SliceBudget& budget, AutoLockForExclusiveAccess& lock)
+GCRuntime::performSweepActions(SliceBudget& budget)
 {
     AutoSetThreadIsSweeping threadIsSweeping;
 
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP);
     FreeOp fop(rt);
 
     // Drain the mark stack, except in the first sweep slice where we must not
     // yield to the mutator until we've starting sweeping a sweep group.
@@ -6488,17 +6487,17 @@ GCRuntime::allCCVisibleZonesWereCollecte
             return false;
         }
     }
 
     return true;
 }
 
 void
-GCRuntime::endSweepPhase(bool destroyingRuntime, AutoLockForExclusiveAccess& lock)
+GCRuntime::endSweepPhase(bool destroyingRuntime)
 {
     sweepActions->assertFinished();
 
     AutoSetThreadIsSweeping threadIsSweeping;
 
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP);
     FreeOp fop(rt);
 
@@ -6565,17 +6564,17 @@ GCRuntime::beginCompactPhase()
     }
 
     MOZ_ASSERT(!relocatedArenasToRelease);
     startedCompacting = true;
 }
 
 IncrementalProgress
 GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
-                        AutoLockForExclusiveAccess& lock)
+                        AutoTraceSession& session)
 {
     assertBackgroundSweepingFinished();
     MOZ_ASSERT(startedCompacting);
 
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::COMPACT);
 
     // TODO: JSScripts can move. If the sampler interrupts the GC in the
     // middle of relocating an arena, invalid JSScript pointers may be
@@ -6589,28 +6588,28 @@ GCRuntime::compactPhase(JS::gcreason::Re
 
         Zone* zone = zonesToMaybeCompact.ref().front();
         zonesToMaybeCompact.ref().removeFront();
 
         MOZ_ASSERT(zone->group()->nursery().isEmpty());
         zone->changeGCState(Zone::Finished, Zone::Compact);
 
         if (relocateArenas(zone, reason, relocatedArenas, sliceBudget)) {
-            updateZonePointersToRelocatedCells(zone, lock);
+            updateZonePointersToRelocatedCells(zone);
             relocatedZones.append(zone);
         } else {
             zone->changeGCState(Zone::Compact, Zone::Finished);
         }
 
         if (sliceBudget.isOverBudget())
             break;
     }
 
     if (!relocatedZones.isEmpty()) {
-        updateRuntimePointersToRelocatedCells(lock);
+        updateRuntimePointersToRelocatedCells(session);
 
         do {
             Zone* zone = relocatedZones.front();
             relocatedZones.removeFront();
             zone->changeGCState(Zone::Compact, Zone::Finished);
         }
         while (!relocatedZones.isEmpty());
     }
@@ -6691,24 +6690,27 @@ AllNurseriesAreEmpty(JSRuntime* rt)
             return false;
     }
     return true;
 }
 #endif
 
 /* Start a new heap session. */
 AutoTraceSession::AutoTraceSession(JSRuntime* rt, JS::HeapState heapState)
-  : lock(rt),
-    runtime(rt),
+  : runtime(rt),
     prevState(TlsContext.get()->heapState),
     pseudoFrame(TlsContext.get(), HeapStateToLabel(heapState), ProfileEntry::Category::GC)
 {
     MOZ_ASSERT(prevState == JS::HeapState::Idle);
     MOZ_ASSERT(heapState != JS::HeapState::Idle);
     MOZ_ASSERT_IF(heapState == JS::HeapState::MajorCollecting, AllNurseriesAreEmpty(rt));
+
+    // Session always begins with lock held, see comment in class definition.
+    maybeLock.emplace(rt);
+
     TlsContext.get()->heapState = heapState;
 }
 
 AutoTraceSession::~AutoTraceSession()
 {
     MOZ_ASSERT(JS::CurrentThreadIsHeapBusy());
     TlsContext.get()->heapState = prevState;
 }
@@ -6729,17 +6731,17 @@ GCRuntime::canChangeActiveContext(JSCont
         && !cx->suppressGC
         && !cx->inUnsafeRegion
         && !cx->generationalDisabled
         && !cx->compactingDisabledCount
         && !cx->keepAtoms;
 }
 
 GCRuntime::IncrementalResult
-GCRuntime::resetIncrementalGC(gc::AbortReason reason, AutoLockForExclusiveAccess& lock)
+GCRuntime::resetIncrementalGC(gc::AbortReason reason, AutoTraceSession& session)
 {
     MOZ_ASSERT(reason != gc::AbortReason::None);
 
     switch (incrementalState) {
       case State::NotActive:
           return IncrementalResult::Ok;
 
       case State::MarkRoots:
@@ -6778,17 +6780,17 @@ GCRuntime::resetIncrementalGC(gc::AbortR
         /* Finish sweeping the current sweep group, then abort. */
         abortSweepAfterCurrentGroup = true;
 
         /* Don't perform any compaction after sweeping. */
         bool wasCompacting = isCompacting;
         isCompacting = false;
 
         auto unlimited = SliceBudget::unlimited();
-        incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
+        incrementalCollectSlice(unlimited, JS::gcreason::RESET, session);
 
         isCompacting = wasCompacting;
 
         {
             gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD);
             rt->gc.waitBackgroundSweepOrAllocEnd();
         }
         break;
@@ -6799,40 +6801,40 @@ GCRuntime::resetIncrementalGC(gc::AbortR
             gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD);
             rt->gc.waitBackgroundSweepOrAllocEnd();
         }
 
         bool wasCompacting = isCompacting;
         isCompacting = false;
 
         auto unlimited = SliceBudget::unlimited();
-        incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
+        incrementalCollectSlice(unlimited, JS::gcreason::RESET, session);
 
         isCompacting = wasCompacting;
 
         break;
       }
 
       case State::Compact: {
         bool wasCompacting = isCompacting;
 
         isCompacting = true;
         startedCompacting = true;
         zonesToMaybeCompact.ref().clear();
 
         auto unlimited = SliceBudget::unlimited();
-        incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
+        incrementalCollectSlice(unlimited, JS::gcreason::RESET, session);
 
         isCompacting = wasCompacting;
         break;
       }
 
       case State::Decommit: {
         auto unlimited = SliceBudget::unlimited();
-        incrementalCollectSlice(unlimited, JS::gcreason::RESET, lock);
+        incrementalCollectSlice(unlimited, JS::gcreason::RESET, session);
         break;
       }
     }
 
     stats().reset(reason);
 
 #ifdef DEBUG
     assertBackgroundSweepingFinished();
@@ -6914,18 +6916,25 @@ ShouldCleanUpEverything(JS::gcreason::Re
     // During shutdown, we must clean everything up, for the sake of leak
     // detection. When a runtime has no contexts, or we're doing a GC before a
     // shutdown CC, those are strong indications that we're shutting down.
     return IsShutdownGC(reason) || gckind == GC_SHRINK;
 }
 
 void
 GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason reason,
-                                   AutoLockForExclusiveAccess& lock)
-{
+                                   AutoTraceSession& session)
+{
+    /*
+     * Drop the exclusive access lock if we are in an incremental collection
+     * that does not touch the atoms zone.
+     */
+    if (isIncrementalGCInProgress() && !atomsZone->isCollecting())
+        session.maybeLock.reset();
+
     AutoGCSlice slice(rt);
 
     bool destroyingRuntime = (reason == JS::gcreason::DESTROY_RUNTIME);
 
     initialState = incrementalState;
 
 #ifdef JS_GC_ZEAL
     /*
@@ -6960,17 +6969,17 @@ GCRuntime::incrementalCollectSlice(Slice
         lastMarkSlice = false;
         rootsRemoved = false;
 
         incrementalState = State::MarkRoots;
 
         MOZ_FALLTHROUGH;
 
       case State::MarkRoots:
-        if (!beginMarkPhase(reason, lock)) {
+        if (!beginMarkPhase(reason, session)) {
             incrementalState = State::NotActive;
             return;
         }
 
         if (!destroyingRuntime)
             pushZealSelectedObjects();
 
         incrementalState = State::Mark;
@@ -7014,25 +7023,25 @@ GCRuntime::incrementalCollectSlice(Slice
              (useZeal && hasZealMode(ZealMode::IncrementalMarkAllThenFinish))))
         {
             lastMarkSlice = true;
             break;
         }
 
         incrementalState = State::Sweep;
 
-        beginSweepPhase(reason, lock);
+        beginSweepPhase(reason, session);
 
         MOZ_FALLTHROUGH;
 
       case State::Sweep:
-        if (performSweepActions(budget, lock) == NotFinished)
+        if (performSweepActions(budget) == NotFinished)
             break;
 
-        endSweepPhase(destroyingRuntime, lock);
+        endSweepPhase(destroyingRuntime);
 
         incrementalState = State::Finalize;
 
         MOZ_FALLTHROUGH;
 
       case State::Finalize:
         {
             gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::WAIT_BACKGROUND_THREAD);
@@ -7067,17 +7076,17 @@ GCRuntime::incrementalCollectSlice(Slice
 
         MOZ_FALLTHROUGH;
 
       case State::Compact:
         if (isCompacting) {
             if (!startedCompacting)
                 beginCompactPhase();
 
-            if (compactPhase(reason, budget, lock) == NotFinished)
+            if (compactPhase(reason, budget, session) == NotFinished)
                 break;
 
             endCompactPhase(reason);
         }
 
         startDecommit();
         incrementalState = State::Decommit;
 
@@ -7135,50 +7144,50 @@ CheckZoneIsScheduled(Zone* zone, JS::gcr
     }
     fflush(stderr);
     MOZ_CRASH("Zone not scheduled");
 #endif
 }
 
 GCRuntime::IncrementalResult
 GCRuntime::budgetIncrementalGC(bool nonincrementalByAPI, JS::gcreason::Reason reason,
-                               SliceBudget& budget, AutoLockForExclusiveAccess& lock)
+                               SliceBudget& budget, AutoTraceSession& session)
 {
     if (nonincrementalByAPI) {
         stats().nonincremental(gc::AbortReason::NonIncrementalRequested);
         budget.makeUnlimited();
 
         // Reset any in progress incremental GC if this was triggered via the
         // API. This isn't required for correctness, but sometimes during tests
         // the caller expects this GC to collect certain objects, and we need
         // to make sure to collect everything possible.
         if (reason != JS::gcreason::ALLOC_TRIGGER)
-            return resetIncrementalGC(gc::AbortReason::NonIncrementalRequested, lock);
+            return resetIncrementalGC(gc::AbortReason::NonIncrementalRequested, session);
 
         return IncrementalResult::Ok;
     }
 
     if (reason == JS::gcreason::ABORT_GC) {
         budget.makeUnlimited();
         stats().nonincremental(gc::AbortReason::AbortRequested);
-        return resetIncrementalGC(gc::AbortReason::AbortRequested, lock);
+        return resetIncrementalGC(gc::AbortReason::AbortRequested, session);
     }
 
     AbortReason unsafeReason = IsIncrementalGCUnsafe(rt);
     if (unsafeReason == AbortReason::None) {
         if (reason == JS::gcreason::COMPARTMENT_REVIVED)
             unsafeReason = gc::AbortReason::CompartmentRevived;
         else if (mode != JSGC_MODE_INCREMENTAL)
             unsafeReason = gc::AbortReason::ModeChange;
     }
 
     if (unsafeReason != AbortReason::None) {
         budget.makeUnlimited();
         stats().nonincremental(unsafeReason);
-        return resetIncrementalGC(unsafeReason, lock);
+        return resetIncrementalGC(unsafeReason, session);
     }
 
     if (mallocCounter.shouldTriggerGC(tunables) == NonIncrementalTrigger) {
         budget.makeUnlimited();
         stats().nonincremental(AbortReason::MallocBytesTrigger);
     }
 
     bool reset = false;
@@ -7198,17 +7207,17 @@ GCRuntime::budgetIncrementalGC(bool noni
             stats().nonincremental(AbortReason::MallocBytesTrigger);
         }
 
         if (isIncrementalGCInProgress() && zone->isGCScheduled() != zone->wasGCStarted())
             reset = true;
     }
 
     if (reset)
-        return resetIncrementalGC(AbortReason::ZoneChange, lock);
+        return resetIncrementalGC(AbortReason::ZoneChange, session);
 
     return IncrementalResult::Ok;
 }
 
 namespace {
 
 class AutoScheduleZonesForGC
 {
@@ -7344,27 +7353,27 @@ GCRuntime::gcCycle(bool nonincrementalBy
         // for it at the start of every slice.
         allocTask.cancel(GCParallelTask::CancelAndWait);
     }
 
     // We don't allow off-thread parsing to start while we're doing an
     // incremental GC.
     MOZ_ASSERT_IF(rt->activeGCInAtomsZone(), !rt->hasHelperThreadZones());
 
-    auto result = budgetIncrementalGC(nonincrementalByAPI, reason, budget, session.lock);
+    auto result = budgetIncrementalGC(nonincrementalByAPI, reason, budget, session);
 
     // If an ongoing incremental GC was reset, we may need to restart.
     if (result == IncrementalResult::Reset) {
         MOZ_ASSERT(!isIncrementalGCInProgress());
         return result;
     }
 
     TraceMajorGCStart();
 
-    incrementalCollectSlice(budget, reason, session.lock);
+    incrementalCollectSlice(budget, reason, session);
 
     chunkAllocationSinceLastGC = false;
 
 #ifdef JS_GC_ZEAL
     /* Keeping these around after a GC is dangerous. */
     clearSelectedForMarking();
 #endif
 
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -11,17 +11,16 @@
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Unused.h"
 
 #include "jsarray.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsobj.h"
 #include "jsopcode.h"
 #include "jsscript.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "ds/Sort.h"
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -24,17 +24,16 @@
 
 #include "fdlibm.h"
 
 #ifdef XP_WIN
 # include "jswin.h"
 #endif
 
 #include "jsapi.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jslibmath.h"
 #include "jstypes.h"
 
 #include "jit/InlinableNatives.h"
 #include "js/Class.h"
 #include "vm/Time.h"
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -16,29 +16,26 @@
 #include "mozilla/RangedPtr.h"
 
 #ifdef HAVE_LOCALECONV
 #include <locale.h>
 #endif
 #include <math.h>
 #include <string.h>
 
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsdtoa.h"
 #include "jsobj.h"
 #include "jsstr.h"
 #include "jstypes.h"
 
 #include "js/Conversions.h"
 #include "vm/GlobalObject.h"
 #include "vm/StringBuffer.h"
 
-#include "jsatominlines.h"
-
 #include "vm/NativeObject-inl.h"
 #include "vm/NumberObject-inl.h"
 #include "vm/String-inl.h"
 
 using namespace js;
 
 using mozilla::Abs;
 using mozilla::ArrayLength;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -14,17 +14,16 @@
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/TemplateLib.h"
 
 #include <string.h>
 
 #include "jsapi.h"
 #include "jsarray.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsexn.h"
 #include "jsfriendapi.h"
 #include "jsfun.h"
 #include "jsiter.h"
 #include "jsnum.h"
 #include "jsopcode.h"
 #include "jsprf.h"
@@ -47,17 +46,16 @@
 #include "js/UniquePtr.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/Interpreter.h"
 #include "vm/ProxyObject.h"
 #include "vm/RegExpStaticsObject.h"
 #include "vm/Shape.h"
 #include "vm/TypedArrayObject.h"
 
-#include "jsatominlines.h"
 #include "jsboolinlines.h"
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
 
 #include "builtin/TypedObject-inl.h"
 #include "gc/Marking-inl.h"
 #include "vm/ArrayObject-inl.h"
 #include "vm/BooleanObject-inl.h"
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -6,30 +6,28 @@
 
 #include "json.h"
 
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Range.h"
 #include "mozilla/ScopeExit.h"
 
 #include "jsarray.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsstr.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "vm/Interpreter.h"
 #include "vm/JSONParser.h"
 #include "vm/StringBuffer.h"
 
 #include "jsarrayinlines.h"
-#include "jsatominlines.h"
 #include "jsboolinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::IsFinite;
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -18,17 +18,16 @@
 
 #include <algorithm>
 #include <ctype.h>
 #include <inttypes.h>
 #include <stdio.h>
 #include <string.h>
 
 #include "jsapi.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jsfun.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsprf.h"
 #include "jsscript.h"
 #include "jsstr.h"
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -18,17 +18,16 @@
 #include "mozilla/Sprintf.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Vector.h"
 
 #include <algorithm>
 #include <string.h>
 
 #include "jsapi.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsfun.h"
 #include "jsobj.h"
 #include "jsopcode.h"
 #include "jsprf.h"
 #include "jstypes.h"
 #include "jsutil.h"
 #include "jswrapper.h"
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -10,17 +10,16 @@
 #define jsscript_h
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Variant.h"
 
-#include "jsatom.h"
 #include "jsopcode.h"
 #include "jstypes.h"
 
 #include "frontend/NameAnalysisTypes.h"
 #include "gc/Barrier.h"
 #include "gc/Rooting.h"
 #include "jit/IonCode.h"
 #include "js/UbiNode.h"
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -17,17 +17,16 @@
 #include "mozilla/Unused.h"
 
 #include <ctype.h>
 #include <limits>
 #include <string.h>
 
 #include "jsapi.h"
 #include "jsarray.h"
-#include "jsatom.h"
 #include "jsbool.h"
 #include "jscntxt.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsopcode.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -14,17 +14,16 @@
 #include "jscntxt.h"
 #include "jsfun.h"
 #include "jswrapper.h"
 
 #include "proxy/DeadObjectProxy.h"
 #include "proxy/ScriptedProxyHandler.h"
 #include "vm/WrapperObject.h"
 
-#include "jsatominlines.h"
 #include "jsobjinlines.h"
 
 #include "gc/Marking-inl.h"
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
--- a/js/src/proxy/SecurityWrapper.cpp
+++ b/js/src/proxy/SecurityWrapper.cpp
@@ -1,18 +1,21 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jsapi.h"
+#include "jsfriendapi.h"
 #include "jswrapper.h"
 
-#include "jsatominlines.h"
+#include "NamespaceImports.h"
+
+#include "vm/String.h"
 
 using namespace js;
 
 template <class Base>
 bool
 SecurityWrapper<Base>::enter(JSContext* cx, HandleObject wrapper, HandleId id,
                              Wrapper::Action act, bool mayThrow, bool* bp) const
 {
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -47,17 +47,16 @@
 # include <sys/mman.h>
 # include <sys/stat.h>
 # include <sys/wait.h>
 # include <unistd.h>
 #endif
 
 #include "jsapi.h"
 #include "jsarray.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsfriendapi.h"
 #include "jsfun.h"
 #include "jsobj.h"
 #include "jsprf.h"
 #include "jsscript.h"
 #include "jstypes.h"
 #include "jsutil.h"
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Intl/tolower-ascii-equivalent.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// Language tags are processed case-insensitive, but unconditionally calling
+// the built-in String.prototype.toLowerCase() or toUpperCase() function
+// before parsing a language tag can map non-ASCII characters into the ASCII
+// range.
+//
+// Validate the BCP47 language tag parser handles this case (pun intended)
+// correctly by passing language tags which contain U+212A (KELVIN SIGN) and
+// U+0131 (LATIN SMALL LETTER DOTLESS I) to Intl.getCanonicalLocales().
+
+// The lower-case form of "i-ha\u212A" is "i-hak".
+assertEq("i-hak", "i-ha\u212A".toLowerCase());
+
+// The upper-case form of "\u0131-hak" is "I-HAK".
+assertEq("I-HAK", "\u0131-hak".toUpperCase());
+
+// "i-hak" is a valid language tag.
+assertEqArray(Intl.getCanonicalLocales("i-hak"), ["hak"]);
+
+// But "i-ha\u212A" is not a valid language tag.
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("i-ha\u212A"), RangeError);
+
+// And "\u0131-hak" is also not a valid language tag.
+assertThrowsInstanceOf(() => Intl.getCanonicalLocales("\u0131-hak"), RangeError);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Intl/uppercase-privateuse.js
@@ -0,0 +1,8 @@
+// |reftest| skip-if(!this.hasOwnProperty("Intl"))
+
+// privateuse subtags can start with upper-case 'X'.
+assertEqArray(Intl.getCanonicalLocales("de-X-a-a"), ["de-x-a-a"]);
+assertEqArray(Intl.getCanonicalLocales("X-a-a"), ["x-a-a"]);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -45,18 +45,16 @@
 #include "js/MemoryMetrics.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/WrapperObject.h"
 #include "wasm/WasmSignalHandlers.h"
 #include "wasm/WasmTypes.h"
 
-#include "jsatominlines.h"
-
 #include "gc/Marking-inl.h"
 #include "gc/Nursery-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/Shape-inl.h"
 
 using JS::ToInt32;
 
 using mozilla::DebugOnly;
new file mode 100644
--- /dev/null
+++ b/js/src/vm/AtomsTable.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implementation details of the atoms table.
+ */
+
+#ifndef vm_AtomsTable_h
+#define vm_AtomsTable_h
+
+#include "js/GCHashTable.h"
+
+class JSAtom;
+
+namespace js {
+
+class AtomStateEntry
+{
+    uintptr_t bits;
+
+    static const uintptr_t NO_TAG_MASK = uintptr_t(-1) - 1;
+
+  public:
+    AtomStateEntry() : bits(0) {}
+    AtomStateEntry(const AtomStateEntry& other) : bits(other.bits) {}
+    AtomStateEntry(JSAtom* ptr, bool tagged)
+      : bits(uintptr_t(ptr) | uintptr_t(tagged))
+    {
+        MOZ_ASSERT((uintptr_t(ptr) & 0x1) == 0);
+    }
+
+    bool isPinned() const {
+        return bits & 0x1;
+    }
+
+    /*
+     * Non-branching code sequence. Note that the const_cast is safe because
+     * the hash function doesn't consider the tag to be a portion of the key.
+     */
+    void setPinned(bool pinned) const {
+        const_cast<AtomStateEntry*>(this)->bits |= uintptr_t(pinned);
+    }
+
+    JSAtom* asPtrUnbarriered() const {
+        MOZ_ASSERT(bits);
+        return reinterpret_cast<JSAtom*>(bits & NO_TAG_MASK);
+    }
+
+    JSAtom* asPtr(JSContext* cx) const;
+
+    bool needsSweep() {
+        JSAtom* atom = asPtrUnbarriered();
+        return gc::IsAboutToBeFinalizedUnbarriered(&atom);
+    }
+};
+
+struct AtomHasher
+{
+    struct Lookup;
+    static inline HashNumber hash(const Lookup& l);
+    static MOZ_ALWAYS_INLINE bool match(const AtomStateEntry& entry, const Lookup& lookup);
+    static void rekey(AtomStateEntry& k, const AtomStateEntry& newKey) { k = newKey; }
+};
+
+using AtomSet = JS::GCHashSet<AtomStateEntry, AtomHasher, SystemAllocPolicy>;
+
+// This class is a wrapper for AtomSet that is used to ensure the AtomSet is
+// not modified. It should only expose read-only methods from AtomSet.
+// Note however that the atoms within the table can be marked during GC.
+class FrozenAtomSet
+{
+    AtomSet* mSet;
+
+  public:
+    // This constructor takes ownership of the passed-in AtomSet.
+    explicit FrozenAtomSet(AtomSet* set) { mSet = set; }
+
+    ~FrozenAtomSet() { js_delete(mSet); }
+
+    MOZ_ALWAYS_INLINE AtomSet::Ptr readonlyThreadsafeLookup(const AtomSet::Lookup& l) const;
+
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
+        return mSet->sizeOfIncludingThis(mallocSizeOf);
+    }
+
+    typedef AtomSet::Range Range;
+
+    AtomSet::Range all() const { return mSet->all(); }
+};
+
+} // namespace js
+
+#endif /* vm_AtomTables_h */
--- a/js/src/vm/Caches.h
+++ b/js/src/vm/Caches.h
@@ -2,17 +2,16 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef vm_Caches_h
 #define vm_Caches_h
 
-#include "jsatom.h"
 #include "jsbytecode.h"
 #include "jsmath.h"
 #include "jsobj.h"
 #include "jsscript.h"
 
 #include "frontend/SourceNotes.h"
 #include "gc/Tracer.h"
 #include "js/RootingAPI.h"
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -17,17 +17,16 @@
 #include "vm/ArgumentsObject.h"
 #include "vm/AsyncFunction.h"
 #include "vm/GlobalObject.h"
 #include "vm/ProxyObject.h"
 #include "vm/Shape.h"
 #include "vm/Xdr.h"
 #include "wasm/WasmInstance.h"
 
-#include "jsatominlines.h"
 #include "jsscriptinlines.h"
 
 #include "gc/Marking-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/Stack-inl.h"
 #include "vm/TypeInference-inl.h"
 
 using namespace js;
--- a/js/src/vm/GeneratorObject.cpp
+++ b/js/src/vm/GeneratorObject.cpp
@@ -3,17 +3,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/. */
 
 #include "vm/GeneratorObject.h"
 
 #include "jsobj.h"
 
-#include "jsatominlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/ArrayObject-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
 
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -2086,18 +2086,26 @@ js::StartOffThreadPromiseHelperTask(Prom
     if (!HelperThreadState().promiseHelperTasks(lock).append(task))
         return false;
 
     HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
     return true;
 }
 
 void
-GlobalHelperThreadState::trace(JSTracer* trc)
+GlobalHelperThreadState::trace(JSTracer* trc, gc::AutoTraceSession& session)
 {
+    // There's an assertion that requires the exclusive access lock when tracing
+    // atoms (see AtomIsPinnedInRuntime). Due to mutex ordering requirements we
+    // need to take that lock before the helper thread lock, if we don't have it
+    // already.
+    Maybe<AutoLockForExclusiveAccess> exclusiveLock;
+    if (!session.maybeLock.isSome())
+        exclusiveLock.emplace(trc->runtime());
+
     AutoLockHelperThreadState lock;
     for (auto builder : ionWorklist(lock))
         builder->trace(trc);
     for (auto builder : ionFinishedList(lock))
         builder->trace(trc);
 
     if (HelperThreadState().threads) {
         for (auto& helper : *HelperThreadState().threads) {
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -293,17 +293,17 @@ class GlobalHelperThreadState
     bool finishParseTask(JSContext* cx, ParseTaskKind kind, void* token, MutableHandle<ScriptVector> scripts);
 
     void cancelParseTask(JSRuntime* rt, ParseTaskKind kind, void* token);
 
     void mergeParseTaskCompartment(JSContext* cx, ParseTask* parseTask,
                                    Handle<GlobalObject*> global,
                                    JSCompartment* dest);
 
-    void trace(JSTracer* trc);
+    void trace(JSTracer* trc, js::gc::AutoTraceSession& session);
 
     JSScript* finishScriptParseTask(JSContext* cx, void* token);
     JSScript* finishScriptDecodeTask(JSContext* cx, void* token);
     bool finishMultiScriptsDecodeTask(JSContext* cx, void* token, MutableHandle<ScriptVector> scripts);
     JSObject* finishModuleParseTask(JSContext* cx, void* token);
 
     bool hasActiveThreads(const AutoLockHelperThreadState&);
     void waitForAllThreadsLocked(AutoLockHelperThreadState&);
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -11,17 +11,16 @@
 
 #include "jscompartment.h"
 #include "jsnum.h"
 #include "jsstr.h"
 
 #include "jit/Ion.h"
 #include "vm/ArgumentsObject.h"
 
-#include "jsatominlines.h"
 #include "jsobjinlines.h"
 
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/Stack-inl.h"
 #include "vm/String-inl.h"
 #include "vm/UnboxedObject-inl.h"
 
 namespace js {
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -15,17 +15,16 @@
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Sprintf.h"
 
 #include <string.h>
 
 #include "jsarray.h"
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jsfun.h"
 #include "jsiter.h"
 #include "jslibmath.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsopcode.h"
 #include "jsprf.h"
@@ -44,17 +43,16 @@
 #include "vm/GeneratorObject.h"
 #include "vm/Opcodes.h"
 #include "vm/Scope.h"
 #include "vm/Shape.h"
 #include "vm/Stopwatch.h"
 #include "vm/StringBuffer.h"
 #include "vm/TraceLogging.h"
 
-#include "jsatominlines.h"
 #include "jsboolinlines.h"
 #include "jsfuninlines.h"
 #include "jsscriptinlines.h"
 
 #include "jit/JitFrames-inl.h"
 #include "vm/Debugger-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 #include "vm/GeckoProfiler-inl.h"
--- a/js/src/vm/RegExpShared.h
+++ b/js/src/vm/RegExpShared.h
@@ -11,17 +11,16 @@
 
 #ifndef vm_RegExpShared_h
 #define vm_RegExpShared_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/MemoryReporting.h"
 
 #include "jsalloc.h"
-#include "jsatom.h"
 
 #include "builtin/SelfHostingDefines.h"
 #include "gc/Barrier.h"
 #include "gc/Heap.h"
 #include "gc/Marking.h"
 #include "js/UbiNode.h"
 #include "js/Vector.h"
 #include "vm/ArrayObject.h"
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -19,17 +19,16 @@
 
 #include <locale.h>
 #include <string.h>
 
 #ifdef JS_CAN_CHECK_THREADSAFE_ACCESSES
 # include <sys/mman.h>
 #endif
 
-#include "jsatom.h"
 #include "jsmath.h"
 #include "jsobj.h"
 #include "jsscript.h"
 #include "jswin.h"
 #include "jswrapper.h"
 
 #include "builtin/Promise.h"
 #include "gc/FreeOp.h"
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -16,17 +16,16 @@
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/Vector.h"
 
 #include <setjmp.h>
 
-#include "jsatom.h"
 #include "jsscript.h"
 
 #include "builtin/AtomicsObject.h"
 #include "builtin/intl/SharedIntlData.h"
 #include "builtin/Promise.h"
 #include "frontend/NameCollections.h"
 #include "gc/GCRuntime.h"
 #include "gc/Tracer.h"
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -51,17 +51,16 @@
 #include "vm/Interpreter.h"
 #include "vm/Printer.h"
 #include "vm/RegExpObject.h"
 #include "vm/String.h"
 #include "vm/StringBuffer.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/WrapperObject.h"
 
-#include "jsatominlines.h"
 #include "jsfuninlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "gc/Iteration-inl.h"
 #include "vm/BooleanObject-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/NumberObject-inl.h"
--- a/js/src/vm/Shape-inl.h
+++ b/js/src/vm/Shape-inl.h
@@ -12,17 +12,16 @@
 #include "mozilla/TypeTraits.h"
 
 #include "jsobj.h"
 
 #include "gc/Allocator.h"
 #include "vm/Interpreter.h"
 #include "vm/TypedArrayObject.h"
 
-#include "jsatominlines.h"
 #include "jscntxtinlines.h"
 #include "gc/Marking-inl.h"
 
 namespace js {
 
 inline
 AutoKeepShapeTables::AutoKeepShapeTables(JSContext* cx)
   : cx_(cx),
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -7,17 +7,16 @@
 /* JS symbol tables. */
 
 #include "vm/Shape-inl.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/PodOperations.h"
 
-#include "jsatom.h"
 #include "jscntxt.h"
 #include "jshashutil.h"
 #include "jsobj.h"
 
 #include "gc/FreeOp.h"
 #include "gc/Policy.h"
 #include "js/HashTable.h"
 
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -29,17 +29,16 @@
 #include "js/MemoryMetrics.h"
 #include "vm/HelperThreads.h"
 #include "vm/Opcodes.h"
 #include "vm/Printer.h"
 #include "vm/Shape.h"
 #include "vm/Time.h"
 #include "vm/UnboxedObject.h"
 
-#include "jsatominlines.h"
 #include "jsscriptinlines.h"
 
 #include "gc/Iteration-inl.h"
 #include "gc/Marking-inl.h"
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 using namespace js::gc;
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -39,18 +39,16 @@
 #include "vm/ArrayBufferObject.h"
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/PIC.h"
 #include "vm/SelfHosting.h"
 #include "vm/SharedMem.h"
 #include "vm/WrapperObject.h"
 
-#include "jsatominlines.h"
-
 #include "gc/Nursery-inl.h"
 #include "gc/StoreBuffer-inl.h"
 #include "vm/ArrayBufferObject-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/Shape-inl.h"
 
 using namespace js;
 using namespace js::gc;
@@ -314,16 +312,21 @@ NewArray(JSContext* cx, uint32_t nelemen
 
 namespace {
 
 enum class SpeciesConstructorOverride {
     None,
     ArrayBuffer
 };
 
+enum class CreateSingleton {
+    Yes,
+    No
+};
+
 template<typename NativeType>
 class TypedArrayObjectTemplate : public TypedArrayObject
 {
     friend class TypedArrayObject;
 
   public:
     static constexpr Scalar::Type ArrayTypeID() { return TypeIDOfType<NativeType>::id; }
     static bool ArrayTypeIsUnsigned() { return TypeIsUnsigned<NativeType>(); }
@@ -402,20 +405,21 @@ class TypedArrayObjectTemplate : public 
     {
         MOZ_ASSERT(proto);
 
         JSObject* obj = NewObjectWithClassProto(cx, instanceClass(), proto, allocKind);
         return obj ? &obj->as<TypedArrayObject>() : nullptr;
     }
 
     static TypedArrayObject*
-    makeTypedInstance(JSContext* cx, uint32_t len, gc::AllocKind allocKind)
+    makeTypedInstance(JSContext* cx, uint32_t len, CreateSingleton createSingleton,
+                      gc::AllocKind allocKind)
     {
         const Class* clasp = instanceClass();
-        if (len * sizeof(NativeType) >= TypedArrayObject::SINGLETON_BYTE_LENGTH) {
+        if (createSingleton == CreateSingleton::Yes) {
             JSObject* obj = NewBuiltinClassInstance(cx, clasp, allocKind, SingletonObject);
             if (!obj)
                 return nullptr;
             return &obj->as<TypedArrayObject>();
         }
 
         jsbytecode* pc;
         RootedScript script(cx, cx->currentScript(&pc));
@@ -431,18 +435,19 @@ class TypedArrayObjectTemplate : public 
         {
             return nullptr;
         }
 
         return &obj->as<TypedArrayObject>();
     }
 
     static TypedArrayObject*
-    makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer, uint32_t byteOffset,
-                 uint32_t len, HandleObject proto)
+    makeInstance(JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
+                 CreateSingleton createSingleton, uint32_t byteOffset, uint32_t len,
+                 HandleObject proto)
     {
         MOZ_ASSERT_IF(!buffer, byteOffset == 0);
         MOZ_ASSERT_IF(buffer, !buffer->isDetached());
         MOZ_ASSERT(len < INT32_MAX / sizeof(NativeType));
 
         gc::AllocKind allocKind = buffer
                                   ? GetGCObjectKind(instanceClass())
                                   : AllocKindForLazyBuffer(len * sizeof(NativeType));
@@ -458,17 +463,17 @@ class TypedArrayObjectTemplate : public 
                 return nullptr;
         }
 
         AutoSetNewObjectMetadata metadata(cx);
         Rooted<TypedArrayObject*> obj(cx);
         if (proto && proto != checkProto)
             obj = makeProtoInstance(cx, proto, allocKind);
         else
-            obj = makeTypedInstance(cx, len, allocKind);
+            obj = makeTypedInstance(cx, len, createSingleton, allocKind);
         if (!obj)
             return nullptr;
 
         bool isSharedMemory = buffer && IsSharedArrayBuffer(buffer.get());
 
         obj->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectOrNullValue(buffer));
         // This is invariant.  Self-hosting code that sets BUFFER_SLOT
         // (if it does) must maintain it, should it need to.
@@ -831,18 +836,22 @@ class TypedArrayObjectTemplate : public 
     fromBufferSameCompartment(JSContext* cx, HandleArrayBufferObjectMaybeShared buffer,
                               uint64_t byteOffset, uint64_t lengthIndex, HandleObject proto)
     {
         // Steps 9-12.
         uint32_t length;
         if (!computeAndCheckLength(cx, buffer, byteOffset, lengthIndex, &length))
             return nullptr;
 
+        CreateSingleton createSingleton = CreateSingleton::No;
+        if (length * sizeof(NativeType) >= TypedArrayObject::SINGLETON_BYTE_LENGTH)
+            createSingleton = CreateSingleton::Yes;
+
         // Steps 13-17.
-        return makeInstance(cx, buffer, uint32_t(byteOffset), length, proto);
+        return makeInstance(cx, buffer, createSingleton, uint32_t(byteOffset), length, proto);
     }
 
     // Create a TypedArray object in another compartment.
     //
     // ES6 supports creating a TypedArray in global A (using global A's
     // TypedArray constructor) backed by an ArrayBuffer created in global B.
     //
     // Our TypedArrayObject implementation doesn't support a TypedArray in
@@ -890,17 +899,18 @@ class TypedArrayObjectTemplate : public 
         {
             JSAutoCompartment ac(cx, unwrappedBuffer);
 
             RootedObject wrappedProto(cx, protoRoot);
             if (!cx->compartment()->wrap(cx, &wrappedProto))
                 return nullptr;
 
             typedArray =
-                makeInstance(cx, unwrappedBuffer, uint32_t(byteOffset), length, wrappedProto);
+                makeInstance(cx, unwrappedBuffer, CreateSingleton::No, uint32_t(byteOffset),
+                             length, wrappedProto);
             if (!typedArray)
                 return nullptr;
         }
 
         if (!cx->compartment()->wrap(cx, &typedArray))
             return nullptr;
 
         return typedArray;
@@ -965,17 +975,17 @@ class TypedArrayObjectTemplate : public 
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
             return nullptr;
         }
 
         Rooted<ArrayBufferObject*> buffer(cx);
         if (!maybeCreateArrayBuffer(cx, uint32_t(nelements), BYTES_PER_ELEMENT, nullptr, &buffer))
             return nullptr;
 
-        return makeInstance(cx, buffer, 0, uint32_t(nelements), proto);
+        return makeInstance(cx, buffer, CreateSingleton::No, 0, uint32_t(nelements), proto);
     }
 
     static bool
     AllocateArrayBuffer(JSContext* cx, HandleObject ctor,
                         uint32_t count, uint32_t unit,
                         MutableHandle<ArrayBufferObject*> buffer);
 
     static JSObject*
@@ -1221,17 +1231,18 @@ TypedArrayObjectTemplate<T>::fromTypedAr
 
     // Step 19.b or 24.1.1.4 step 4.
     if (srcArray->hasDetachedBuffer()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
         return nullptr;
     }
 
     // Steps 3-4 (remaining part), 20-23.
-    Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, elementLength, proto));
+    Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, CreateSingleton::No, 0,
+                                                   elementLength, proto));
     if (!obj)
         return nullptr;
 
     // Steps 19.c-f or 24.1.1.4 steps 5-7.
     MOZ_ASSERT(!obj->isSharedMemory());
     if (isShared) {
         if (!ElementSpecific<T, SharedOps>::setFromTypedArray(obj, srcArray, 0))
             return nullptr;
@@ -1281,17 +1292,18 @@ TypedArrayObjectTemplate<T>::fromObject(
         // Step 6.b.
         uint32_t len = array->getDenseInitializedLength();
 
         // Step 6.c.
         Rooted<ArrayBufferObject*> buffer(cx);
         if (!maybeCreateArrayBuffer(cx, len, BYTES_PER_ELEMENT, nullptr, &buffer))
             return nullptr;
 
-        Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len, proto));
+        Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, CreateSingleton::No, 0,
+                                                       len, proto));
         if (!obj)
             return nullptr;
 
         // Steps 6.d-e.
         MOZ_ASSERT(!obj->isSharedMemory());
         if (!ElementSpecific<T, UnsharedOps>::initFromIterablePackedArray(cx, obj, array))
             return nullptr;
 
@@ -1348,17 +1360,18 @@ TypedArrayObjectTemplate<T>::fromObject(
     if (!GetLengthProperty(cx, arrayLike, &len))
         return nullptr;
 
     // Step 10.
     Rooted<ArrayBufferObject*> buffer(cx);
     if (!maybeCreateArrayBuffer(cx, len, BYTES_PER_ELEMENT, nullptr, &buffer))
         return nullptr;
 
-    Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, len, proto));
+    Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, CreateSingleton::No, 0, len,
+                                                   proto));
     if (!obj)
         return nullptr;
 
     // Steps 11-12.
     MOZ_ASSERT(!obj->isSharedMemory());
     if (!ElementSpecific<T, UnsharedOps>::setFromNonTypedArray(cx, obj, arrayLike, len))
         return nullptr;
 
--- a/js/src/vm/TypedArrayObject.h
+++ b/js/src/vm/TypedArrayObject.h
@@ -181,18 +181,19 @@ class TypedArrayObject : public NativeOb
 
     void notifyBufferDetached(JSContext* cx, void* newData);
 
     static bool
     GetTemplateObjectForNative(JSContext* cx, Native native, uint32_t len,
                                MutableHandleObject res);
 
     /*
-     * Byte length above which created typed arrays and data views will have
-     * singleton types regardless of the context in which they are created.
+     * Byte length above which created typed arrays will have singleton types
+     * regardless of the context in which they are created. This only applies to
+     * typed arrays created with an existing ArrayBuffer.
      */
     static const uint32_t SINGLETON_BYTE_LENGTH = 1024 * 1024 * 10;
 
     static bool isOriginalLengthGetter(Native native);
 
     ArrayBufferObject* bufferUnshared() const {
         MOZ_ASSERT(!isSharedMemory());
         JSObject* obj = bufferValue(const_cast<TypedArrayObject*>(this)).toObjectOrNull();
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -935,16 +935,20 @@ js::wasm::StartUnwinding(const RegisterS
             // frame is incomplete. During profiling frame iteration, it means
             // that the jit profiling frame iterator won't be able to unwind
             // this frame; drop it.
             return false;
         }
 #endif
         fixedFP = offsetFromEntry < SetJitEntryFP ? (Frame*) sp : fp;
         fixedPC = nullptr;
+
+        // On the error return path, FP might be set to FailFP. Ignore these transient frames.
+        if (intptr_t(fixedFP) == (FailFP & ~JitActivation::ExitFpWasmBit))
+            return false;
         break;
       case CodeRange::Throw:
         // The throw stub executes a small number of instructions before popping
         // the entire activation. To simplify testing, we simply pretend throw
         // stubs have already popped the entire stack.
         return false;
       case CodeRange::Interrupt:
         // When the PC is in the async interrupt stub, the fp may be garbage and
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -25,18 +25,16 @@
 
 #include "jit/JitOptions.h"
 #include "threading/LockGuard.h"
 #include "wasm/WasmCompile.h"
 #include "wasm/WasmInstance.h"
 #include "wasm/WasmJS.h"
 #include "wasm/WasmSerialize.h"
 
-#include "jsatominlines.h"
-
 #include "vm/ArrayBufferObject-inl.h"
 #include "vm/Debugger-inl.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::IsNaN;
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -617,21 +617,19 @@ xpc::IsSandbox(JSObject* obj)
 nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox()
 {
 }
 
 nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox()
 {
 }
 
-NS_INTERFACE_MAP_BEGIN(nsXPCComponents_utils_Sandbox)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_utils_Sandbox)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_utils_Sandbox)
-NS_INTERFACE_MAP_END
+NS_IMPL_QUERY_INTERFACE(nsXPCComponents_utils_Sandbox,
+                        nsIXPCComponents_utils_Sandbox,
+                        nsIXPCScriptable)
 
 NS_IMPL_ADDREF(nsXPCComponents_utils_Sandbox)
 NS_IMPL_RELEASE(nsXPCComponents_utils_Sandbox)
 
 // We use the nsIXPScriptable macros to generate lots of stuff for us.
 #define XPC_MAP_CLASSNAME         nsXPCComponents_utils_Sandbox
 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_utils_Sandbox"
 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | \
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -182,25 +182,20 @@ nsXPCComponents_Interfaces::nsXPCCompone
 }
 
 nsXPCComponents_Interfaces::~nsXPCComponents_Interfaces()
 {
     // empty
 }
 
 
-NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Interfaces)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Interfaces)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Interfaces)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(nsXPCComponents_Interfaces)
-NS_IMPL_RELEASE(nsXPCComponents_Interfaces)
+NS_IMPL_ISUPPORTS(nsXPCComponents_Interfaces,
+                  nsIXPCComponents_Interfaces,
+                  nsIXPCScriptable,
+                  nsIClassInfo);
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsXPCComponents_Interfaces
 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Interfaces"
 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \
                        XPC_SCRIPTABLE_WANT_NEWENUMERATE | \
                        XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
 #include "xpc_map_end.h" /* This will #undef the above */
@@ -386,25 +381,20 @@ nsXPCComponents_InterfacesByID::nsXPCCom
 {
 }
 
 nsXPCComponents_InterfacesByID::~nsXPCComponents_InterfacesByID()
 {
     // empty
 }
 
-NS_INTERFACE_MAP_BEGIN(nsXPCComponents_InterfacesByID)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_InterfacesByID)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_InterfacesByID)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(nsXPCComponents_InterfacesByID)
-NS_IMPL_RELEASE(nsXPCComponents_InterfacesByID)
+NS_IMPL_ISUPPORTS(nsXPCComponents_InterfacesByID,
+                  nsIXPCComponents_InterfacesByID,
+                  nsIXPCScriptable,
+                  nsIClassInfo)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsXPCComponents_InterfacesByID
 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_InterfacesByID"
 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \
                        XPC_SCRIPTABLE_WANT_NEWENUMERATE | \
                        XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
 #include "xpc_map_end.h" /* This will #undef the above */
@@ -590,25 +580,20 @@ nsXPCComponents_Classes::nsXPCComponents
 {
 }
 
 nsXPCComponents_Classes::~nsXPCComponents_Classes()
 {
     // empty
 }
 
-NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Classes)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Classes)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Classes)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(nsXPCComponents_Classes)
-NS_IMPL_RELEASE(nsXPCComponents_Classes)
+NS_IMPL_ISUPPORTS(nsXPCComponents_Classes,
+                  nsIXPCComponents_Classes,
+                  nsIXPCScriptable,
+                  nsIClassInfo)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsXPCComponents_Classes
 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Classes"
 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \
                        XPC_SCRIPTABLE_WANT_NEWENUMERATE | \
                        XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
 #include "xpc_map_end.h" /* This will #undef the above */
@@ -784,25 +769,20 @@ nsXPCComponents_ClassesByID::nsXPCCompon
 {
 }
 
 nsXPCComponents_ClassesByID::~nsXPCComponents_ClassesByID()
 {
     // empty
 }
 
-NS_INTERFACE_MAP_BEGIN(nsXPCComponents_ClassesByID)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_ClassesByID)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_ClassesByID)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(nsXPCComponents_ClassesByID)
-NS_IMPL_RELEASE(nsXPCComponents_ClassesByID)
+NS_IMPL_ISUPPORTS(nsXPCComponents_ClassesByID,
+                  nsIXPCComponents_ClassesByID,
+                  nsIXPCScriptable,
+                  nsIClassInfo)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsXPCComponents_ClassesByID
 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_ClassesByID"
 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \
                        XPC_SCRIPTABLE_WANT_NEWENUMERATE | \
                        XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
 #include "xpc_map_end.h" /* This will #undef the above */
@@ -995,25 +975,20 @@ nsXPCComponents_Results::nsXPCComponents
 {
 }
 
 nsXPCComponents_Results::~nsXPCComponents_Results()
 {
     // empty
 }
 
-NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Results)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Results)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Results)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(nsXPCComponents_Results)
-NS_IMPL_RELEASE(nsXPCComponents_Results)
+NS_IMPL_ISUPPORTS(nsXPCComponents_Results,
+                  nsIXPCComponents_Results,
+                  nsIXPCScriptable,
+                  nsIClassInfo)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsXPCComponents_Results
 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Results"
 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \
                        XPC_SCRIPTABLE_WANT_NEWENUMERATE | \
                        XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
 #include "xpc_map_end.h" /* This will #undef the above */
@@ -1162,25 +1137,20 @@ nsXPCComponents_ID::nsXPCComponents_ID()
 {
 }
 
 nsXPCComponents_ID::~nsXPCComponents_ID()
 {
     // empty
 }
 
-NS_INTERFACE_MAP_BEGIN(nsXPCComponents_ID)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_ID)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_ID)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(nsXPCComponents_ID)
-NS_IMPL_RELEASE(nsXPCComponents_ID)
+NS_IMPL_ISUPPORTS(nsXPCComponents_ID,
+                  nsIXPCComponents_ID,
+                  nsIXPCScriptable,
+                  nsIClassInfo)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsXPCComponents_ID
 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_ID"
 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | \
                        XPC_SCRIPTABLE_WANT_CONSTRUCT | \
                        XPC_SCRIPTABLE_WANT_HASINSTANCE | \
                        XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
@@ -1338,25 +1308,20 @@ nsXPCComponents_Exception::nsXPCComponen
 {
 }
 
 nsXPCComponents_Exception::~nsXPCComponents_Exception()
 {
     // empty
 }
 
-NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Exception)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Exception)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Exception)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(nsXPCComponents_Exception)
-NS_IMPL_RELEASE(nsXPCComponents_Exception)
+NS_IMPL_ISUPPORTS(nsXPCComponents_Exception,
+                  nsIXPCComponents_Exception,
+                  nsIXPCScriptable,
+                  nsIClassInfo)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsXPCComponents_Exception
 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Exception"
 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | \
                        XPC_SCRIPTABLE_WANT_CONSTRUCT | \
                        XPC_SCRIPTABLE_WANT_HASINSTANCE | \
                        XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
@@ -1703,25 +1668,20 @@ nsXPCConstructor::GetInterfaceID(nsIJSII
 }
 
 NS_IMETHODIMP
 nsXPCConstructor::GetInitializer(char * *aInitializer)
 {
     XPC_STRING_GETTER_BODY(aInitializer, mInitializer);
 }
 
-NS_INTERFACE_MAP_BEGIN(nsXPCConstructor)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCConstructor)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCConstructor)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(nsXPCConstructor)
-NS_IMPL_RELEASE(nsXPCConstructor)
+NS_IMPL_ISUPPORTS(nsXPCConstructor,
+                  nsIXPCConstructor,
+                  nsIXPCScriptable,
+                  nsIClassInfo)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsXPCConstructor
 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCConstructor"
 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | \
                        XPC_SCRIPTABLE_WANT_CONSTRUCT)
 #include "xpc_map_end.h" /* This will #undef the above */
 
@@ -1876,25 +1836,20 @@ nsXPCComponents_Constructor::nsXPCCompon
 {
 }
 
 nsXPCComponents_Constructor::~nsXPCComponents_Constructor()
 {
     // empty
 }
 
-NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Constructor)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Constructor)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Constructor)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(nsXPCComponents_Constructor)
-NS_IMPL_RELEASE(nsXPCComponents_Constructor)
+NS_IMPL_ISUPPORTS(nsXPCComponents_Constructor,
+                  nsIXPCComponents_Constructor,
+                  nsIXPCScriptable,
+                  nsIClassInfo)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsXPCComponents_Constructor
 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Constructor"
 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_CALL | \
                        XPC_SCRIPTABLE_WANT_CONSTRUCT | \
                        XPC_SCRIPTABLE_WANT_HASINSTANCE | \
                        XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE)
@@ -2073,24 +2028,19 @@ public:
 public:
     nsXPCComponents_Utils() { }
 
 private:
     virtual ~nsXPCComponents_Utils() { }
     nsCOMPtr<nsIXPCComponents_utils_Sandbox> mSandbox;
 };
 
-NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Utils)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Utils)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Utils)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(nsXPCComponents_Utils)
-NS_IMPL_RELEASE(nsXPCComponents_Utils)
+NS_IMPL_ISUPPORTS(nsXPCComponents_Utils,
+                  nsIXPCComponents_Utils,
+                  nsIXPCScriptable)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsXPCComponents_Utils
 #define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Utils"
 #define XPC_MAP_FLAGS XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE
 #include "xpc_map_end.h" /* This will #undef the above */
 
 NS_IMETHODIMP
@@ -3346,20 +3296,17 @@ private:
 };
 
 ComponentsSH ComponentsSH::singleton(0);
 
 // Singleton refcounting.
 NS_IMETHODIMP_(MozExternalRefCountType) ComponentsSH::AddRef(void) { return 1; }
 NS_IMETHODIMP_(MozExternalRefCountType) ComponentsSH::Release(void) { return 1; }
 
-NS_INTERFACE_MAP_BEGIN(ComponentsSH)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+NS_IMPL_QUERY_INTERFACE(ComponentsSH, nsIXPCScriptable)
 
 #define NSXPCCOMPONENTSBASE_CID \
 { 0xc62998e5, 0x95f1, 0x4058, \
   { 0xa5, 0x09, 0xec, 0x21, 0x66, 0x18, 0x92, 0xb9 } }
 
 #define NSXPCCOMPONENTS_CID \
 { 0x3649f405, 0xf0ec, 0x4c28, \
     { 0xae, 0xb0, 0xaf, 0x9a, 0x51, 0xe4, 0x4c, 0x81 } }
--- a/js/xpconnect/src/XPCJSID.cpp
+++ b/js/xpconnect/src/XPCJSID.cpp
@@ -209,23 +209,18 @@ class SharedScriptableHelperForJSIID fin
 {
     ~SharedScriptableHelperForJSIID() {}
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIXPCSCRIPTABLE
     SharedScriptableHelperForJSIID() {}
 };
 
-NS_INTERFACE_MAP_BEGIN(SharedScriptableHelperForJSIID)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCScriptable)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(SharedScriptableHelperForJSIID)
-NS_IMPL_RELEASE(SharedScriptableHelperForJSIID)
+NS_IMPL_ISUPPORTS(SharedScriptableHelperForJSIID,
+                  nsIXPCScriptable)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME SharedScriptableHelperForJSIID
 #define XPC_MAP_QUOTED_CLASSNAME "JSIID"
 #define XPC_MAP_FLAGS XPC_SCRIPTABLE_ALLOW_PROP_MODS_DURING_RESOLVE
 #include "xpc_map_end.h" /* This will #undef the above */
 
 static mozilla::StaticRefPtr<nsIXPCScriptable> gSharedScriptableHelperForJSIID;
@@ -271,23 +266,20 @@ void xpc_DestroyJSxIDClassObjects()
         gSharedScriptableHelperForJSIID = nullptr;
 
         gClassObjectsWereInited = false;
     }
 }
 
 /***************************************************************************/
 
-NS_INTERFACE_MAP_BEGIN(nsJSIID)
-  NS_INTERFACE_MAP_ENTRY(nsIJSID)
-  NS_INTERFACE_MAP_ENTRY(nsIJSIID)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSID)
-  NS_IMPL_QUERY_CLASSINFO(nsJSIID)
-NS_INTERFACE_MAP_END
+NS_IMPL_QUERY_INTERFACE_CI(nsJSIID,
+                           nsIJSID,
+                           nsIJSIID,
+                           nsIXPCScriptable)
 
 NS_IMPL_ADDREF(nsJSIID)
 NS_IMPL_RELEASE(nsJSIID)
 NS_IMPL_CI_INTERFACE_GETTER(nsJSIID, nsIJSID, nsIJSIID)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsJSIID
 #define XPC_MAP_QUOTED_CLASSNAME "nsJSIID"
@@ -528,23 +520,20 @@ nsJSIID::HasInstance(nsIXPConnectWrapped
 
     const nsIID* iid;
     mInfo->GetIIDShared(&iid);
     return xpc::HasInstance(cx, obj, iid, bp);
 }
 
 /***************************************************************************/
 
-NS_INTERFACE_MAP_BEGIN(nsJSCID)
-  NS_INTERFACE_MAP_ENTRY(nsIJSID)
-  NS_INTERFACE_MAP_ENTRY(nsIJSCID)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSID)
-  NS_IMPL_QUERY_CLASSINFO(nsJSCID)
-NS_INTERFACE_MAP_END
+NS_IMPL_QUERY_INTERFACE_CI(nsJSCID,
+                           nsIJSID,
+                           nsIJSCID,
+                           nsIXPCScriptable)
 
 NS_IMPL_ADDREF(nsJSCID)
 NS_IMPL_RELEASE(nsJSCID)
 NS_IMPL_CI_INTERFACE_GETTER(nsJSCID, nsIJSID, nsIJSCID)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         nsJSCID
 #define XPC_MAP_QUOTED_CLASSNAME "nsJSCID"
--- a/js/xpconnect/src/XPCRuntimeService.cpp
+++ b/js/xpconnect/src/XPCRuntimeService.cpp
@@ -7,27 +7,22 @@
 #include "xpcprivate.h"
 
 #include "nsContentUtils.h"
 #include "BackstagePass.h"
 #include "nsDOMClassInfo.h"
 #include "nsIPrincipal.h"
 #include "mozilla/dom/BindingUtils.h"
 
-NS_INTERFACE_MAP_BEGIN(BackstagePass)
-  NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
-  NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
-  NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
-  NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCScriptable)
-NS_INTERFACE_MAP_END
-
-NS_IMPL_ADDREF(BackstagePass)
-NS_IMPL_RELEASE(BackstagePass)
+NS_IMPL_ISUPPORTS(BackstagePass,
+                  nsIXPCScriptable,
+                  nsIGlobalObject,
+                  nsIClassInfo,
+                  nsIScriptObjectPrincipal,
+                  nsISupportsWeakReference)
 
 // The nsIXPCScriptable map declaration that will generate stubs for us...
 #define XPC_MAP_CLASSNAME         BackstagePass
 #define XPC_MAP_QUOTED_CLASSNAME "BackstagePass"
 #define XPC_MAP_FLAGS (XPC_SCRIPTABLE_WANT_RESOLVE | \
                        XPC_SCRIPTABLE_WANT_ENUMERATE | \
                        XPC_SCRIPTABLE_WANT_FINALIZE | \
                        XPC_SCRIPTABLE_WANT_PRECREATE | \
--- a/mobile/android/modules/geckoview/GeckoViewContent.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewContent.jsm
@@ -52,17 +52,25 @@ class GeckoViewContent extends GeckoView
     switch (aEvent) {
       case "GeckoViewContent:ExitFullScreen":
         this.messageManager.sendAsyncMessage("GeckoView:DOMFullscreenExited");
         break;
       case "GeckoView:ZoomToInput":
         this.messageManager.sendAsyncMessage(aEvent);
         break;
       case "GeckoView:SetActive":
-        this.browser.docShellIsActive = aData.active;
+        if (aData.active) {
+          this.browser.setAttribute("primary", "true");
+          this.browser.focus();
+          this.browser.docShellIsActive = true;
+        } else {
+          this.browser.removeAttribute("primary");
+          this.browser.docShellIsActive = false;
+          this.browser.blur();
+        }
         break;
     }
   }
 
   unregister() {
     this.window.removeEventListener("MozDOMFullScreen:Entered", this,
                                     /* capture */ true);
     this.window.removeEventListener("MozDOMFullScreen:Exited", this,
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -1398,25 +1398,20 @@ nsPrefBranch::~nsPrefBranch()
 
   nsCOMPtr<nsIObserverService> observerService =
     mozilla::services::GetObserverService();
   if (observerService) {
     observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
   }
 }
 
-NS_IMPL_ADDREF(nsPrefBranch)
-NS_IMPL_RELEASE(nsPrefBranch)
-
-NS_INTERFACE_MAP_BEGIN(nsPrefBranch)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefBranch)
-  NS_INTERFACE_MAP_ENTRY(nsIPrefBranch)
-  NS_INTERFACE_MAP_ENTRY(nsIObserver)
-  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
-NS_INTERFACE_MAP_END
+NS_IMPL_ISUPPORTS(nsPrefBranch,
+                  nsIPrefBranch,
+                  nsIObserver,
+                  nsISupportsWeakReference)
 
 NS_IMETHODIMP
 nsPrefBranch::GetRoot(nsACString& aRoot)
 {
   aRoot = mPrefRoot;
   return NS_OK;
 }
 
@@ -2235,24 +2230,19 @@ nsPrefBranch::GetPrefName(const char* aP
 //----------------------------------------------------------------------------
 // nsPrefLocalizedString
 //----------------------------------------------------------------------------
 
 nsPrefLocalizedString::nsPrefLocalizedString() = default;
 
 nsPrefLocalizedString::~nsPrefLocalizedString() = default;
 
-NS_IMPL_ADDREF(nsPrefLocalizedString)
-NS_IMPL_RELEASE(nsPrefLocalizedString)
-
-NS_INTERFACE_MAP_BEGIN(nsPrefLocalizedString)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefLocalizedString)
-  NS_INTERFACE_MAP_ENTRY(nsIPrefLocalizedString)
-  NS_INTERFACE_MAP_ENTRY(nsISupportsString)
-NS_INTERFACE_MAP_END
+NS_IMPL_ISUPPORTS(nsPrefLocalizedString,
+                  nsIPrefLocalizedString,
+                  nsISupportsString)
 
 nsresult
 nsPrefLocalizedString::Init()
 {
   nsresult rv;
   mUnicodeString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
 
   return rv;
@@ -2952,26 +2942,21 @@ Preferences::~Preferences()
   gHashTable = nullptr;
 
   delete gTelemetryLoadData;
   gTelemetryLoadData = nullptr;
 
   gPrefNameArena.Clear();
 }
 
-NS_IMPL_ADDREF(Preferences)
-NS_IMPL_RELEASE(Preferences)
-
-NS_INTERFACE_MAP_BEGIN(Preferences)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefService)